Before:
  call ale#test#SetDirectory('/testplugin/test')
  call ale#test#SetFilename('dummy.txt')
  Save g:ale_buffer_info

  let g:ale_buffer_info = {}

  let g:old_filename = expand('%:p')
  let g:Callback = ''
  let g:expr_list = []
  let g:message_list = []
  let g:handle_code_action_called = 0
  let g:code_actions = []
  let g:options = {}
  let g:capability_checked = ''
  let g:conn_id = v:null
  let g:InitCallback = v:null

  runtime autoload/ale/lsp_linter.vim
  runtime autoload/ale/lsp.vim
  runtime autoload/ale/util.vim
  runtime autoload/ale/codefix.vim
  runtime autoload/ale/code_action.vim

  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)

    if a:linter.lsp is# 'tsserver'
        call ale#lsp#MarkConnectionAsTsserver(g:conn_id)
    endif

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

    let g:InitCallback = {-> ale#lsp_linter#OnInit(a:linter, l:details, a:Callback)}
  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

  function! ale#lsp#Send(conn_id, message) abort
    call add(g:message_list, a:message)

    return 42
  endfunction

  function! ale#util#Execute(expr) abort
    call add(g:expr_list, a:expr)
  endfunction

  function! ale#code_action#HandleCodeAction(code_action, options) abort
    let g:handle_code_action_called = 1
    Assert !get(a:options, 'should_save')
    call add(g:code_actions, a:code_action)
  endfunction

  function! ale#util#Input(message, value) abort
    return '2'
  endfunction

After:
  Restore

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

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

  unlet! g:capability_checked
  unlet! g:InitCallback
  unlet! g:old_filename
  unlet! g:conn_id
  unlet! g:Callback
  unlet! g:message_list
  unlet! g:expr_list
  unlet! b:ale_linters
  unlet! g:options
  unlet! g:code_actions
  unlet! g:handle_code_action_called

  runtime autoload/ale/lsp_linter.vim
  runtime autoload/ale/lsp.vim
  runtime autoload/ale/util.vim
  runtime autoload/ale/codefix.vim
  runtime autoload/ale/code_action.vim

Execute(Failed codefix responses should be handled correctly):
  call ale#codefix#HandleTSServerResponse(
  \ 1,
  \ {'command': 'getCodeFixes', 'request_seq': 3}
  \)
  AssertEqual g:handle_code_action_called, 0

Given typescript(Some typescript file):
  foo
  somelongerline ()
  bazxyzxyzxyz

Execute(getCodeFixes from tsserver should be handled):
  call ale#codefix#SetMap({3: {}})
  call ale#codefix#HandleTSServerResponse(1, {
    \ 'command': 'getCodeFixes',
    \ 'request_seq': 3,
    \ 'success': v:true,
    \ 'type': 'response',
    \ 'body': [
    \   {
    \     'description': 'Import default "x" from module "./z"',
    \     'fixName': 'import',
    \     'changes': [
    \       {
    \         'fileName': "/foo/bar/file1.ts",
    \         'textChanges': [
    \           {
    \             'end': {
    \               'line': 2,
    \               'offset': 1,
    \             },
    \             'newText': 'import x from "./z";^@',
    \             'start': {
    \               'line': 2,
    \               'offset': 1,
    \             }
    \           }
    \         ]
    \       }
    \     ]
    \   }
    \ ]
    \})

  AssertEqual g:handle_code_action_called, 1
  AssertEqual
  \ [
  \   {
  \     'description': 'codefix',
  \     'changes': [
  \       {
  \         'fileName': "/foo/bar/file1.ts",
  \         'textChanges': [
  \           {
  \             'end': {
  \               'line': 2,
  \               'offset': 1
  \             },
  \             'newText': 'import x from "./z";^@',
  \             'start': {
  \               'line': 2,
  \               'offset': 1
  \             }
  \           }
  \         ]
  \       }
  \     ]
  \   }
  \ ],
  \ g:code_actions

