-- Copyright (c) 2020-2021 shadmansaleh
-- MIT license, see LICENSE for more details.

local helpers = require('tests.helpers')

local eq = assert.are.same
local neq = assert.are_not.same
local assert_component = helpers.assert_component
local build_component_opts = helpers.build_component_opts
local stub = require('luassert.stub')

describe('Component:', function()
  it('can select separators', function()
    local opts = build_component_opts()
    local comp = require('lualine.components.special.function_component')(opts)
    -- correct for lualine_c
    eq('', comp.options.separator)
    local opts2 = build_component_opts { self = { section = 'y' } }
    local comp2 = require('lualine.components.special.function_component')(opts2)
    -- correct for lualine_u
    eq('', comp2.options.separator)
  end)

  it('can provide unique identifier', function()
    local opts1 = build_component_opts()
    local comp1 = require('lualine.components.special.function_component')(opts1)
    local opts2 = build_component_opts()
    local comp2 = require('lualine.components.special.function_component')(opts2)
    neq(comp1.component_no, comp2.component_no)
  end)

  it('create option highlights', function()
    local color = { fg = '#224532', bg = '#892345' }
    local opts1 = build_component_opts { color = color }
    local hl = require('lualine.highlight')
    stub(hl, 'create_component_highlight_group')
    hl.create_component_highlight_group.returns('MyCompHl')
    local comp1 = require('lualine.components.special.function_component')(opts1)
    eq('MyCompHl', comp1.options.color_highlight)
    -- color highlight wan't in options when create_comp_hl was
    -- called so remove it before assert
    comp1.options.color_highlight = nil
    assert
      .stub(hl.create_component_highlight_group)
      .was_called_with(color, comp1.options.component_name, comp1.options, false)
    hl.create_component_highlight_group:revert()
    color = 'MyHl'
    local opts2 = build_component_opts { color = color }
    stub(hl, 'create_component_highlight_group')
    hl.create_component_highlight_group.returns('MyCompLinkedHl')
    local comp2 = require('lualine.components.special.function_component')(opts2)
    eq('MyCompLinkedHl', comp2.options.color_highlight)
    -- color highlight wan't in options when create_comp_hl was
    -- called so remove it before assert
    comp2.options.color_highlight = nil
    assert
      .stub(hl.create_component_highlight_group)
      .was_called_with(color, comp2.options.component_name, comp2.options, false)
    hl.create_component_highlight_group:revert()
  end)

  it('can draw', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
    }
    assert_component(nil, opts, 'test')
  end)

  it('can apply separators', function()
    local opts = build_component_opts { padding = 0 }
    assert_component(nil, opts, 'test')
  end)

  it('can apply default highlight', function()
    local opts = build_component_opts { padding = 0, hl = '%#My_highlight#' }
    assert_component(nil, opts, '%#My_highlight#test')
    opts = build_component_opts {
      function()
        return '%#Custom_hl#test'
      end,
      padding = 0,
      hl = '%#My_highlight#',
    }
    assert_component(nil, opts, '%#Custom_hl#test%#My_highlight#')
    opts = build_component_opts {
      function()
        return 'in middle%#Custom_hl#test'
      end,
      padding = 0,
      hl = '%#My_highlight#',
    }
    assert_component(nil, opts, '%#My_highlight#in middle%#Custom_hl#test%#My_highlight#')
  end)

  describe('Global options:', function()
    it('left_padding', function()
      local opts = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = { left = 5 },
      }
      assert_component(nil, opts, '     test')
    end)

    it('right_padding', function()
      local opts = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = { right = 5 },
      }
      assert_component(nil, opts, 'test     ')
    end)

    it('padding', function()
      local opts = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = 5,
      }
      assert_component(nil, opts, '     test     ')
    end)

    it('icon', function()
      local opts = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = 0,
        icon = '0',
      }
      assert_component(nil, opts, '0 test')
    end)

    it('icons_enabled', function()
      local opts = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = 0,
        icons_enabled = true,
        icon = '0',
      }
      assert_component(nil, opts, '0 test')
      local opts2 = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = 0,
        icons_enabled = false,
        icon = '0',
      }
      assert_component(nil, opts2, 'test')
    end)

    it('separator', function()
      local opts = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = 0,
        separator = '|',
      }
      assert_component(nil, opts, 'test|')
    end)

    it('fmt', function()
      local opts = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = 0,
        fmt = function(data)
          return data:sub(1, 1):upper() .. data:sub(2, #data)
        end,
      }
      assert_component(nil, opts, 'Test')
    end)

    it('cond', function()
      local opts = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = 0,
        cond = function()
          return true
        end,
      }
      assert_component(nil, opts, 'test')
      local opts2 = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = 0,
        cond = function()
          return false
        end,
      }
      assert_component(nil, opts2, '')
    end)

    it('color', function()
      local opts = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = 0,
        color = 'MyHl',
      }
      local comp = require('lualine.components.special.function_component')(opts)
      local custom_link_hl_name = 'lualine_c_' .. comp.options.component_name
      eq('%#' .. custom_link_hl_name .. '#test', comp:draw(opts.hl))
      local opts2 = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = 0,
        color = { bg = '#230055', fg = '#223344' },
      }
      local hl = require('lualine.highlight')
      stub(hl, 'component_format_highlight')
      hl.component_format_highlight.returns('%#MyCompHl#')
      local comp2 = require('lualine.components.special.function_component')(opts2)
      assert_component(nil, opts2, '%#MyCompHl#test')
      assert.stub(hl.component_format_highlight).was_called_with(comp2.options.color_highlight)
      hl.component_format_highlight:revert()
    end)

    it('draw_empty', function()
      local opts = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = 0,
        separator = '>',
        fmt = function()
          return ''
        end,
        draw_empty = true,
      }
      assert_component(nil, opts, '>')
    end)
  end)
