diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8756d6a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: Tests + +on: + push: + branches-ignore: [master] + pull_request: + types: [opened, reopened] + +jobs: + appimage-ubuntu: + name: ubuntu + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: date +%F > todays-date + - name: Restore cache for today's nightly. + uses: actions/cache@v2 + with: + path: | + build + key: ${{ runner.os }}-appimage-${{ hashFiles('todays-date') }} + + - name: Prepare + run: | + sudo apt install fd-find + test -d build || { + mkdir -p build + wget https://github.com/neovim/neovim/releases/download/nightly/nvim.appimage + chmod +x nvim.appimage + mv nvim.appimage ./build/nvim + } + mkdir -p ~/.local/share/nvim/site/pack/vendor/start + git clone --depth 1 https://github.com/nvim-lua/plenary.nvim ~/.local/share/nvim/site/pack/vendor/start/plenary.nvim + git clone --depth 1 https://github.com/kyazdani42/nvim-web-devicons ~/.local/share/nvim/site/pack/vendor/start/nvim-web-devicons + ln -s $(pwd) ~/.local/share/nvim/site/pack/vendor/start + - name: Run tests + run: | + export PATH="${PWD}/build/:${PATH}" + make test diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..65812e4 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,22 @@ +name: Linting + +on: + push: + branches-ignore: [master] + pull_request: + types: [opened, reopened] + +jobs: + build: + name: Luacheck + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install linter + run: | + sudo apt-get update + sudo apt-get install luarocks + sudo luarocks install luacheck + - name: Lint + run: make lint diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..88c5c2f --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +.DEFAULT_GOAL = check + +lint: + @luacheck lua/lualine + @luacheck tests + +format: + @for file in `find -name '*.lua'`;do lua-format $$file -i; done; + +test: + @nvim --headless -u tests/minimal_init.lua -c "PlenaryBustedDirectory tests/ { minimal_init = './tests/minimal_init.lua' }" + +check: lint test diff --git a/tests/helpers.lua b/tests/helpers.lua new file mode 100644 index 0000000..e9f978b --- /dev/null +++ b/tests/helpers.lua @@ -0,0 +1,35 @@ +local luassert = require 'luassert' +local M = {} + +M.eq = luassert.are.same +M.neq = luassert.are_not.same +M.meths = setmetatable({}, { + __index = function(_, key) return vim.api['nvim_' .. key] end +}) + +-- Checks ouput of a component +M.assert_component = function(component, opts, result) + -- for testing global options + if component == nil then component = 'special.function_component' end + local comp = require('lualine.components.' .. component):new(opts) + M.eq(result, comp:draw(opts.hl)) +end + +-- sets defaults for component options +M.build_component_opts = function(opts) + if not opts then opts = {} end + if opts[1] == nil then opts[1] = function() return 'test' end end + if not opts.self then opts.self = {section = 'lualine_c'} end + if not opts.theme then opts.theme = 'gruvbox' end + if not opts.hl then opts.hl = '' end + if opts.icons_enabled == nil then opts.icons_enabled = true end + if not opts.component_separators then + opts.component_separators = {'', ''} + end + if not opts.section_separators then opts.section_separators = {'', ''} end + return opts +end + +M.P = function(t) print(vim.inspect(t)) end + +return M diff --git a/tests/minimal_init.lua b/tests/minimal_init.lua new file mode 100644 index 0000000..3a27414 --- /dev/null +++ b/tests/minimal_init.lua @@ -0,0 +1,5 @@ +-- load lualine and plenary +vim.api.nvim_exec([[ + set rtp+=. + set rtp+=../plenary.nvim +]], false) diff --git a/tests/spec/component_spec.lua b/tests/spec/component_spec.lua new file mode 100644 index 0000000..ac9b174 --- /dev/null +++ b/tests/spec/component_spec.lua @@ -0,0 +1,288 @@ +local helpers = require 'tests.helpers' + +local eq = helpers.eq +local neq = helpers.neq +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('lua.lualine.components.special.function_component'):new(opts) + -- correct for lualine_c + eq('', comp.options.separator) + local opts2 = build_component_opts({self = {section = 'lualine_y'}}) + local comp2 = + require('lua.lualine.components.special.function_component'):new(opts2) + -- correct for lualine_u + eq('', comp2.options.separator) + end) + + it('can provide unique identifier', function() + local opts1 = build_component_opts() + local comp1 = + require('lua.lualine.components.special.function_component'):new(opts1) + local opts2 = build_component_opts() + local comp2 = + require('lua.lualine.components.special.function_component'):new(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('lua.lualine.components.special.function_component'):new(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) + hl.create_component_highlight_group:revert() + local opts2 = build_component_opts({color = 'MyHl'}) + local comp2 = + require('lua.lualine.components.special.function_component'):new(opts2) + eq('MyHl', comp2.options.color_highlight_link) + end) + + it('can draw', function() + local opts = build_component_opts({ + component_separators = {'', ''}, + 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, 'test%#My_highlight#') + end) + + describe('Global options:', function() + it('upper', function() + local opts = build_component_opts({ + component_separators = {'', ''}, + padding = 0, + upper = true + }) + assert_component(nil, opts, 'TEST') + end) + + it('lower', function() + local opts = build_component_opts({ + function() return 'TeSt' end, + component_separators = {'', ''}, + padding = 0, + lower = true + }) + assert_component(nil, opts, 'test') + end) + + it('left_padding', function() + local opts = build_component_opts({ + component_separators = {'', ''}, + padding = 0, + left_padding = 5 + }) + assert_component(nil, opts, ' test') + end) + + it('right_padding', function() + local opts = build_component_opts({ + component_separators = {'', ''}, + padding = 0, + right_padding = 5 + }) + assert_component(nil, opts, 'test ') + end) + + it('padding', function() + local opts = build_component_opts({ + component_separators = {'', ''}, + padding = 5 + }) + assert_component(nil, opts, ' test ') + end) + + it('icon', function() + local opts = build_component_opts({ + component_separators = {'', ''}, + padding = 0, + icon = '0' + }) + assert_component(nil, opts, '0 test') + end) + + it('icons_enabled', function() + local opts = build_component_opts({ + component_separators = {'', ''}, + padding = 0, + icons_enabled = true, + icon = '0' + }) + assert_component(nil, opts, '0 test') + local opts2 = build_component_opts( + { + component_separators = {'', ''}, + padding = 0, + icons_enabled = false, + icon = '0' + }) + assert_component(nil, opts2, 'test') + end) + + it('separator', function() + local opts = build_component_opts({ + component_separators = {'', ''}, + padding = 0, + separator = '|' + }) + assert_component(nil, opts, 'test|') + end) + + it('format', function() + local opts = build_component_opts({ + component_separators = {'', ''}, + padding = 0, + format = function(data) + return data:sub(1, 1):upper() .. data:sub(2, #data) + end + }) + assert_component(nil, opts, 'Test') + end) + + it('condition', function() + local opts = build_component_opts({ + component_separators = {'', ''}, + padding = 0, + condition = function() return true end + }) + assert_component(nil, opts, 'test') + local opts2 = build_component_opts( + { + component_separators = {'', ''}, + padding = 0, + condition = function() return false end + }) + assert_component(nil, opts2, '') + end) + + it('color', function() + local opts = build_component_opts({ + component_separators = {'', ''}, + padding = 0, + color = 'MyHl' + }) + assert_component(nil, opts, '%#MyHl#test') + local opts2 = build_component_opts( + { + component_separators = {'', ''}, + 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('lua.lualine.components.special.function_component'):new(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) + end) +end) + +describe('Encoding component', function() + it('works', function() + local opts = build_component_opts({ + component_separators = {'', ''}, + padding = 0 + }) + assert_component('encoding', opts, '%{strlen(&fenc)?&fenc:&enc}') + end) +end) + +describe('Fileformat component', function() + it('works with icons', function() + local opts = build_component_opts({ + component_separators = {'', ''}, + 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 = {'', ''}, + padding = 0, + icons_enabled = false + }) + assert_component('fileformat', opts, vim.bo.fileformat) + 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 = {'', ''}, + 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 = {'', ''}, + padding = 0 + }) + assert_component('location', opts, '%3l:%-2c') + end) +end) + +describe('Progress component', function() + it('works', function() + local opts = build_component_opts({ + component_separators = {'', ''}, + padding = 0 + }) + assert_component('progress', opts, '%3P') + 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 = {'', ''}, + padding = 0 + }) + assert_component('mode', opts, 'NORMAL') + vim.api.nvim_get_mode:revert() + end) +end) diff --git a/tests/spec/utils_spec.lua b/tests/spec/utils_spec.lua new file mode 100644 index 0000000..a559bc2 --- /dev/null +++ b/tests/spec/utils_spec.lua @@ -0,0 +1,115 @@ +local helpers = require 'tests.helpers' + +local eq = helpers.eq +local meths = helpers.meths +local build_component_opts = helpers.build_component_opts + +describe('Utils', function() + local utils = require('lualine.utils.utils') + + it('can save and restore highlights', function() + local hl1 = {'hl1', '#122233', '#445566', 'italic', true} + utils.save_highlight(hl1[1], hl1) + -- highlight loaded in loaded_highlights table + eq(utils.loaded_highlights[hl1[1]], hl1) + -- highlight exists works properly + eq(utils.highlight_exists('hl1'), true) + eq(utils.highlight_exists('hl2'), false) + -- highlights can be restored + -- hl doesn't exist + assert.has_error(function() meths.get_hl_by_name('hl1', true) end, + 'Invalid highlight name: hl1') + utils.reload_highlights() + -- Now hl1 is created + eq(meths.get_hl_by_name('hl1', true), { + foreground = tonumber(hl1[2]:sub(2, #hl1[2]), 16), -- convert rgb -> int + background = tonumber(hl1[3]:sub(2, #hl1[3]), 16), -- convert rgb -> int + italic = true + }) + -- highlights can be cleared + utils.clear_highlights() + eq(utils.highlight_exists('hl1'), false) + -- highlight group has been cleared + eq(meths.get_hl_by_name('hl1', true), {[true] = 6}) + end) + + it('can retrive highlight groups', function() + local hl2 = {fg = '#aabbcc', bg = '#889977', reverse = true} + -- handles non existing hl groups + eq(utils.extract_highlight_colors('hl2'), nil) + -- create highlight + vim.cmd( + string.format('hi hl2 guifg=%s guibg=%s gui=reverse', hl2.fg, hl2.bg)) + -- Can retrive entire highlight table + eq(utils.extract_highlight_colors('hl2'), hl2) + -- Can retrive specific parts of highlight + eq(utils.extract_highlight_colors('hl2', 'fg'), hl2.fg) + -- clear hl2 + vim.cmd 'hi clear hl2' + end) + + it('can shrink list with holes', function() + local list_with_holes = { + '2', '4', '6', nil, '43', nil, '2', '', 'a', '', 'b', ' ' + } + local list_without_holes = {'2', '4', '6', '43', '2', 'a', 'b', ' '} + eq(utils.list_shrink(list_with_holes), list_without_holes) + end) +end) + +describe('Cterm genarator', function() + local cterm = require 'lualine.utils.cterm_colors' + + it('can convert rgb to cterm', function() + local colors = {['#112233'] = 235, ['#7928ae'] = 97, ['#017bdc'] = 68} + for rgb, ct in pairs(colors) do + eq(cterm.get_cterm_color(rgb), tostring(ct)) + end + end) +end) + +describe('Section genarator', function() + local sec = require 'lualine.utils.section' + it('can draw', function() + local opts = build_component_opts() + local section = { + require('lua.lualine.components.special.function_component'):new(opts), + require('lua.lualine.components.special.function_component'):new(opts) + } + eq('%#MyHl# test %#MyHl# test ', sec.draw_section(section, '%#MyHl#')) + end) + + it('can remove separators from component with custom colors', function() + local opts = build_component_opts() + local opts_colored = build_component_opts({color = 'MyColor'}) + local opts_colored2 = build_component_opts({color = {bg = '#223344'}}) + local opts_colored3 = build_component_opts({color = {fg = '#223344'}}) + require'lualine.highlight'.create_highlight_groups( + require 'lualine.themes.gruvbox') + local section = { + require('lua.lualine.components.special.function_component'):new(opts), + require('lua.lualine.components.special.function_component'):new( + opts_colored), + require('lua.lualine.components.special.function_component'):new(opts) + } + -- Removes separator on string color + eq('%#MyHl# test %#MyHl#%#MyColor# test %#MyHl# test ', + sec.draw_section(section, '%#MyHl#')) + section[2] = + require('lua.lualine.components.special.function_component'):new( + opts_colored2) + local highlight_name = + '%#lualine_c_' .. section[2].options.component_name .. '_normal#' + -- Removes separator on color with bg + eq('%#MyHl# test %#MyHl#' .. highlight_name .. ' test %#MyHl# test ', + sec.draw_section(section, '%#MyHl#')) + section[2] = + require('lua.lualine.components.special.function_component'):new( + opts_colored3) + local highlight_name2 = + '%#lualine_c_' .. section[2].options.component_name .. '_normal#' + -- Doesn't remove separator on color without bg + eq('%#MyHl# test %#MyHl#' .. highlight_name2 .. ' test %#MyHl# test ', + sec.draw_section(section, '%#MyHl#')) + end) +end)