local helpers = require('test.functional.helpers')(after_each)

local Screen = require('test.functional.ui.screen')

local meths = helpers.meths
local command = helpers.command
local clear = helpers.clear
local exc_exec = helpers.exc_exec
local eval = helpers.eval
local eq = helpers.eq
local funcs = helpers.funcs
local insert = helpers.insert
local iswin = helpers.iswin
local neq = helpers.neq
local mkdir = helpers.mkdir
local rmdir = helpers.rmdir
local alter_slashes = helpers.alter_slashes
local tbl_contains = helpers.tbl_contains

describe('startup defaults', function()
  describe(':filetype', function()
    local function expect_filetype(expected)
      local screen = Screen.new(50, 4)
      screen:attach()
      command('filetype')
      screen:expect([[
        ^                                                  |
        ~                                                 |
        ~                                                 |
        ]]..expected
      )
    end

    it('all ON after `-u NORC`', function()
      clear('-u', 'NORC')
      expect_filetype(
        'filetype detection:ON  plugin:ON  indent:ON       |')
    end)

    it('all ON after `:syntax …` #7765', function()
      clear('-u', 'NORC', '--cmd', 'syntax on')
      expect_filetype(
        'filetype detection:ON  plugin:ON  indent:ON       |')
      clear('-u', 'NORC', '--cmd', 'syntax off')
      expect_filetype(
        'filetype detection:ON  plugin:ON  indent:ON       |')
    end)

    it('all OFF after `-u NONE`', function()
      clear('-u', 'NONE')
      expect_filetype(
        'filetype detection:OFF  plugin:OFF  indent:OFF    |')
    end)

    it('explicit OFF stays OFF', function()
      clear('-u', 'NORC', '--cmd',
            'syntax off | filetype off | filetype plugin indent off')
      expect_filetype(
        'filetype detection:OFF  plugin:OFF  indent:OFF    |')
      clear('-u', 'NORC', '--cmd', 'syntax off | filetype plugin indent off')
      expect_filetype(
        'filetype detection:ON  plugin:OFF  indent:OFF     |')
      clear('-u', 'NORC', '--cmd', 'filetype indent off')
      expect_filetype(
        'filetype detection:ON  plugin:ON  indent:OFF      |')
      clear('-u', 'NORC', '--cmd', 'syntax off | filetype off')
      expect_filetype(
        'filetype detection:OFF  plugin:(on)  indent:(on)  |')
      -- Swap the order.
      clear('-u', 'NORC', '--cmd', 'filetype off | syntax off')
      expect_filetype(
        'filetype detection:OFF  plugin:(on)  indent:(on)  |')
    end)

    it('all ON after early `:filetype … on`', function()
      -- `:filetype … on` should not change the defaults. #7765
      -- Only an explicit `:filetype … off` sets OFF.

      clear('-u', 'NORC', '--cmd', 'filetype on')
      expect_filetype(
        'filetype detection:ON  plugin:ON  indent:ON       |')
      clear('-u', 'NORC', '--cmd', 'filetype plugin on')
      expect_filetype(
        'filetype detection:ON  plugin:ON  indent:ON       |')
      clear('-u', 'NORC', '--cmd', 'filetype indent on')
      expect_filetype(
        'filetype detection:ON  plugin:ON  indent:ON       |')
    end)

    it('late `:filetype … off` stays OFF', function()
      clear('-u', 'NORC', '-c', 'filetype off')
      expect_filetype(
        'filetype detection:OFF  plugin:(on)  indent:(on)  |')
      clear('-u', 'NORC', '-c', 'filetype plugin off')
      expect_filetype(
        'filetype detection:ON  plugin:OFF  indent:ON      |')
      clear('-u', 'NORC', '-c', 'filetype indent off')
      expect_filetype(
        'filetype detection:ON  plugin:ON  indent:OFF      |')
    end)
  end)

  describe('syntax', function()
    it('enabled by `-u NORC`', function()
      clear('-u', 'NORC')
      eq(1, eval('g:syntax_on'))
    end)

    it('disabled by `-u NONE`', function()
      clear('-u', 'NONE')
      eq(0, eval('exists("g:syntax_on")'))
    end)

    it('`:syntax off` stays off', function()
      -- early
      clear('-u', 'NORC', '--cmd', 'syntax off')
      eq(0, eval('exists("g:syntax_on")'))
      -- late
      clear('-u', 'NORC', '-c', 'syntax off')
      eq(0, eval('exists("g:syntax_on")'))
    end)

    it('":if 0|syntax on|endif" does not affect default #8728', function()
      clear('-u', 'NORC', '--cmd', ':if 0|syntax on|endif')
      eq(1, eval('exists("g:syntax_on")'))
      clear('-u', 'NORC', '--cmd', ':if 0|syntax off|endif')
      eq(1, eval('exists("g:syntax_on")'))
    end)
  end)

  describe("'fillchars'", function()
    it('vert/fold flags', function()
      clear()
      local screen = Screen.new(50, 5)
      screen:attach()
      command('set laststatus=0')
      insert([[
        1
        2
        3
        4]])
      command('normal! ggjzfj')
      command('vsp')
      screen:expect([[
        1                        │1                       |
        ^+--  2 lines: 2··········│+--  2 lines: 2·········|
        4                        │4                       |
        ~                        │~                       |
                                                          |
      ]])

      -- ambiwidth=double defaults to single-byte fillchars.
      command('set ambiwidth=double')
      screen:expect([[
        1                        |1                       |
        ^+--  2 lines: 2----------|+--  2 lines: 2---------|
        4                        |4                       |
        ~                        |~                       |
                                                          |
      ]])
    end)
  end)

  it("'shadafile' ('viminfofile')", function()
    local env = {XDG_DATA_HOME='Xtest-userdata', XDG_CONFIG_HOME='Xtest-userconfig'}
    clear{args={}, args_rm={'-i'}, env=env}
    -- Default 'shadafile' is empty.
    -- This means use the default location. :help shada-file-name
    eq('', meths.get_option('shadafile'))
    eq('', meths.get_option('viminfofile'))
    -- Check that shada data (such as v:oldfiles) is saved/restored.
    command('edit Xtest-foo')
    command('write')
    local f = eval('fnamemodify(@%,":p")')
    assert(string.len(f) > 3)
    command('qall')
    clear{args={}, args_rm={'-i'}, env=env}
    eq({ f }, eval('v:oldfiles'))
    os.remove('Xtest-foo')
    rmdir('Xtest-userdata')

    -- Handles viminfo/viminfofile as alias for shada/shadafile.
    eq('\n  shadafile=', eval('execute("set shadafile?")'))
    eq('\n  shadafile=', eval('execute("set viminfofile?")'))
    eq("\n  shada=!,'100,<50,s10,h", eval('execute("set shada?")'))
    eq("\n  shada=!,'100,<50,s10,h", eval('execute("set viminfo?")'))
  end)

  it("'packpath'", function()
    clear()
    -- Defaults to &runtimepath.
    eq(meths.get_option('runtimepath'), meths.get_option('packpath'))

    -- Does not follow modifications to runtimepath.
    meths.command('set runtimepath+=foo')
    neq(meths.get_option('runtimepath'), meths.get_option('packpath'))
    meths.command('set packpath+=foo')
    eq(meths.get_option('runtimepath'), meths.get_option('packpath'))
  end)

  it('v:progpath is set to the absolute path', function()
    clear()
    eq(eval("fnamemodify(v:progpath, ':p')"), eval('v:progpath'))
  end)

  describe('$NVIM_LOG_FILE', function()
    local xdgdir = 'Xtest-startup-xdg-logpath'
    local xdgcachedir = xdgdir..'/nvim'
    after_each(function()
      os.remove('Xtest-logpath')
      rmdir(xdgdir)
    end)

    it('is used if expansion succeeds', function()
      clear({env={
        NVIM_LOG_FILE='Xtest-logpath',
      }})
      eq('Xtest-logpath', eval('$NVIM_LOG_FILE'))
    end)
    it('defaults to stdpath("cache")/log if empty', function()
      eq(true, mkdir(xdgdir) and mkdir(xdgcachedir))
      clear({env={
        XDG_CACHE_HOME=xdgdir,
        NVIM_LOG_FILE='',  -- Empty is invalid.
      }})
      eq(xdgcachedir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/'))
    end)
    it('defaults to stdpath("cache")/log if invalid', function()
      eq(true, mkdir(xdgdir) and mkdir(xdgcachedir))
      clear({env={
        XDG_CACHE_HOME=xdgdir,
        NVIM_LOG_FILE='.',  -- Any directory is invalid.
      }})
      eq(xdgcachedir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/'))
    end)
  end)
end)

describe('XDG-based defaults', function()
  -- Need separate describe() blocks to not run clear() twice.
  -- Do not put before_each() here for the same reasons.

  it("&runtimepath data-dir matches stdpath('data') #9910", function()
    clear()
    local rtp = eval('split(&runtimepath, ",")')
    local rv = {}
    local expected = (iswin()
                      and { [[\nvim-data\site]], [[\nvim-data\site\after]], }
                      or { '/nvim/site', '/nvim/site/after', })

    for _,v in ipairs(rtp) do
      local m = string.match(v, [=[[/\]nvim[^/\]*[/\]site.*$]=])
      if m and not tbl_contains(rv, m) then
        table.insert(rv, m)
      end
    end
    eq(expected, rv)
  end)

  describe('with empty/broken environment', function()
    it('sets correct defaults', function()
      clear({env={
        XDG_CONFIG_HOME=nil,
        XDG_DATA_HOME=nil,
        XDG_CACHE_HOME=nil,
        XDG_RUNTIME_DIR=nil,
        XDG_CONFIG_DIRS=nil,
        XDG_DATA_DIRS=nil,
        LOCALAPPDATA=nil,
        HOMEPATH=nil,
        HOMEDRIVE=nil,
        HOME=nil,
        TEMP=nil,
        VIMRUNTIME=nil,
        USER=nil,
      }})

      eq('.', meths.get_option('backupdir'))
      eq('.', meths.get_option('viewdir'))
      eq('.', meths.get_option('directory'))
      eq('.', meths.get_option('undodir'))
    end)
  end)

  local function vimruntime_and_libdir()
    local vimruntime = eval('$VIMRUNTIME')
    -- libdir is hard to calculate reliably across various ci platforms
    -- local libdir = string.gsub(vimruntime, "share/nvim/runtime$", "lib/nvim")
    local libdir = meths._get_lib_dir()
    return vimruntime, libdir
  end

  local env_sep = iswin() and ';' or ':'
  local data_dir = iswin() and 'nvim-data' or 'nvim'
  local root_path = iswin() and 'C:' or ''

  describe('with too long XDG variables', function()
    before_each(function()
      clear({env={
        XDG_CONFIG_HOME=(root_path .. ('/x'):rep(4096)),
        XDG_CONFIG_DIRS=(root_path .. ('/a'):rep(2048)
                         .. env_sep.. root_path .. ('/b'):rep(2048)
                         .. (env_sep .. root_path .. '/c'):rep(512)),
        XDG_DATA_HOME=(root_path .. ('/X'):rep(4096)),
        XDG_DATA_DIRS=(root_path .. ('/A'):rep(2048)
                       .. env_sep .. root_path .. ('/B'):rep(2048)
                       .. (env_sep .. root_path .. '/C'):rep(512)),
      }})
    end)

    it('are correctly set', function()
      local vimruntime, libdir = vimruntime_and_libdir()

      eq(((root_path .. ('/x'):rep(4096) .. '/nvim'
          .. ',' .. root_path .. ('/a'):rep(2048) .. '/nvim'
          .. ',' .. root_path .. ('/b'):rep(2048) .. '/nvim'
          .. (',' .. root_path .. '/c/nvim'):rep(512)
          .. ',' .. root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/site'
          .. ',' .. root_path .. ('/A'):rep(2048) .. '/nvim/site'
          .. ',' .. root_path .. ('/B'):rep(2048) .. '/nvim/site'
          .. (',' .. root_path .. '/C/nvim/site'):rep(512)
          .. ',' .. vimruntime
          .. ',' .. libdir
          .. (',' .. root_path .. '/C/nvim/site/after'):rep(512)
          .. ',' .. root_path .. ('/B'):rep(2048) .. '/nvim/site/after'
          .. ',' .. root_path .. ('/A'):rep(2048) .. '/nvim/site/after'
          .. ',' .. root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/site/after'
          .. (',' .. root_path .. '/c/nvim/after'):rep(512)
          .. ',' .. root_path .. ('/b'):rep(2048) .. '/nvim/after'
          .. ',' .. root_path .. ('/a'):rep(2048) .. '/nvim/after'
          .. ',' .. root_path .. ('/x'):rep(4096) .. '/nvim/after'
      ):gsub('\\', '/')), (meths.get_option('runtimepath')):gsub('\\', '/'))
      meths.command('set runtimepath&')
      meths.command('set backupdir&')
      meths.command('set directory&')
      meths.command('set undodir&')
      meths.command('set viewdir&')
      eq(((root_path .. ('/x'):rep(4096) .. '/nvim'
          .. ',' .. root_path .. ('/a'):rep(2048) .. '/nvim'
          .. ',' .. root_path .. ('/b'):rep(2048) .. '/nvim'
          .. (',' .. root_path .. '/c/nvim'):rep(512)
          .. ',' .. root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/site'
          .. ',' .. root_path .. ('/A'):rep(2048) .. '/nvim/site'
          .. ',' .. root_path .. ('/B'):rep(2048) .. '/nvim/site'
          .. (',' .. root_path .. '/C/nvim/site'):rep(512)
          .. ',' .. vimruntime
          .. ',' .. libdir
          .. (',' .. root_path .. '/C/nvim/site/after'):rep(512)
          .. ',' .. root_path .. ('/B'):rep(2048) .. '/nvim/site/after'
          .. ',' .. root_path .. ('/A'):rep(2048) .. '/nvim/site/after'
          .. ',' .. root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/site/after'
          .. (',' .. root_path .. '/c/nvim/after'):rep(512)
          .. ',' .. root_path .. ('/b'):rep(2048) .. '/nvim/after'
          .. ',' .. root_path .. ('/a'):rep(2048) .. '/nvim/after'
          .. ',' .. root_path .. ('/x'):rep(4096) .. '/nvim/after'
      ):gsub('\\', '/')), (meths.get_option('runtimepath')):gsub('\\', '/'))
      eq('.,' .. root_path .. ('/X'):rep(4096).. '/' .. data_dir .. '/backup',
         (meths.get_option('backupdir'):gsub('\\', '/')))
      eq(root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/swap//',
         (meths.get_option('directory')):gsub('\\', '/'))
      eq(root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/undo',
         (meths.get_option('undodir')):gsub('\\', '/'))
      eq(root_path .. ('/X'):rep(4096) .. '/'  ..  data_dir .. '/view',
         (meths.get_option('viewdir')):gsub('\\', '/'))
    end)
  end)

  describe('with XDG variables that can be expanded', function()
    before_each(function()
      clear({env={
        XDG_CONFIG_HOME='$XDG_DATA_HOME',
        XDG_CONFIG_DIRS='$XDG_DATA_DIRS',
        XDG_DATA_HOME='$XDG_CONFIG_HOME',
        XDG_DATA_DIRS='$XDG_CONFIG_DIRS',
      }})
    end)

    it('are not expanded', function()
      local vimruntime, libdir = vimruntime_and_libdir()
      eq((('$XDG_DATA_HOME/nvim'
          .. ',$XDG_DATA_DIRS/nvim'
          .. ',$XDG_CONFIG_HOME/' .. data_dir .. '/site'
          .. ',$XDG_CONFIG_DIRS/nvim/site'
          .. ',' .. vimruntime
          .. ',' .. libdir
          .. ',$XDG_CONFIG_DIRS/nvim/site/after'
          .. ',$XDG_CONFIG_HOME/' .. data_dir .. '/site/after'
          .. ',$XDG_DATA_DIRS/nvim/after'
          .. ',$XDG_DATA_HOME/nvim/after'
      ):gsub('\\', '/')), (meths.get_option('runtimepath')):gsub('\\', '/'))
      meths.command('set runtimepath&')
      meths.command('set backupdir&')
      meths.command('set directory&')
      meths.command('set undodir&')
      meths.command('set viewdir&')
      eq((('$XDG_DATA_HOME/nvim'
          .. ',$XDG_DATA_DIRS/nvim'
          .. ',$XDG_CONFIG_HOME/' .. data_dir .. '/site'
          .. ',$XDG_CONFIG_DIRS/nvim/site'
          .. ',' .. vimruntime
          .. ',' .. libdir
          .. ',$XDG_CONFIG_DIRS/nvim/site/after'
          .. ',$XDG_CONFIG_HOME/' .. data_dir .. '/site/after'
          .. ',$XDG_DATA_DIRS/nvim/after'
          .. ',$XDG_DATA_HOME/nvim/after'
      ):gsub('\\', '/')), (meths.get_option('runtimepath')):gsub('\\', '/'))
      eq(('.,$XDG_CONFIG_HOME/' .. data_dir .. '/backup'),
          meths.get_option('backupdir'):gsub('\\', '/'))
      eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/swap//'),
          meths.get_option('directory'):gsub('\\', '/'))
      eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/undo'),
          meths.get_option('undodir'):gsub('\\', '/'))
      eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/view'),
          meths.get_option('viewdir'):gsub('\\', '/'))
      meths.command('set all&')
      eq(('$XDG_DATA_HOME/nvim'
          .. ',$XDG_DATA_DIRS/nvim'
          .. ',$XDG_CONFIG_HOME/' .. data_dir .. '/site'
          .. ',$XDG_CONFIG_DIRS/nvim/site'
          .. ',' .. vimruntime
          .. ',' .. libdir
          .. ',$XDG_CONFIG_DIRS/nvim/site/after'
          .. ',$XDG_CONFIG_HOME/' .. data_dir .. '/site/after'
          .. ',$XDG_DATA_DIRS/nvim/after'
          .. ',$XDG_DATA_HOME/nvim/after'
      ):gsub('\\', '/'), (meths.get_option('runtimepath')):gsub('\\', '/'))
      eq(('.,$XDG_CONFIG_HOME/' .. data_dir .. '/backup'),
          meths.get_option('backupdir'):gsub('\\', '/'))
      eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/swap//'),
          meths.get_option('directory'):gsub('\\', '/'))
      eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/undo'),
          meths.get_option('undodir'):gsub('\\', '/'))
      eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/view'),
          meths.get_option('viewdir'):gsub('\\', '/'))
    end)
  end)

  describe('with commas', function()
    before_each(function()
      clear({env={
        XDG_CONFIG_HOME=', , ,',
        XDG_CONFIG_DIRS=',-,-,' .. env_sep .. '-,-,-',
        XDG_DATA_HOME=',=,=,',
        XDG_DATA_DIRS=',≡,≡,' .. env_sep .. '≡,≡,≡',
      }})
    end)

    it('are escaped properly', function()
      local vimruntime, libdir = vimruntime_and_libdir()
      local path_sep = iswin() and '\\' or '/'
      eq(('\\, \\, \\,' .. path_sep .. 'nvim'
          .. ',\\,-\\,-\\,' .. path_sep .. 'nvim'
          .. ',-\\,-\\,-' .. path_sep .. 'nvim'
          .. ',\\,=\\,=\\,' .. path_sep .. data_dir .. path_sep .. 'site'
          .. ',\\,≡\\,≡\\,' .. path_sep .. 'nvim'  .. path_sep .. 'site'
          .. ',≡\\,≡\\,≡' .. path_sep .. 'nvim' .. path_sep .. 'site'
          .. ',' .. vimruntime
          .. ',' .. libdir
          .. ',≡\\,≡\\,≡' .. path_sep .. 'nvim' .. path_sep .. 'site' .. path_sep .. 'after'
          .. ',\\,≡\\,≡\\,' .. path_sep .. 'nvim' .. path_sep .. 'site' .. path_sep .. 'after'
          .. ',\\,=\\,=\\,'  .. path_sep.. data_dir .. path_sep .. 'site' .. path_sep .. 'after'
          .. ',-\\,-\\,-' .. path_sep .. 'nvim' .. path_sep .. 'after'
          .. ',\\,-\\,-\\,' .. path_sep .. 'nvim' .. path_sep .. 'after'
          .. ',\\, \\, \\,' .. path_sep .. 'nvim' .. path_sep .. 'after'
      ), meths.get_option('runtimepath'))
      meths.command('set runtimepath&')
      meths.command('set backupdir&')
      meths.command('set directory&')
      meths.command('set undodir&')
      meths.command('set viewdir&')
      eq(('\\, \\, \\,' .. path_sep .. 'nvim'
          .. ',\\,-\\,-\\,' .. path_sep ..'nvim'
          .. ',-\\,-\\,-' .. path_sep ..'nvim'
          .. ',\\,=\\,=\\,' .. path_sep ..'' .. data_dir .. '' .. path_sep ..'site'
          .. ',\\,≡\\,≡\\,' .. path_sep ..'nvim' .. path_sep ..'site'
          .. ',≡\\,≡\\,≡' .. path_sep ..'nvim' .. path_sep ..'site'
          .. ',' .. vimruntime
          .. ',' .. libdir
          .. ',≡\\,≡\\,≡' .. path_sep ..'nvim' .. path_sep ..'site' .. path_sep ..'after'
          .. ',\\,≡\\,≡\\,' .. path_sep ..'nvim' .. path_sep ..'site' .. path_sep ..'after'
          .. ',\\,=\\,=\\,' .. path_sep ..'' .. data_dir .. '' .. path_sep ..'site' .. path_sep ..'after'
          .. ',-\\,-\\,-' .. path_sep ..'nvim' .. path_sep ..'after'
          .. ',\\,-\\,-\\,' .. path_sep ..'nvim' .. path_sep ..'after'
          .. ',\\, \\, \\,' .. path_sep ..'nvim' .. path_sep ..'after'
      ), meths.get_option('runtimepath'))
      eq('.,\\,=\\,=\\,' .. path_sep .. data_dir .. '' .. path_sep ..'backup',
          meths.get_option('backupdir'))
      eq('\\,=\\,=\\,' .. path_sep ..'' .. data_dir .. '' .. path_sep ..'swap' .. (path_sep):rep(2),
          meths.get_option('directory'))
      eq('\\,=\\,=\\,' .. path_sep ..'' .. data_dir .. '' .. path_sep ..'undo',
          meths.get_option('undodir'))
      eq('\\,=\\,=\\,' .. path_sep ..'' .. data_dir .. '' .. path_sep ..'view',
          meths.get_option('viewdir'))
    end)
  end)