end)

describe('Encoding component', function()
  it('works', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
    }
    local tmp_path = 'tmp.txt'
    local tmp_fp = io.open(tmp_path, 'w')
    tmp_fp:write('test file')
    tmp_fp:close()
    vim.cmd('e ' .. tmp_path)
    assert_component('encoding', opts, 'utf-8')
    vim.cmd('bd!')
    os.remove(tmp_path)
  end)
  it('works with BOM and default config', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
    }
    local tmp_path = 'tmp.txt'
    local tmp_fp = io.open(tmp_path, 'w')
    tmp_fp:write('test file')
    tmp_fp:close()
    vim.cmd('e ' .. tmp_path)
    vim.cmd('set bomb')
    assert_component('encoding', opts, 'utf-8')
    vim.cmd('bd!')
    os.remove(tmp_path)
  end)
  it('works with BOM and option enabled', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      show_bomb = true,
    }
    local tmp_path = 'tmp.txt'
    local tmp_fp = io.open(tmp_path, 'w')
    tmp_fp:write('test file')
    tmp_fp:close()
    vim.cmd('e ' .. tmp_path)
    vim.cmd('set bomb')
    assert_component('encoding', opts, 'utf-8 [BOM]')
    vim.cmd('bd!')
    os.remove(tmp_path)
  end)
end)

describe('Fileformat component', function()
  it('works with icons', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
    }
    local fmt = vim.bo.fileformat
    vim.bo.fileformat = 'unix'
    assert_component('fileformat', opts, '')
    vim.bo.fileformat = 'dos'
    assert_component('fileformat', opts, '')
    vim.bo.fileformat = 'mac'
    assert_component('fileformat', opts, '')
    vim.bo.fileformat = fmt
  end)
  it('works without icons', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      icons_enabled = false,
    }
    assert_component('fileformat', opts, vim.bo.fileformat)
  end)
end)