Execute(getCodeFixes from tsserver should be handled with user input if there are more than one action):
  call ale#codefix#SetMap({3: {}})
  call ale#codefix#HandleTSServerResponse(1, {
    \ 'command': 'getCodeFixes',
    \ 'request_seq': 3,
    \ 'success': v:true,
    \ 'type': 'response',
    \ 'body': [
    \   {
    \     'description': 'Import default "x" from module "./z"',
    \     'fixName': 'import',
    \     'changes': [
    \       {
    \         'fileName': "/foo/bar/file1.ts",
    \         'textChanges': [
    \           {
    \             'end': {
    \               'line': 2,
    \               'offset': 1,
    \             },
    \             'newText': 'import x from "./z";^@',
    \             'start': {
    \               'line': 2,
    \               'offset': 1,
    \             }
    \           }
    \         ]
    \       }
    \     ]
    \   },
    \   {
    \     'description': 'Import default "x" from module "./y"',
    \     'fixName': 'import',
    \     'changes': [
    \       {
    \         'fileName': "/foo/bar/file1.ts",
    \         'textChanges': [
    \           {
    \             'end': {
    \               'line': 2,
    \               'offset': 1,
    \             },
    \             'newText': 'import x from "./y";^@',
    \             'start': {
    \               'line': 2,
    \               'offset': 1,
    \             }
    \           }
    \         ]
    \       }
    \     ]
    \   }
    \ ]
    \})

  AssertEqual g:handle_code_action_called, 1
  AssertEqual
  \ [
  \   {
  \     'description': 'codefix',
  \     'changes': [
  \       {
  \         'fileName': "/foo/bar/file1.ts",
  \         'textChanges': [
  \           {
  \             'end': {
  \               'line': 2,
  \               'offset': 1
  \             },
  \             'newText': 'import x from "./y";^@',
  \             'start': {
  \               'line': 2,
  \               'offset': 1
  \             }
  \           }
  \         ]
  \       }
  \     ]
  \   }
  \ ],
  \ g:code_actions

Execute(Prints a tsserver error message when getCodeFixes unsuccessful):
  call ale#codefix#SetMap({3: {}})
  call ale#codefix#HandleTSServerResponse(1, {
  \ 'command': 'getCodeFixes',
  \ 'request_seq': 3,
  \ 'success': v:false,
  \ 'message': 'something is wrong',
  \})

  AssertEqual g:handle_code_action_called, 0
  AssertEqual ['echom ''Error while getting code fixes. Reason: something is wrong'''], g:expr_list

Execute(Does nothing when where are no code fixes):
  call ale#codefix#SetMap({3: {}})
  call ale#codefix#HandleTSServerResponse(1, {
  \ 'command': 'getCodeFixes',
  \ 'request_seq': 3,
  \ 'success': v:true,
  \ 'body': []
  \})

  AssertEqual g:handle_code_action_called, 0
  AssertEqual ['echom ''No code fixes available.'''], g:expr_list

Execute(tsserver codefix requests should be sent):
  call ale#linter#Reset()

  runtime ale_linters/typescript/tsserver.vim
  let g:ale_buffer_info = {bufnr(''): {'loclist': [{'lnum': 2, 'col': 5, 'code': 2304, 'linter_name': 'tsserver'}]}}
  call setpos('.', [bufnr(''), 2, 16, 0])

  " ALECodeAction
  call ale#codefix#Execute(0)

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

  AssertEqual type(function('type')), type(g:InitCallback)
  call g:InitCallback()

  AssertEqual 'code_actions', g:capability_checked
  AssertEqual
  \ 'function(''ale#codefix#HandleTSServerResponse'')',
  \ string(g:Callback)
  AssertEqual
  \ [
  \   ale#lsp#tsserver_message#Change(bufnr('')),
  \   [0, 'ts@getCodeFixes', {
  \     'startLine': 2,
  \     'startOffset': 16,
  \     'endLine': 2,
  \     'endOffset': 17,
  \     'file': expand('%:p'),
  \     'errorCodes': [2304],
  \   }]
  \ ],
  \ g:message_list

