From 0fabc05ceb606cd188024b267b773a384b93e97f Mon Sep 17 00:00:00 2001 From: Simon McLean Date: Fri, 6 Sep 2024 21:29:51 +0100 Subject: [PATCH] test framework beta ready --- tests/test_framework.lua | 21 ++- tests/ui_tests.lua | 293 ++++++++++++++++++++++++--------------- tests/utils.lua | 26 ++-- 3 files changed, 205 insertions(+), 135 deletions(-) diff --git a/tests/test_framework.lua b/tests/test_framework.lua index 3e07549..61418eb 100644 --- a/tests/test_framework.lua +++ b/tests/test_framework.lua @@ -2,12 +2,14 @@ local u = require 'tests.utils' local M = {} -local TIMEOUT_SECONDS = 10 +local TIMEOUT_SECONDS = 7 -- TODO: values (like has_run, result, is_timed_out) persist between test runs. Write a function to reset the tests -- This should run on pcall error, and also on successful completion -- Perhaps I can just call the constructor again? +---@alias TestBodyCallback fun(done: { assertions: function, cleanup?: function }) + ---@class Test ---@field name string ---@field is_skipped boolean @@ -16,11 +18,11 @@ local TIMEOUT_SECONDS = 10 ---@field has_run boolean ---@field result? ('passed' | 'failed' | 'skipped') ---@field fail_message? string ----@field test_body fun(callback: function) +---@field test_body fun(done: TestBodyCallback) local Test = {} ---@param name string ----@param test_body fun(done: function) +---@param test_body fun(done: TestBodyCallback) ---@return Test function Test.new(name, test_body) local instance = {} @@ -40,17 +42,24 @@ M.test = Test.new ---@param callback fun(passed: boolean, fail_message?: string) function Test:run(callback) - local success, err = pcall(self.test_body, function(run_assertions) + local success, err = pcall(self.test_body, function(test_callback) if self.has_run then - error("Attempted to invoke test completion more than once. Check that any async callbacks in the test are not firing multiple times.") + error 'Attempted to invoke test completion more than once. Check that any async callbacks in the test are not firing multiple times.' end self.has_run = true + if test_callback.cleanup then + local cleanup_successful, cleanup_err = pcall(test_callback.cleanup) + if not cleanup_successful then + error(cleanup_err) + end + end + if self.is_timed_out then callback(false, 'Timed out') else - local passed, fail_message = pcall(run_assertions) + local passed, fail_message = pcall(test_callback.assertions) if passed then callback(true, nil) else diff --git a/tests/ui_tests.lua b/tests/ui_tests.lua index 5079761..8db6fbd 100644 --- a/tests/ui_tests.lua +++ b/tests/ui_tests.lua @@ -16,18 +16,23 @@ end ---@param callback function local function close_triptych(callback) u.on_event('TriptychDidClose', callback) - u.press_key 'q' + u.press_keys 'q' end run_tests { - test('opens on Triptych command', function(done) + test('opens on Triptych command, with cursor on current file', function(done) + u.setup_triptych() vim.cmd 'Triptych' u.on_all_wins_updated(function() local is_open = vim.g.triptych_is_open + local current_line = vim.api.nvim_get_current_line() close_triptych(function() - done(function() - assert.same(is_open, true) - end) + done { + assertions = function() + assert.same(is_open, true) + assert.same(current_line, 'ui_tests.lua') + end, + } end) end) end), @@ -35,9 +40,11 @@ run_tests { test('closes on Triptych command', function(done) open_triptych(function() u.on_event('TriptychDidClose', function() - done(function() - assert.same(vim.g.triptych_is_open, false) - end) + done { + assertions = function() + assert.same(vim.g.triptych_is_open, false) + end, + } end) vim.cmd 'Triptych' end) @@ -61,10 +68,12 @@ run_tests { open_triptych(function() result = u.get_state() close_triptych(function() - done(function() - assert.same(expected_lines, result.lines) - assert.same(expected_winbars, result.winbars) - end) + done { + assertions = function() + assert.same(expected_lines, result.lines) + assert.same(expected_winbars, result.winbars) + end, + } end) end) end), @@ -83,16 +92,18 @@ run_tests { } open_triptych(function() + u.press_keys 'l' u.on_all_wins_updated(function() local result = u.get_state() close_triptych(function() - done(function() - assert.same(expected_lines, result.lines) - assert.same(expected_winbars, result.winbars) - end) + done { + assertions = function() + assert.same(expected_lines, result.lines) + assert.same(expected_winbars, result.winbars) + end, + } end) end) - u.press_key 'l' end) end), @@ -110,14 +121,16 @@ run_tests { } open_triptych(function() - u.press_key 'h' + u.press_keys 'h' u.on_all_wins_updated(function() local result = u.get_state() close_triptych(function() - done(function() - assert.same(expected_lines, result.lines) - assert.same(expected_winbars, result.winbars) - end) + done { + assertions = function() + assert.same(expected_lines, result.lines) + assert.same(expected_winbars, result.winbars) + end, + } end) end) end) @@ -129,14 +142,18 @@ run_tests { open_triptych(function() u.on_event('TriptychDidClose', function() - done(function() - assert.same(vim.g.triptych_is_open, false) - vim.api.nvim_set_current_buf(current_buf) - end) + done { + assertions = function() + assert.same(vim.g.triptych_is_open, false) + end, + cleanup = function() + vim.api.nvim_set_current_buf(current_buf) + end, + } end) - u.press_key 'j' + u.press_keys 'j' u.on_child_window_updated(function() - u.press_key 'l' + u.press_keys 'l' end) end) end), @@ -153,16 +170,17 @@ run_tests { u.on_child_window_updated(function() local state = u.get_state() close_triptych(function() - done(function() - assert.same(expected_file_preview, state.lines.child) - end) + done { + assertions = function() + assert.same(expected_file_preview, state.lines.child) + end, + } end) end) - u.press_key 'j' + u.press_keys 'j' end) end), - -- TODO: Include public events test('creates files and folders', function(done) local expected_lines = { primary = { @@ -177,27 +195,28 @@ run_tests { } open_triptych(function() - u.press_key 'a' - -- TODO: Can I just use press_key? - u.key_sequence { 'a_new_file.lua' } - u.press_key 'a' - u.key_sequence { 'a_new_dir/another_new_file.md' } + u.press_keys 'a' + u.press_keys 'a_new_file.lua' + u.press_keys 'a' + u.press_keys 'a_new_dir/another_new_file.md' u.on_wins_updated({ 'primary', 'child' }, function() local state = u.get_state() close_triptych(function() - -- cleanup - vim.fn.delete(u.join_path(opening_dir, 'a_new_dir'), 'rf') - vim.fn.delete(u.join_path(opening_dir, 'a_new_file.lua')) - done(function() - assert.same(expected_lines.primary, state.lines.primary) - assert.same(expected_lines.child, state.lines.child) - end) + done { + assertions = function() + assert.same(expected_lines.primary, state.lines.primary) + assert.same(expected_lines.child, state.lines.child) + end, + cleanup = function() + vim.fn.delete(u.join_path(opening_dir, 'a_new_dir'), 'rf') + vim.fn.delete(u.join_path(opening_dir, 'a_new_file.lua')) + end, + } end) end) end) end), - -- TODO: This is running twice test('publishes public events on file/folder creation', function(done) local expected_events = { ['TriptychWillCreateNode'] = { @@ -211,22 +230,24 @@ run_tests { } open_triptych(function() - u.press_key 'a' - -- TODO: Can I just use press_key? - u.key_sequence { 'a_new_file.lua' } - u.press_key 'a' - u.key_sequence { 'a_new_dir/another_new_file.md' } + u.press_keys 'a' + u.press_keys 'a_new_file.lua' + u.press_keys 'a' + u.press_keys 'a_new_dir/another_new_file.md' u.on_events({ { name = 'TriptychWillCreateNode', wait_for_n = 2 }, { name = 'TriptychDidCreateNode', wait_for_n = 2 }, }, function(events) close_triptych(function() - -- cleanup - vim.fn.delete(u.join_path(opening_dir, 'a_new_dir'), 'rf') - vim.fn.delete(u.join_path(opening_dir, 'a_new_file.lua')) - done(function() - assert.same(expected_events, events) - end) + done { + assertions = function() + assert.same(expected_events, events) + end, + cleanup = function() + vim.fn.delete(u.join_path(opening_dir, 'a_new_dir'), 'rf') + vim.fn.delete(u.join_path(opening_dir, 'a_new_file.lua')) + end, + } end) end) end) @@ -234,7 +255,6 @@ run_tests { -- Having trouble with this -- How to programatically input a selection in vim.ui.select - -- TODO: Include public events test('deletes files and folders', function(done) local expected_lines = { primary = { @@ -248,20 +268,22 @@ run_tests { open_triptych(function() -- Add things - u.press_key 'a' - u.key_sequence { 'a_new_file.lua' } - u.press_key 'a' - u.key_sequence { 'a_new_dir/another_new_file.md' } + u.press_keys 'a' + u.press_keys 'a_new_file.lua' + u.press_keys 'a' + u.press_keys 'a_new_dir/another_new_file.md' -- Then remove them u.on_wins_updated({ 'primary', 'child' }, function() local state = u.get_state() - u.key_sequence { 'd', '1' } + u.press_keys 'd1' u.on_wins_updated({ 'primary', 'child' }, function() close_triptych(function() - done(function() - assert.same(expected_lines.primary, state.lines.primary) - assert.same(expected_lines.child, state.lines.child) - end) + done { + assertions = function() + assert.same(expected_lines.primary, state.lines.primary) + assert.same(expected_lines.child, state.lines.child) + end, + } end) end) end) @@ -269,7 +291,6 @@ run_tests { end):skip(), -- TODO: Skipped this because there seems to be a bug with dir pasting! - -- TODO: Include public events test('copies file and folders', function(done) local expected_lines = { primary = { @@ -284,27 +305,30 @@ run_tests { }, } open_triptych(function() - u.press_key 'c' + u.press_keys 'c' u.on_primary_window_updated(function() - u.press_key 'p' + u.press_keys 'p' u.on_primary_window_updated(function() - u.press_key 'j' - u.press_key 'c' + u.press_keys 'j' + u.press_keys 'c' u.on_primary_window_updated(function() - u.press_key 'p' + u.press_keys 'p' u.on_primary_window_updated(function() -- Go back to the top, so we can the new dir we've pasted in the the child directory - u.press_key 'gg' + u.press_keys 'gg' u.on_child_window_updated(function() local state = u.get_state() close_triptych(function() - -- Cleanup - vim.fn.delete(u.join_path(opening_dir, 'level_3_file_1_copy1.md')) - vim.fn.delete(u.join_path(opening_dir, 'level_4/level_4', 'rf')) - done(function() - assert.same(expected_lines.child, state.lines.child) - assert.same(expected_lines.primary, state.lines.primary) - end) + done { + assertions = function() + assert.same(expected_lines.child, state.lines.child) + assert.same(expected_lines.primary, state.lines.primary) + end, + cleanup = function() + vim.fn.delete(u.join_path(opening_dir, 'level_3_file_1_copy1.md')) + vim.fn.delete(u.join_path(opening_dir, 'level_4/level_4', 'rf')) + end, + } end) end) end) @@ -315,14 +339,11 @@ run_tests { end):skip(), -- TODO: This once the dir pasting bug has been fixed - -- TODO: Include public events - -- test('moves files and folders', function (done) end) + -- test('moves files and folders', function (done) end):skip() -- TODO: This once the dir pasting bug has been fixed - -- TODO: Include public events - -- test('copies files and folders', function(done) end) + -- test('copies files and folders', function(done) end):skip() - -- TODO: Include public events test('renames files and folders', function(done) local expected_lines = { primary = { @@ -332,28 +353,74 @@ run_tests { } open_triptych(function() - u.press_key 'r' - u.key_sequence { 'renamed_dir' } + u.press_keys 'r' + u.press_keys 'renamed_dir' u.on_primary_window_updated(function() - u.press_key 'j' - u.press_key 'r' - u.key_sequence { 'renamed_file.lua' } + u.press_keys 'j' + u.press_keys 'r' + u.press_keys 'renamed_file.lua' u.on_primary_window_updated(function() local state = u.get_state() close_triptych(function() - -- cleanup - vim.fn.rename(u.join_path(opening_dir, 'renamed_dir'), u.join_path(opening_dir, 'level_4')) - vim.fn.rename(u.join_path(opening_dir, 'renamed_file.lua'), u.join_path(opening_dir, 'level_3_file_1.md')) - done(function() - assert.same(expected_lines.primary, state.lines.primary) - end) + done { + assertions = function() + assert.same(expected_lines.primary, state.lines.primary) + end, + cleanup = function() + vim.fn.rename(u.join_path(opening_dir, 'renamed_dir'), u.join_path(opening_dir, 'level_4')) + vim.fn.rename( + u.join_path(opening_dir, 'renamed_file.lua'), + u.join_path(opening_dir, 'level_3_file_1.md') + ) + end, + } end) end) end) end) end), - -- TODO: Why doesn't "done" have type signature? + test('publishes public events on renaming files/folders', function(done) + local expected_data = { + { from_path = u.join_path(opening_dir, 'level_4'), to_path = u.join_path(opening_dir, 'renamed_dir') }, + { + from_path = u.join_path(opening_dir, 'level_3_file_1.md'), + to_path = u.join_path(opening_dir, 'renamed_file.lua'), + }, + } + + local expected_events = { + ['TriptychWillMoveNode'] = expected_data, + ['TriptychDidMoveNode'] = expected_data, + } + + open_triptych(function() + u.press_keys 'r' + u.press_keys 'renamed_dir' + u.on_primary_window_updated(function() + u.press_keys 'j' + u.press_keys 'r' + u.press_keys 'renamed_file.lua' + end) + u.on_events({ + { name = 'TriptychWillMoveNode', wait_for_n = 2 }, + { name = 'TriptychDidMoveNode', wait_for_n = 2 }, + }, function(events) + close_triptych(function() + done { + assertions = function() + assert.same(expected_events, events) + end, + cleanup = function() + vim.fn.rename(u.join_path(opening_dir, 'renamed_dir'), u.join_path(opening_dir, 'level_4')) + vim.fn.rename(u.join_path(opening_dir, 'renamed_file.lua'), u.join_path(opening_dir, 'level_3_file_1.md')) + end, + } + end) + end) + end) + end), + test('toggles hidden files (dot and gitignored)', function(done) local expected_lines_without_hidden = { primary = { @@ -372,18 +439,20 @@ run_tests { open_triptych(function() local first_state = u.get_state() - u.press_key '.' + u.press_keys '.' u.on_primary_window_updated(function() local second_state = u.get_state() - u.press_key '.' + u.press_keys '.' u.on_primary_window_updated(function() local third_state = u.get_state() close_triptych(function() - done(function() - assert.same(expected_lines_without_hidden.primary, first_state.lines.primary) - assert.same(expected_lines_with_hidden.primary, second_state.lines.primary) - assert.same(expected_lines_without_hidden.primary, third_state.lines.primary) - end) + done { + assertions = function() + assert.same(expected_lines_without_hidden.primary, first_state.lines.primary) + assert.same(expected_lines_with_hidden.primary, second_state.lines.primary) + assert.same(expected_lines_without_hidden.primary, third_state.lines.primary) + end, + } end) end) end) @@ -399,19 +468,21 @@ run_tests { local winbar_after_second_jump open_triptych(function() - u.press_key '.' + u.press_keys '.' u.on_all_wins_updated(function() local state = u.get_state() winbar_after_first_jump = state.winbars.primary - u.press_key '.' + u.press_keys '.' u.on_all_wins_updated(function() local state_2 = u.get_state() winbar_after_second_jump = state_2.winbars.primary close_triptych(function() - done(function() - assert.same(expected_winbar_after_first_jump, winbar_after_first_jump) - assert.same(expected_winbar_after_second_jump, winbar_after_second_jump) - end) + done { + assertions = function() + assert.same(expected_winbar_after_first_jump, winbar_after_first_jump) + assert.same(expected_winbar_after_second_jump, winbar_after_second_jump) + end, + } end) end) end) diff --git a/tests/utils.lua b/tests/utils.lua index 9155bbd..8b720d5 100644 --- a/tests/utils.lua +++ b/tests/utils.lua @@ -124,9 +124,8 @@ function M.on_wins_updated(wins, callback) end, false) end -function M.open_triptych(opening_dir) - local triptych = require 'triptych.init' - triptych.setup { +function M.setup_triptych() + require('triptych.init').setup { debug = false, -- Set options for easier testing options = { @@ -138,7 +137,11 @@ function M.open_triptych(opening_dir) }, }, } - triptych.toggle_triptych(opening_dir) +end + +function M.open_triptych(opening_dir) + M.setup_triptych() + require('triptych.init').toggle_triptych(opening_dir) end function M.get_state() @@ -182,24 +185,11 @@ function M.get_state() end ---@param key string -function M.press_key(key) +function M.press_keys(key) local input_parsed = api.nvim_replace_termcodes(key, true, true, true) api.nvim_feedkeys(input_parsed, 'normal', false) end -function M.key_sequence(keys) - local function key_sequence(remaining_keys) - local k = table.remove(remaining_keys, #remaining_keys) - M.press_key(k) - if #remaining_keys > 0 then - vim.schedule(function() - M.key_sequence(remaining_keys) - end) - end - end - key_sequence(M.reverse_list(keys)) -end - function M.reverse_list(list) local reversed = {} for i = #list, 1, -1 do