describe('Filetype component', function()
  local filetype

  before_each(function()
    filetype = vim.bo.filetype
    vim.bo.filetype = 'lua'
  end)

  after_each(function()
    vim.bo.filetype = filetype
  end)

  it('does not add icon when library unavailable', function()
    local old_require = _G.require
    function _G.require(...)
      if select(1, ...) == 'nvim-web-devicons' then
        error('Test case not suppose to have web-dev-icon 👀')
      end
      return old_require(...)
    end
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
    }
    assert_component('filetype', opts, 'lua')
    _G.require = old_require
  end)

  it('colors nvim-web-devicons icons', function()
    vim.g.actual_curwin = tostring(vim.api.nvim_get_current_win())
    stub(vim.fn, 'expand')
    vim.fn.expand.on_call_with('%:t').returns('test.lua')

    local hl = require('lualine.highlight')
    stub(hl, 'create_component_highlight_group')
    hl.create_component_highlight_group.returns { name = 'MyCompHl', no_mode = false, section = 'a' }
    stub(hl, 'format_highlight')
    hl.format_highlight.returns('%#lualine_c_normal#')

    local utils = require('lualine.utils.utils')
    stub(utils, 'extract_highlight_colors')
    utils.extract_highlight_colors.returns('#000')

    local devicons = require('nvim-web-devicons')
    stub(devicons, 'get_icon')
    devicons.get_icon.on_call_with('test.lua').returns('*', 'test_highlight_group')

    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      hl = '%#lualine_c_normal#',
      padding = 0,
      colored = true,
      icon_only = false,
    }
    assert_component('filetype', opts, '%#MyCompHl_normal#* %#lualine_c_normal#lua%#lualine_c_normal#')
    assert.stub(devicons.get_icon).was_called_with('test.lua')
    assert.stub(utils.extract_highlight_colors).was_called_with('test_highlight_group', 'fg')
    assert
      .stub(hl.create_component_highlight_group)
      .was_called_with({ fg = '#000' }, 'filetype_test_highlight_group', opts, false)
    assert.stub(vim.fn.expand).was_called_with('%:t')

    devicons.get_icon:revert()
    utils.extract_highlight_colors:revert()
    hl.create_component_highlight_group:revert()
    hl.format_highlight:revert()
    vim.fn.expand:revert()
    vim.g.actual_curwin = nil
  end)

  it("doesn't color when colored is false", function()
    stub(vim.fn, 'expand')
    vim.fn.expand.on_call_with('%:t').returns('test.lua')

    local hl = require('lualine.highlight')
    stub(hl, 'create_component_highlight_group')

    local utils = require('lualine.utils.utils')
    stub(utils, 'extract_highlight_colors')

    local devicons = require('nvim-web-devicons')
    stub(devicons, 'get_icon')
    devicons.get_icon.on_call_with('test.lua').returns('*', 'test_highlight_group')

    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      colored = false,
    }
    assert_component('filetype', opts, '* lua')
    assert.stub(devicons.get_icon).was_called_with('test.lua')
    assert.stub(utils.extract_highlight_colors).was_not_called()
    assert.stub(hl.create_component_highlight_group).was_not_called()
    assert.stub(vim.fn.expand).was_called_with('%:t')

    devicons.get_icon:revert()
    utils.extract_highlight_colors:revert()
    hl.create_component_highlight_group:revert()
    vim.fn.expand:revert()
  end)

  it('displays only icon when icon_only is true', function()
    stub(vim.fn, 'expand')
    vim.fn.expand.on_call_with('%:t').returns('test.lua')

    local hl = require('lualine.highlight')
    stub(hl, 'create_component_highlight_group')

    local utils = require('lualine.utils.utils')
    stub(utils, 'extract_highlight_colors')

    local devicons = require('nvim-web-devicons')
    stub(devicons, 'get_icon')
    devicons.get_icon.on_call_with('test.lua').returns('*', 'test_highlight_group')

    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      colored = false,
      icon_only = true,
    }
    assert_component('filetype', opts, '* ')
    assert.stub(devicons.get_icon).was_called_with('test.lua')
    assert.stub(utils.extract_highlight_colors).was_not_called()
    assert.stub(hl.create_component_highlight_group).was_not_called()
    assert.stub(vim.fn.expand).was_called_with('%:t')

    devicons.get_icon:revert()
    utils.extract_highlight_colors:revert()
    hl.create_component_highlight_group:revert()
    vim.fn.expand:revert()
  end)

  it('displays right aligned icon when icon.align is "right"', function()
    stub(vim.fn, 'expand')
    vim.fn.expand.on_call_with('%:t').returns('test.lua')

    local hl = require('lualine.highlight')
    stub(hl, 'create_component_highlight_group')

    local utils = require('lualine.utils.utils')
    stub(utils, 'extract_highlight_colors')

    local devicons = require('nvim-web-devicons')
    stub(devicons, 'get_icon')
    devicons.get_icon.on_call_with('test.lua').returns('*', 'test_highlight_group')

    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      colored = false,
      icon_only = false,
      icon = { align = 'right' },
    }
    assert_component('filetype', opts, 'lua * ')
    assert.stub(devicons.get_icon).was_called_with('test.lua')
    assert.stub(utils.extract_highlight_colors).was_not_called()
    assert.stub(hl.create_component_highlight_group).was_not_called()
    assert.stub(vim.fn.expand).was_called_with('%:t')

    devicons.get_icon:revert()
    utils.extract_highlight_colors:revert()
    hl.create_component_highlight_group:revert()
    vim.fn.expand:revert()
  end)

  it('uses filetype lookup when file has no extension', function()
    stub(vim.fn, 'expand')
    vim.fn.expand.on_call_with('%:t').returns('test')

    local hl = require('lualine.highlight')
    stub(hl, 'create_component_highlight_group')

    local utils = require('lualine.utils.utils')
    stub(utils, 'extract_highlight_colors')

    local devicons = require('nvim-web-devicons')
    stub(devicons, 'get_icon')
    devicons.get_icon.on_call_with('test').returns(nil)
    stub(devicons, 'get_icon_by_filetype')
    devicons.get_icon_by_filetype.on_call_with('lua').returns('*', 'test_highlight_group')

    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      colored = false,
      icon_only = false,
    }
    assert_component('filetype', opts, '* lua')
    assert.stub(devicons.get_icon).was_called_with('test')
    assert.stub(devicons.get_icon_by_filetype).was_called_with('lua')
    assert.stub(utils.extract_highlight_colors).was_not_called()
    assert.stub(hl.create_component_highlight_group).was_not_called()
    assert.stub(vim.fn.expand).was_called_with('%:t')

    devicons.get_icon_by_filetype:revert()
    devicons.get_icon:revert()
    utils.extract_highlight_colors:revert()
    hl.create_component_highlight_group:revert()
    vim.fn.expand:revert()
  end)