Execute(tsserver codefix requests should be sent only for error with code):
  call ale#linter#Reset()

  runtime ale_linters/typescript/tsserver.vim
  let g:ale_buffer_info = {bufnr(''): {'loclist': [{'lnum': 2, 'col': 16, 'linter_name': 'tsserver'}, {'lnum': 2, 'col': 16, 'code': 2304, 'linter_name': 'tsserver'}]}}
  call setpos('.', [bufnr(''), 2, 16, 0])

  " ALECodeAction
  call ale#codefix#Execute(0)

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

  AssertEqual type(function('type')), type(g:InitCallback)
  call g:InitCallback()

  AssertEqual 'code_actions', g:capability_checked
  AssertEqual
  \ 'function(''ale#codefix#HandleTSServerResponse'')',
  \ string(g:Callback)
  AssertEqual
  \ [
  \   ale#lsp#tsserver_message#Change(bufnr('')),
  \   [0, 'ts@getCodeFixes', {
  \     'startLine': 2,
  \     'startOffset': 16,
  \     'endLine': 2,
  \     'endOffset': 17,
  \     'file': expand('%:p'),
  \     'errorCodes': [2304],
  \   }]
  \ ],
  \ g:message_list

Execute(getApplicableRefactors from tsserver should be handled):
  call ale#codefix#SetMap({3: {
  \ 'buffer': expand('%:p'),
  \ 'line': 1,
  \ 'column': 2,
  \ 'end_line': 3,
  \ 'end_column': 4,
  \ 'connection_id': 0,
  \}})
  call ale#codefix#HandleTSServerResponse(1,
  \ {'seq': 0, 'request_seq': 3, 'type': 'response', 'success': v:true, 'body': [{'actions': [{'description': 'Extract to constant in enclosing scope', 'name': 'constant_scope_0'}], 'description': 'Extract constant', 'name': 'Extract Symbol'}, {'actions': [{'description': 'Extract to function in module scope', 'name': 'function_scope_1'}], 'description': 'Extract function', 'name': 'Extract Symbol'}], 'command': 'getApplicableRefactors'})

  AssertEqual
  \ [
  \   [0, 'ts@getEditsForRefactor', {
  \     'startLine': 1,
  \     'startOffset': 2,
  \     'endLine': 3,
  \     'endOffset': 5,
  \     'file': expand('%:p'),
  \     'refactor': 'Extract Symbol',
  \     'action': 'function_scope_1',
  \   }]
  \ ],
  \ g:message_list

Execute(getApplicableRefactors should print error on failure):
  call ale#codefix#SetMap({3: {
  \ 'buffer': expand('%:p'),
  \ 'line': 1,
  \ 'column': 2,
  \ 'end_line': 3,
  \ 'end_column': 4,
  \ 'connection_id': 0,
  \}})
  call ale#codefix#HandleTSServerResponse(1,
  \ {'seq': 0, 'request_seq': 3, 'type': 'response', 'success': v:false, 'message': 'oops', 'command': 'getApplicableRefactors'})

  AssertEqual ['echom ''Error while getting applicable refactors. Reason: oops'''], g:expr_list

Execute(getApplicableRefactors should do nothing if there are no refactors):
  call ale#codefix#SetMap({3: {
  \ 'buffer': expand('%:p'),
  \ 'line': 1,
  \ 'column': 2,
  \ 'end_line': 3,
  \ 'end_column': 4,
  \ 'connection_id': 0,
  \}})
  call ale#codefix#HandleTSServerResponse(1,
  \ {'seq': 0, 'request_seq': 3, 'type': 'response', 'success': v:true, 'body': [], 'command': 'getApplicableRefactors'})

  AssertEqual ['echom ''No applicable refactors available.'''], g:expr_list