end)


describe('stdpath()', function()
  -- Windows appends 'nvim-data' instead of just 'nvim' to prevent collisions
  -- due to XDG_CONFIG_HOME and XDG_DATA_HOME being the same.
  local datadir = iswin() and 'nvim-data' or 'nvim'
  local env_sep = iswin() and ';' or ':'

  it('acceptance', function()
    clear()  -- Do not explicitly set any env vars.

    eq('nvim', funcs.fnamemodify(funcs.stdpath('cache'), ':t'))
    eq('nvim', funcs.fnamemodify(funcs.stdpath('config'), ':t'))
    eq(datadir, funcs.fnamemodify(funcs.stdpath('data'), ':t'))
    eq('table', type(funcs.stdpath('config_dirs')))
    eq('table', type(funcs.stdpath('data_dirs')))
    -- Check for crash. #8393
    eq(2, eval('1+1'))
  end)

  context('returns a String', function()

    describe('with "config"' , function ()
      it('knows XDG_CONFIG_HOME', function()
        clear({env={
          XDG_CONFIG_HOME=alter_slashes('/home/docwhat/.config'),
        }})
        eq(alter_slashes('/home/docwhat/.config/nvim'), funcs.stdpath('config'))
      end)

      it('handles changes during runtime', function()
        clear({env={
          XDG_CONFIG_HOME=alter_slashes('/home/original'),
        }})
        eq(alter_slashes('/home/original/nvim'), funcs.stdpath('config'))
        command("let $XDG_CONFIG_HOME='"..alter_slashes('/home/new').."'")
        eq(alter_slashes('/home/new/nvim'), funcs.stdpath('config'))
      end)

      it("doesn't expand $VARIABLES", function()
        clear({env={
          XDG_CONFIG_HOME='$VARIABLES',
          VARIABLES='this-should-not-happen',
        }})
        eq(alter_slashes('$VARIABLES/nvim'), funcs.stdpath('config'))
      end)

      it("doesn't expand ~/", function()
        clear({env={
          XDG_CONFIG_HOME=alter_slashes('~/frobnitz'),
        }})
        eq(alter_slashes('~/frobnitz/nvim'), funcs.stdpath('config'))
      end)
    end)

    describe('with "data"' , function ()
      it('knows XDG_DATA_HOME', function()
        clear({env={
          XDG_DATA_HOME=alter_slashes('/home/docwhat/.local'),
        }})
        eq(alter_slashes('/home/docwhat/.local/'..datadir), funcs.stdpath('data'))
      end)

      it('handles changes during runtime', function()
        clear({env={
          XDG_DATA_HOME=alter_slashes('/home/original'),
        }})
        eq(alter_slashes('/home/original/'..datadir), funcs.stdpath('data'))
        command("let $XDG_DATA_HOME='"..alter_slashes('/home/new').."'")
        eq(alter_slashes('/home/new/'..datadir), funcs.stdpath('data'))
      end)

      it("doesn't expand $VARIABLES", function()
        clear({env={
          XDG_DATA_HOME='$VARIABLES',
          VARIABLES='this-should-not-happen',
        }})
        eq(alter_slashes('$VARIABLES/'..datadir), funcs.stdpath('data'))
      end)

      it("doesn't expand ~/", function()
        clear({env={
          XDG_DATA_HOME=alter_slashes('~/frobnitz'),
        }})
        eq(alter_slashes('~/frobnitz/'..datadir), funcs.stdpath('data'))
      end)
    end)

    describe('with "cache"' , function ()
      it('knows XDG_CACHE_HOME', function()
        clear({env={
          XDG_CACHE_HOME=alter_slashes('/home/docwhat/.cache'),
        }})
        eq(alter_slashes('/home/docwhat/.cache/nvim'), funcs.stdpath('cache'))
      end)

      it('handles changes during runtime', function()
        clear({env={
          XDG_CACHE_HOME=alter_slashes('/home/original'),
        }})
        eq(alter_slashes('/home/original/nvim'), funcs.stdpath('cache'))
        command("let $XDG_CACHE_HOME='"..alter_slashes('/home/new').."'")
        eq(alter_slashes('/home/new/nvim'), funcs.stdpath('cache'))
      end)

      it("doesn't expand $VARIABLES", function()
        clear({env={
          XDG_CACHE_HOME='$VARIABLES',
          VARIABLES='this-should-not-happen',
        }})
        eq(alter_slashes('$VARIABLES/nvim'), funcs.stdpath('cache'))
      end)

      it("doesn't expand ~/", function()
        clear({env={
          XDG_CACHE_HOME=alter_slashes('~/frobnitz'),
        }})
        eq(alter_slashes('~/frobnitz/nvim'), funcs.stdpath('cache'))
      end)
    end)
  end)

  context('returns a List', function()
    -- Some OS specific variables the system would have set.
    local function base_env()
      if iswin() then
        return {
          HOME='C:\\Users\\docwhat', -- technically, is not a usual PATH
          HOMEDRIVE='C:',
          HOMEPATH='\\Users\\docwhat',
          LOCALAPPDATA='C:\\Users\\docwhat\\AppData\\Local',
          TEMP='C:\\Users\\docwhat\\AppData\\Local\\Temp',
          TMPDIR='C:\\Users\\docwhat\\AppData\\Local\\Temp',
          TMP='C:\\Users\\docwhat\\AppData\\Local\\Temp',
        }
      else
        return {
          HOME='/home/docwhat',
          HOMEDRIVE='HOMEDRIVE-should-be-ignored',
          HOMEPATH='HOMEPATH-should-be-ignored',
          LOCALAPPDATA='LOCALAPPDATA-should-be-ignored',
          TEMP='TEMP-should-be-ignored',
          TMPDIR='TMPDIR-should-be-ignored',
          TMP='TMP-should-be-ignored',
        }
      end
    end

    local function set_paths_via_system(var_name, paths)
      local env = base_env()
      env[var_name] = table.concat(paths, env_sep)
      clear({env=env})
    end

    local function set_paths_at_runtime(var_name, paths)
      clear({env=base_env()})
      meths.set_var('env_val', table.concat(paths, env_sep))
      command(('let $%s=g:env_val'):format(var_name))
    end

    local function behaves_like_dir_list_env(msg, stdpath_arg, env_var_name, paths, expected_paths)
      describe(msg, function()
        it('set via system', function()
          set_paths_via_system(env_var_name, paths)
          eq(expected_paths, funcs.stdpath(stdpath_arg))
        end)

        it('set at runtime', function()
          set_paths_at_runtime(env_var_name, paths)
          eq(expected_paths, funcs.stdpath(stdpath_arg))
        end)
      end)
    end

    describe('with "config_dirs"' , function ()
      behaves_like_dir_list_env(
        'handles XDG_CONFIG_DIRS with one path',
        'config_dirs', 'XDG_CONFIG_DIRS',
        {
          alter_slashes('/home/docwhat/.config')
        },
        {
          alter_slashes('/home/docwhat/.config/nvim')
        })

      behaves_like_dir_list_env(
        'handles XDG_CONFIG_DIRS with two paths',
        'config_dirs', 'XDG_CONFIG_DIRS',
        {
          alter_slashes('/home/docwhat/.config'),
          alter_slashes('/etc/config')
        },
        {
          alter_slashes('/home/docwhat/.config/nvim'),
          alter_slashes('/etc/config/nvim')
        })

      behaves_like_dir_list_env(
        "doesn't expand $VAR and $IBLES",
        'config_dirs', 'XDG_CONFIG_DIRS',
        { '$HOME', '$TMP' },
        {
          alter_slashes('$HOME/nvim'),
          alter_slashes('$TMP/nvim')
        })


      behaves_like_dir_list_env(
        "doesn't expand ~/",
        'config_dirs', 'XDG_CONFIG_DIRS',
        {
          alter_slashes('~/.oldconfig'),
          alter_slashes('~/.olderconfig')
        },
        {
          alter_slashes('~/.oldconfig/nvim'),
          alter_slashes('~/.olderconfig/nvim')
        })
    end)

    describe('with "data_dirs"' , function ()
      behaves_like_dir_list_env(
        'knows XDG_DATA_DIRS with one path',
        'data_dirs', 'XDG_DATA_DIRS',
        {
          alter_slashes('/home/docwhat/.data')
        },
        {
          alter_slashes('/home/docwhat/.data/nvim')
        })

      behaves_like_dir_list_env(
        'knows XDG_DATA_DIRS with two paths',
        'data_dirs', 'XDG_DATA_DIRS',
        {
          alter_slashes('/home/docwhat/.data'),
          alter_slashes('/etc/local')
        },
        {
          alter_slashes('/home/docwhat/.data/nvim'),
          alter_slashes('/etc/local/nvim'),
        })

      behaves_like_dir_list_env(
        "doesn't expand $VAR and $IBLES",
        'data_dirs', 'XDG_DATA_DIRS',
        { '$HOME', '$TMP' },
        {
          alter_slashes('$HOME/nvim'),
          alter_slashes('$TMP/nvim')
        })

      behaves_like_dir_list_env(
        "doesn't expand ~/",
        'data_dirs', 'XDG_DATA_DIRS',
        {
          alter_slashes('~/.oldconfig'),
          alter_slashes('~/.olderconfig')
        },
        {
          alter_slashes('~/.oldconfig/nvim'),
          alter_slashes('~/.olderconfig/nvim'),
        })
    end)
  end)

  describe('errors', function()
    it('on unknown strings', function()
      eq('Vim(call):E6100: "capybara" is not a valid stdpath', exc_exec('call stdpath("capybara")'))
      eq('Vim(call):E6100: "" is not a valid stdpath', exc_exec('call stdpath("")'))
      eq('Vim(call):E6100: "23" is not a valid stdpath', exc_exec('call stdpath(23)'))
    end)

    it('on non-strings', function()
      eq('Vim(call):E731: using Dictionary as a String', exc_exec('call stdpath({"eris": 23})'))
      eq('Vim(call):E730: using List as a String', exc_exec('call stdpath([23])'))
    end)
  end)
end)