end)

describe('Hostname component', function()
  it('works', function()
    stub(vim.loop, 'os_gethostname')
    vim.loop.os_gethostname.returns('localhost')
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
    }
    assert_component('hostname', opts, 'localhost')
    vim.loop.os_gethostname:revert()
  end)
end)

describe('Location component', function()
  it('works', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
    }
    assert_component('location', opts, '  1:1 ')
    vim.cmd('normal! 9o')
    assert_component('location', opts, ' 10:1 ')
    vim.api.nvim_win_set_cursor(0, { 5, 0 })
    assert_component('location', opts, '  5:1 ')
    -- test column number
    vim.cmd('normal! oTest')
    assert_component('location', opts, '  6:4 ')
    -- test column number in line containing cyrillic symbols
    vim.cmd('normal! oТест')
    assert_component('location', opts, '  7:4 ')
    vim.cmd('bdelete!')
  end)
end)

describe('Progress component', function()
  it('works', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
    }
    assert_component('progress', opts, 'Top')
    vim.cmd('normal! 9o')
    assert_component('progress', opts, 'Bot')
    vim.api.nvim_win_set_cursor(0, { 5, 0 })
    assert_component('progress', opts, '50%%')
    vim.cmd('bdelete!')
  end)
end)

describe('Mode component', function()
  it('works', function()
    stub(vim.api, 'nvim_get_mode')
    vim.api.nvim_get_mode.returns { mode = 'n', blocking = false }
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
    }
    assert_component('mode', opts, 'NORMAL')
    vim.api.nvim_get_mode:revert()
  end)
end)

describe('FileSize component', function()
  it('works', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
    }
    local fname = 'test-file.txt'
    local f = io.open(fname, 'w')
    f:write(string.rep('........................................\n', 200))
    f:close()
    vim.cmd(':edit ' .. fname)
    assert_component('filesize', opts, '8.0k')
    vim.cmd(':bdelete!')
    os.remove(fname)
  end)
end)