Execute(getEditsForRefactor from tsserver should be handled):
  call ale#codefix#SetMap({3: {}})
  call ale#codefix#HandleTSServerResponse(1,
  \{'seq': 0, 'request_seq': 3, 'type': 'response', 'success': v:true, 'body': {'edits': [{'fileName': '/foo/bar/file.ts', 'textChanges': [{'end': {'offset': 35, 'line': 9}, 'newText': 'newFunction(app);', 'start': {'offset': 3, 'line': 8}}, {'end': {'offset': 4, 'line': 19}, 'newText': '^@function newFunction(app: Router) {^@    app.use(booExpressCsrf());^@    app.use(booExpressRequireHttps);^@}^@', 'start': {'offset': 4, 'line': 19}}]}], 'renameLocation': {'offset': 3, 'line': 8}, 'renameFilename': '/foo/bar/file.ts'}, 'command': 'getEditsForRefactor' }
  \)

  AssertEqual g:handle_code_action_called, 1
  AssertEqual
  \ [
  \   {
  \     'description': 'editsForRefactor',
  \     'changes': [{'fileName': '/foo/bar/file.ts', 'textChanges': [{'end': {'offset': 35, 'line': 9}, 'newText': 'newFunction(app);', 'start': {'offset': 3, 'line': 8}}, {'end': {'offset': 4, 'line': 19}, 'newText': '^@function newFunction(app: Router) {^@    app.use(booExpressCsrf());^@    app.use(booExpressRequireHttps);^@}^@', 'start': {'offset': 4, 'line': 19}}]}],
  \   }
  \ ],
  \ g:code_actions

Execute(getEditsForRefactor should print error on failure):
  call ale#codefix#SetMap({3: {}})
  call ale#codefix#HandleTSServerResponse(1,
  \{'seq': 0, 'request_seq': 3, 'type': 'response', 'success': v:false, 'message': 'oops', 'command': 'getEditsForRefactor' }
  \)

  AssertEqual ['echom ''Error while getting edits for refactor. Reason: oops'''], g:expr_list

Execute(Failed LSP responses should be handled correctly):
  call ale#codefix#HandleLSPResponse(
  \ 1,
  \ {'method': 'workspace/applyEdit', 'request_seq': 3}
  \)
  AssertEqual g:handle_code_action_called, 0

Given python(Some python file):
  def main():
      a = 1
      b = a + 2

Execute("workspace/applyEdit" from LSP should be handled):
  call ale#codefix#SetMap({3: {}})
  call ale#codefix#HandleLSPResponse(1,
  \ {'id': 0, 'jsonrpc': '2.0', 'method': 'workspace/applyEdit', 'params': {'edit': {'changes': {'file:///foo/bar/file.ts': [{'range': {'end': {'character': 27, 'line': 7}, 'start': {'character': 27, 'line': 7}}, 'newText': ', Config'}, {'range': {'end': {'character': 12, 'line': 96}, 'start': {'character': 2, 'line': 94}}, 'newText': 'await newFunction(redis, imageKey, cover, config);'}, {'range': {'end': {'character': 2, 'line': 99}, 'start': {'character': 2, 'line': 99}}, 'newText': '^@async function newFunction(redis: IRedis, imageKey: string, cover: Buffer, config: Config) {^@    try {^@        await redis.set(imageKey, cover, ''ex'', parseInt(config.coverKeyTTL, 10));^@    }^@    catch { }^@}^@'}]}}}})

  AssertEqual g:handle_code_action_called, 1
  AssertEqual
  \ [{'description': 'applyEdit', 'changes': [{'fileName': '/foo/bar/file.ts', 'textChanges': [{'end': {'offset': 28, 'line': 8}, 'newText': ', Config', 'start': {'offset': 28, 'line': 8}}, {'end': {'offset': 13, 'line': 97}, 'newText': 'await newFunction(redis, imageKey, cover, config);', 'start': {'offset': 3, 'line': 95}}, {'end': {'offset': 3, 'line': 100}, 'newText': '^@async function newFunction(redis: IRedis, imageKey: string, cover: Buffer, config: Config) {^@    try {^@        await redis.set(imageKey, cover, ''ex'', parseInt(config.coverKeyTTL, 10));^@    }^@    catch { }^@}^@', 'start': {'offset': 3, 'line': 100}}]}]}],
  \ g:code_actions