describe('Filename component', function()
  it('works', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      file_status = false,
      path = 0,
    }
    vim.cmd(':e test-file.txt')
    assert_component('filename', opts, 'test-file.txt')
    vim.cmd(':bdelete!')
  end)

  it('can show file_status', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      file_status = true,
      path = 0,
    }
    vim.cmd(':e test-file.txt')
    vim.bo.modified = false
    assert_component('filename', opts, 'test-file.txt')
    vim.bo.modified = true
    assert_component('filename', opts, 'test-file.txt [+]')
    vim.bo.ro = true
    assert_component('filename', opts, 'test-file.txt [+][-]')
    vim.bo.modified = false
    assert_component('filename', opts, 'test-file.txt [-]')
    vim.cmd(':bdelete!')
  end)

  it('can show new_file_status', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      newfile_status = true,
      path = 0,
    }
    vim.cmd(':e new-file.txt')
    assert_component('filename', opts, 'new-file.txt [New]')
    vim.bo.modified = true
    assert_component('filename', opts, 'new-file.txt [+][New]')
    vim.cmd(':bdelete!')
  end)

  it('can show relative path', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      file_status = false,
      path = 1,
    }
    vim.cmd(':e test-file.txt')
    assert_component('filename', opts, vim.fn.expand('%:~:.'))
    vim.cmd(':bdelete!')
  end)

  it('can show full path', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      file_status = false,
      path = 2,
      shorting_target = 0,
    }
    vim.cmd(':e test-file.txt')
    assert_component('filename', opts, vim.fn.expand('%:p'))
    vim.cmd(':bdelete!')
  end)

  it('shortens path', function()
    stub(vim.fn, 'expand')
    vim.fn.expand.on_call_with('%:p').returns('/home/foobar/test/test.lua')
    stub(vim.fn, 'winwidth')
    vim.fn.winwidth.on_call_with(0).returns(100)

    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      file_status = false,
      path = 2,
      shorting_target = 90,
    }
    vim.cmd(':e test-file.txt')
    assert_component('filename', opts, '/h/f/t/test.lua')

    vim.cmd(':bdelete!')
    vim.fn.winwidth:revert()
    vim.fn.expand:revert()
  end)

  it('shortens path with tilde', function()
    stub(vim.fn, 'expand')
    vim.fn.expand.on_call_with('%:p:~').returns('~/test/test.lua')
    stub(vim.fn, 'winwidth')
    vim.fn.winwidth.on_call_with(0).returns(100)

    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      file_status = false,
      path = 3,
      shorting_target = 90,
    }
    vim.cmd(':e test-file.txt')
    assert_component('filename', opts, '~/t/test.lua')

    vim.cmd(':bdelete!')
    vim.fn.winwidth:revert()
    vim.fn.expand:revert()
  end)

  it('shortens path with hidden directory', function()
    stub(vim.fn, 'expand')
    vim.fn.expand.on_call_with('%:p').returns('/home/foobar/.test/test.lua')
    stub(vim.fn, 'winwidth')
    vim.fn.winwidth.on_call_with(0).returns(100)

    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      file_status = false,
      path = 2,
      shorting_target = 90,
    }
    vim.cmd(':e test-file.txt')
    assert_component('filename', opts, '/h/f/.t/test.lua')

    vim.cmd(':bdelete!')
    vim.fn.winwidth:revert()
    vim.fn.expand:revert()
  end)

  it('shortens path with %', function()
    stub(vim.fn, 'expand')
    vim.fn.expand.on_call_with('%:p').returns('%dir1/%dir2/%actual_%file')
    stub(vim.fn, 'winwidth')
    vim.fn.winwidth.on_call_with(0).returns(100)

    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      file_status = false,
      path = 2,
      shorting_target = 90,
    }
    vim.cmd(':e test-file.txt')
    assert_component('filename', opts, '%%/%%/%%actual_%%file')

    vim.cmd(':bdelete!')
    vim.fn.winwidth:revert()
    vim.fn.expand:revert()
  end)
end)

describe('vim option & variable component', function()
  local opts = build_component_opts {
    component_separators = { left = '', right = '' },
    padding = 0,
  }

  local function assert_vim_var_component(name, options, result)
    options[1] = name
    assert_component('special.vim_var_component', options, result)
    opts[1] = nil
  end
  it('works with variable', function()
    assert_vim_var_component('g:gvar', opts, '')
    vim.g.gvar = 'var1'
    assert_vim_var_component('g:gvar', opts, 'var1')
    vim.g.gvar = 'var2'
    assert_vim_var_component('g:gvar', opts, 'var2')
    vim.b.gvar = 'bvar1'
    assert_vim_var_component('b:gvar', opts, 'bvar1')
    vim.w.gvar = 'wvar1'
    assert_vim_var_component('w:gvar', opts, 'wvar1')
  end)
  it('can index dictionaries', function()
    vim.g.gvar = { a = { b = 'var-value' } }
    assert_vim_var_component('g:gvar.a.b', opts, 'var-value')
  end)
  it('works with options', function()
    local old_number = vim.wo.number
    vim.wo.number = false
    assert_vim_var_component('wo:number', opts, 'false')
    vim.wo.number = old_number
    local old_tw = vim.go.tw
    vim.go.tw = 80
    assert_vim_var_component('go:tw', opts, '80')
    vim.go.tw = old_tw
  end)
end)