Execute(Code Actions from LSP should be handled when returned with documentChanges):
  call ale#codefix#SetMap({2: {}})
  call ale#codefix#HandleLSPResponse(1,
  \ {'id': 2, 'jsonrpc': '2.0', 'result': [{'diagnostics': v:null, 'edit': {'changes': v:null, 'documentChanges': [{'edits': [{'range': {'end': {'character': 4, 'line': 2}, 'start': {'character': 4, 'line': 1}}, 'newText': ''}, {'range': {'end': {'character': 9, 'line': 2}, 'start': {'character': 8, 'line': 2}}, 'newText': '(1)'}], 'textDocument': {'uri': 'file:///foo/bar/test.py', 'version': v:null}}]}, 'kind': 'refactor.inline', 'title': 'Inline variable', 'command': v:null}, {'diagnostics': v:null, 'edit': {'changes': v:null, 'documentChanges': [{'edits': [{'range': {'end': {'character': 0, 'line': 0}, 'start': {'character': 0, 'line': 0}}, 'newText': 'def func_bomdjnxh():^@    a = 1return a^@^@^@'}, {'range': {'end': {'character': 9, 'line': 1}, 'start': {'character': 8, 'line': 1}}, 'newText': 'func_bomdjnxh()^@'}], 'textDocument': {'uri': 'file:///foo/bar/test.py', 'version': v:null}}]}, 'kind': 'refactor.extract', 'title': 'Extract expression into function ''func_bomdjnxh''', 'command': v:null}]})

  AssertEqual g:handle_code_action_called, 1
  AssertEqual
  \ [{'description': 'codeaction', 'changes': [{'fileName': '/foo/bar/test.py', 'textChanges': [{'end': {'offset': 1, 'line': 1}, 'newText': 'def func_bomdjnxh():^@    a = 1return a^@^@^@', 'start': {'offset': 1, 'line': 1}}, {'end': {'offset': 10, 'line': 2}, 'newText': 'func_bomdjnxh()^@', 'start': {'offset': 9, 'line': 2}}]}]}],
  \ g:code_actions

Execute(LSP Code Actions handles CodeAction responses):
  call ale#codefix#SetMap({3: {
  \ 'connection_id': 0,
  \}})
  call ale#codefix#HandleLSPResponse(1,
  \ {'id': 3, 'jsonrpc': '2.0', 'result': [{'kind': 'refactor', 'title': 'Extract to inner function in function ''getVideo''', 'command': {'arguments': [{'file': '/foo/bar/file.ts', 'endOffset': 0, 'action': 'function_scope_0', 'startOffset': 1, 'startLine': 65, 'refactor': 'Extract Symbol', 'endLine': 68}], 'title': 'Extract to inner function in function ''getVideo''', 'command': '_typescript.applyRefactoring'}}, {'kind': 'refactor', 'title': 'Extract to function in module scope', 'command': {'arguments': [{'file': '/foo/bar/file.ts', 'endOffset': 0, 'action': 'function_scope_1', 'startOffset': 1, 'startLine': 65, 'refactor': 'Extract Symbol', 'endLine': 68}], 'title': 'Extract to function in module scope', 'command': '_typescript.applyRefactoring'}}]})

  AssertEqual
  \ [[0, 'workspace/executeCommand', {'arguments': [{'file': '/foo/bar/file.ts', 'action': 'function_scope_1', 'endOffset': 0, 'refactor': 'Extract Symbol', 'endLine': 68, 'startLine': 65, 'startOffset': 1}], 'command': '_typescript.applyRefactoring'}]],
  \ g:message_list