describe('Vim option & variable component', function()
  local opts = build_component_opts {
    component_separators = { left = '', right = '' },
    padding = 0,
  }

  local function assert_vim_var_component(name, options, result)
    options[1] = name
    assert_component('special.eval_func_component', options, result)
    opts[1] = nil
  end

  it('works with vim function', function()
    vim.cmd([[
      func! TestFunction() abort
        return "TestVimFunction"
      endf
    ]])
    assert_vim_var_component('TestFunction', opts, 'TestVimFunction')
    vim.cmd('delfunction TestFunction')
  end)

  it('works with lua expression', function()
    _G.TestFunction = function()
      return 'TestLuaFunction'
    end
    assert_vim_var_component('TestFunction()', opts, 'TestLuaFunction')
    _G.TestFunction = nil
  end)
end)

describe('Branch component', function()
  -- these tests are broken in wsl will look at them later
  if vim.fn.has('wsl') == 1 then
    return
  end
  local tmpdir
  local file
  local git = function(...)
    return vim.fn.system(
      "git -c user.name='asdf' -c user.email='asdf@jlk.org' -C " .. tmpdir .. ' ' .. string.format(...)
    )
  end
  local assert_comp_ins = helpers.assert_component_instance

  before_each(function()
    tmpdir = os.tmpname()
    os.remove(tmpdir)
    file = tmpdir .. '/test.txt'
    vim.fn.mkdir(tmpdir, 'p')
    git('init -b test_branch')
    vim.cmd([[aug lualine
    au!
    aug END
  ]])
  end)

  after_each(function()
    os.remove(tmpdir)
  end)

  it('works with regular branches', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
    }
    local branch_comp = helpers.init_component('branch', opts)
    vim.cmd('e ' .. file)
    assert_comp_ins(branch_comp, ' test_branch')
    git('checkout -b test_branch2')
    vim.cmd('e k')
    vim.cmd('bd')
    vim.cmd('e ' .. file)
    opts.icons_enabled = false
    assert_comp_ins(branch_comp, 'test_branch2')
  end)

  it('works in detached head mode', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      icons_enabled = false,
      padding = 0,
    }
    git('checkout -b test_branch2')
    git('commit --allow-empty -m "test commit1"')
    git('commit --allow-empty -m "test commit2"')
    git('commit --allow-empty -m "test commit3"')
    git('checkout HEAD~1')
    vim.cmd('e ' .. file)
    local rev = git('rev-parse --short=6 HEAD'):sub(1, 6)
    assert_component('branch', opts, rev)
  end)
end)