Execute(LSP Code Actions handles Command responses):
  call ale#codefix#SetMap({2: {
  \ 'connection_id': 2,
  \}})
  call ale#codefix#HandleLSPResponse(1,
  \ {'id': 2, 'jsonrpc': '2.0', 'result': [{'title': 'fake for testing'}, {'arguments': [{'documentChanges': [{'edits': [{'range': {'end': {'character': 31, 'line': 2}, 'start': {'character': 31, 'line': 2}}, 'newText': ', createVideo'}], 'textDocument': {'uri': 'file:///foo/bar/file.ts', 'version': 1}}]}], 'title': 'Add ''createVideo'' to existing import declaration from "./video"', 'command': '_typescript.applyWorkspaceEdit'}]})

  AssertEqual
  \ [[0, 'workspace/executeCommand', {'arguments': [{'documentChanges': [{'edits': [{'range': {'end': {'character': 31, 'line': 2}, 'start': {'character': 31, 'line': 2}}, 'newText': ', createVideo'}], 'textDocument': {'uri': 'file:///foo/bar/file.ts', 'version': 1}}]}], 'command': '_typescript.applyWorkspaceEdit'}]],
  \ g:message_list

Execute(Prints message when LSP code action returns no results):
  call ale#codefix#SetMap({3: {}})
  call ale#codefix#HandleLSPResponse(1,
  \ {'id': 3, 'jsonrpc': '2.0', 'result': []})

  AssertEqual g:handle_code_action_called, 0
  AssertEqual ['echom ''No code actions received from server'''], g:expr_list

Execute(LSP code action requests should be sent):
  call ale#linter#Reset()

  runtime ale_linters/python/jedils.vim
  let g:ale_buffer_info = {bufnr(''): {'loclist': [{'lnum': 2, 'col': 5, 'end_lnum': 2, 'end_col': 6, 'code': 2304, 'text': 'oops'}]}}
  call setpos('.', [bufnr(''), 2, 5, 0])

  " ALECodeAction
  call ale#codefix#Execute(0)

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

  AssertEqual type(function('type')), type(g:InitCallback)
  call g:InitCallback()

  AssertEqual 'code_actions', g:capability_checked
  AssertEqual
  \ 'function(''ale#codefix#HandleLSPResponse'')',
  \ string(g:Callback)
  AssertEqual
  \ [
  \   [0, 'textDocument/codeAction', {
  \     'context': {
  \       'diagnostics': [{'range': {'end': {'character': 6, 'line': 1}, 'start': {'character': 4, 'line': 1}}, 'code': 2304, 'message': 'oops'}]
  \     },
  \     'range': {'end': {'character': 5, 'line': 1}, 'start': {'character': 4, 'line': 1}},
  \     'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))}
  \   }]
  \ ],
  \ g:message_list[-1:]

Execute(LSP code action requests should be sent only for error with code):
  call ale#linter#Reset()

  runtime ale_linters/python/jedils.vim
  let g:ale_buffer_info = {bufnr(''): {'loclist': [{'lnum': 2, 'col': 5, 'end_lnum': 2, 'end_col': 6, 'code': 2304, 'text': 'oops'}]}}
  call setpos('.', [bufnr(''), 2, 5, 0])

  " ALECodeAction
  call ale#codefix#Execute(0)

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

  AssertEqual type(function('type')), type(g:InitCallback)
  call g:InitCallback()

  AssertEqual 'code_actions', g:capability_checked
  AssertEqual
  \ 'function(''ale#codefix#HandleLSPResponse'')',
  \ string(g:Callback)
  AssertEqual
  \ [
  \   [0, 'textDocument/codeAction', {
  \     'context': {
  \       'diagnostics': [{'range': {'end': {'character': 6, 'line': 1}, 'start': {'character': 4, 'line': 1}}, 'code': 2304, 'message': 'oops'}]
  \     },
  \     'range': {'end': {'character': 5, 'line': 1}, 'start': {'character': 4, 'line': 1}},
  \     'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))}
  \   }]
  \ ],
  \ g:message_list[-1:]