describe('lsp_status component', function()
  local assert_comp_ins = helpers.assert_component_instance
  local tmpdir
  local file
  local opts = build_component_opts {
    component_separators = { left = '', right = '' },
    padding = 0,
    ignore_lsp = { 'null-ls' },
  }
  local lsp_status_comp

  before_each(function()
    require('lualine').setup {}
    tmpdir = os.tmpname()
    file = os.tmpname() .. '/test.lua'
    vim.cmd([[
      augroup lualine
        autocmd!
      augroup END
    ]])
    lsp_status_comp = helpers.init_component('lsp_status', opts)
  end)

  after_each(function()
    os.remove(tmpdir)
  end)

  it('is empty when no LSP', function()
    vim.cmd('edit ' .. file)
    stub(vim.lsp, 'get_clients')
    vim.lsp.get_clients.on_call_with({ bufnr = vim.api.nvim_get_current_buf() }).returns {}

    assert_comp_ins(lsp_status_comp, '')
  end)

  it('is empty when LSP is ignored', function()
    vim.cmd('edit ' .. file)
    stub(vim.lsp, 'get_clients')
    vim.lsp.get_clients.on_call_with({ bufnr = vim.api.nvim_get_current_buf() }).returns {
      { id = 2, name = 'null-ls' },
    }

    assert_comp_ins(lsp_status_comp, '')
  end)

  it('shows LSP name and icon when attached', function()
    vim.cmd('edit ' .. file)
    stub(vim.lsp, 'get_clients')
    vim.lsp.get_clients.on_call_with({ bufnr = vim.api.nvim_get_current_buf() }).returns { { id = 2, name = 'lua_ls' } }

    vim.cmd('edit ' .. file)

    assert_comp_ins(lsp_status_comp, ' lua_ls')
  end)

  it('shows LSP name and icon in older nvim version', function()
    vim.cmd('edit ' .. file)
    vim.lsp.get_clients = nil
    stub(vim.lsp, 'get_active_clients')
    ---@diagnostic disable-next-line: deprecated
    vim.lsp.get_active_clients
      .on_call_with({ bufnr = vim.api.nvim_get_current_buf() })
      .returns { { id = 2, name = 'lua_ls' } }

    assert_comp_ins(lsp_status_comp, ' lua_ls')
  end)

  it('shows LSP progress when supported', function()
    vim.cmd('edit ' .. file)
    stub(vim.lsp, 'get_clients')
    vim.lsp.get_clients.on_call_with({ bufnr = vim.api.nvim_get_current_buf() }).returns { { id = 2, name = 'lua_ls' } }
    if vim.uv and vim.uv.hrtime then
      stub(vim.uv, 'hrtime')
      vim.uv.hrtime.on_call_with().returns(12 * 1e6 * 80)
    elseif vim.loop and vim.loop.hrtime then
      stub(vim.loop, 'hrtime')
      vim.loop.hrtime.on_call_with().returns(12 * 1e6 * 80)
    end

    local ok = pcall(vim.api.nvim_exec_autocmds, 'LspProgress', {
      data = { client_id = 2, params = { value = { kind = 'begin' } } },
    })

    -- Skip assertion if LSP progress updates are not supported by the current `nvim` version.
    if ok then
      -- Use spinner symbol 12 (time) % 10 (symbol table size) = 2.
      assert_comp_ins(lsp_status_comp, ' lua_ls ⠹')
    end
  end)

  it('shows LSP done when supported', function()
    vim.cmd('edit ' .. file)
    stub(vim.lsp, 'get_clients')
    vim.lsp.get_clients.on_call_with({ bufnr = vim.api.nvim_get_current_buf() }).returns { { id = 2, name = 'lua_ls' } }

    local ok = pcall(vim.api.nvim_exec_autocmds, 'LspProgress', {
      data = { client_id = 2, params = { value = { kind = 'begin' } } },
    }) and pcall(vim.api.nvim_exec_autocmds, 'LspProgress', {
      data = { client_id = 2, params = { value = { kind = 'end' } } },
    })

    -- Skip assertion if LSP progress updates are not supported by the current `nvim` version.
    if ok then
      assert_comp_ins(lsp_status_comp, ' lua_ls ✓')
    end
  end)

  it('shows LSP done when supported and no begin', function()
    vim.cmd('edit ' .. file)
    stub(vim.lsp, 'get_clients')
    vim.lsp.get_clients.on_call_with({ bufnr = vim.api.nvim_get_current_buf() }).returns { { id = 2, name = 'lua_ls' } }

    local ok = pcall(vim.api.nvim_exec_autocmds, 'LspProgress', {
      data = { client_id = 2, params = { value = { kind = 'end' } } },
    })

    -- Skip assertion if LSP progress updates are not supported by the current `nvim` version.
    if ok then
      assert_comp_ins(lsp_status_comp, ' lua_ls ✓')
    end
  end)

  it('does not add unnecessary space separator when LSP progress symbol is empty', function()
    local opts_no_done = opts
    opts_no_done.symbols = { done = '' }
    local lsp_status_comp_no_done = helpers.init_component('lsp_status', opts)

    vim.cmd('edit ' .. file)
    stub(vim.lsp, 'get_clients')
    vim.lsp.get_clients.on_call_with({ bufnr = vim.api.nvim_get_current_buf() }).returns { { id = 2, name = 'lua_ls' } }

    local ok = pcall(vim.api.nvim_exec_autocmds, 'LspProgress', {
      data = { client_id = 2, params = { value = { kind = 'begin' } } },
    }) and pcall(vim.api.nvim_exec_autocmds, 'LspProgress', {
      data = { client_id = 2, params = { value = { kind = 'end' } } },
    })

    -- Skip assertion if LSP progress updates are not supported by the current `nvim` version.
    if ok then
      assert_comp_ins(lsp_status_comp_no_done, ' lua_ls')
    end
  end)
end)
