diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3740fb7e923..322672267d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,3 +98,42 @@ jobs: - run: make help-check if: ${{ matrix.nvim_version == 'stable' }} + + functionaltest: + runs-on: ubuntu-latest + + concurrency: + group: ${{ github.workflow }}-${{ matrix.nvim_version }}-${{ matrix.luals_version }}-${{ github.head_ref || github.ref_name }} + cancel-in-progress: true + + strategy: + matrix: + nvim_version: [ stable, nightly ] + luals_version: [ 3.15.0 ] + + env: + VIMRUNTIME: /home/runner/nvim-${{ matrix.nvim_version }}/share/nvim/runtime + DIR_NVIM_SRC: /home/runner/src/neovim-${{ matrix.nvim_version }} + + steps: + - name: checkout + uses: actions/checkout@v7 + + - name: install lua-language-server ${{ matrix.luals_version }} + run: | + mkdir -p luals + curl -L "https://github.com/LuaLS/lua-language-server/releases/download/${{ matrix.luals_version }}/lua-language-server-${{ matrix.luals_version }}-linux-x64.tar.gz" | tar zx --directory luals + echo "luals/bin" >> "$GITHUB_PATH" + + - name: build Nvim from source + run: | + mkdir -p "${DIR_NVIM_SRC}" + curl -L "https://github.com/neovim/neovim/archive/refs/tags/${{ matrix.nvim_version }}.tar.gz" | tar zx --directory "${DIR_NVIM_SRC}/.." + cd "${DIR_NVIM_SRC}" + make + + - run: scripts/functionaltest.sh -a + + - run: scripts/luals-check.sh test + + - run: scripts/luals-check.sh codestyle-check test diff --git a/.luacheckrc b/.luacheckrc index 267d0ce54ce..db7aa55a141 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -4,12 +4,25 @@ local M = {} M.self = false M.ignore = { - "631", -- max_line_length + "631", -- max_line_length } --- Global objects defined by the C code M.globals = { + -- Global objects defined by the C code "vim", + -- Global neovim test functions + "it", + "describe", + "pending", + "setup", + "before_each", + "after_each", + "teardown", + "finally", + -- Global nvt functiontest fixtures + "NVT_FUNCTEST_CONTEXT", + "NVT_FUNCTEST_EVENTS", + "NVT_FUNCTEST_CLIPBOARD_LINES", } return M diff --git a/.luarc.json b/.luarc.json index 6a2d3d63eb2..6f3ba03d697 100644 --- a/.luarc.json +++ b/.luarc.json @@ -4,7 +4,9 @@ "workspace": { "library": [ "$VIMRUNTIME/lua/vim", - "${3rd}/luv/library" + "${3rd}/luv/library", + "$DIR_NVIM_SRC/test", + "/tmp/src/neovim-stable/test" ] }, "format": { @@ -21,7 +23,11 @@ }, "diagnostics": { "libraryFiles": "Disable", - "globals": [], + "globals": [ + "NVT_FUNCTEST_CONTEXT", + "NVT_FUNCTEST_EVENTS", + "NVT_FUNCTEST_CLIPBOARD_LINES" + ], "neededFileStatus": { "ambiguity-1": "Any", "assign-type-mismatch": "Any", diff --git a/Makefile b/Makefile index bd65580b90b..57d9e56e46c 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +# TODO functest move any checks from ci.yml into here, adding any missing e.g. codeformat + all: lint style check # @@ -14,15 +16,18 @@ check: luals # luacheck: luacheck --codes --quiet lua --exclude-files "**/_meta/**" + luacheck --codes --quiet test style-check: - scripts/luals-check.sh codestyle-check + scripts/luals-check.sh codestyle-check lua + scripts/luals-check.sh codestyle-check scripts style-doc: scripts/doc-comments.sh luals: - scripts/luals-check.sh + scripts/luals-check.sh lua + scripts/luals-check.sh scripts # # format @@ -47,6 +52,5 @@ help-update: help-check: help-update git diff --exit-code doc/nvim-tree-lua.txt - .PHONY: all lint style check luacheck style-check style-doc luals format-fix format-check help-update help-check diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index 6fd01f18f1b..f468e6866b2 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -1708,7 +1708,7 @@ Config: actions *nvim-tree-config-actions* • {use_system_clipboard}? (`boolean`, default: `true`) Use the system clipboard for copy/paste. Copied text will be stored in registers `+` (system), otherwise, - it will be stored in `1` and `"` + it will be stored in `1` *nvim_tree.config.actions.change_dir* vim |current-directory| behaviour diff --git a/lua/nvim-tree/_meta/config/actions.lua b/lua/nvim-tree/_meta/config/actions.lua index 012f4c364d9..5f39fbda3f1 100644 --- a/lua/nvim-tree/_meta/config/actions.lua +++ b/lua/nvim-tree/_meta/config/actions.lua @@ -5,7 +5,7 @@ error("Cannot require a meta file") ---@class nvim_tree.config.actions --- ----Use the system clipboard for copy/paste. Copied text will be stored in registers `+` (system), otherwise, it will be stored in `1` and `"` +---Use the system clipboard for copy/paste. Copied text will be stored in registers `+` (system), otherwise, it will be stored in `1` ---(default: `true`) ---@field use_system_clipboard? boolean --- diff --git a/lua/nvim-tree/appearance/init.lua b/lua/nvim-tree/appearance/init.lua index 9918ca4dd0b..f90bbe659ce 100644 --- a/lua/nvim-tree/appearance/init.lua +++ b/lua/nvim-tree/appearance/init.lua @@ -182,8 +182,7 @@ M.LEGACY_LINKS = { NvimTreeDiagnosticHintFolderHL = "NvimTreeLspDiagnosticsHintFolderText", } ----Create all highlight groups and links. Idempotent. -function M.highlight() +local function highlight_main() -- non-linked for _, g in ipairs(M.HIGHLIGHT_GROUPS) do if g.def then @@ -208,4 +207,57 @@ function M.highlight() end end +---Create all NvimTree* highlight groups with just a unique foreground colour +---NvimTreeCursorLine has just a background colour +---Add a CL variant for each group with the same background colour as NvimTreeCursorLine +---This is done here, as redefining groups in the test context is a performance issue, due to the number of :help ui-event-hl_attr_define events +local function highlight_functest() + -- arbitrary cursor line "unique" value: math.random(tonumber('0x707070'),tonumber('0x909090')) + local hex_bg = "#85a450" + + -- unique colour for each group, descending from #fefefe + local i, r, g, b = 1, 254, 254, 254 + local rgb_fg, hex_fg + + -- build the cursor line first, background only + local hgs = vim.tbl_filter(function(hg) + if hg.group == "NvimTreeCursorLine" then + vim.api.nvim_set_hl(0, "NvimTreeCursorLine", { bg = hex_bg }) + return false + else + return true + end + end, M.HIGHLIGHT_GROUPS) + + -- define the remainder with unique foregrounds + for _, hg in ipairs(hgs) do + -- next unique fg colour + rgb_fg = r * 256 * 256 + g * 256 + b + hex_fg = string.format("#%x", rgb_fg) + i = i + 1 + if (i % 256 == 0) then + b, g, r = 254, 254, r - 1 + elseif (i % 16 == 0) then + b, g = 254, g - 1 + else + b = b - 1 + end + + -- add the group's concrete definition with fg only + vim.api.nvim_set_hl(0, hg.group, { fg = hex_fg }) + + -- create an NvimTreeCursorLine variant + vim.api.nvim_set_hl(0, hg.group .. "CL", { fg = hex_fg, bg = hex_bg }) + end +end + +---Create all highlight groups and links. Idempotent. +function M.highlight() + if NVT_FUNCTEST_CONTEXT then ---@diagnostic disable-line: undefined-global + highlight_functest() + else + highlight_main() + end +end + return M diff --git a/lua/nvim-tree/core.lua b/lua/nvim-tree/core.lua index 8892dfd0dac..5ea6b688693 100644 --- a/lua/nvim-tree/core.lua +++ b/lua/nvim-tree/core.lua @@ -67,8 +67,7 @@ function M.get_nodes_starting_line() end function M.purge_all_state() - view.close_all_tabs() - view.abandon_all_windows() + view.purge_state() if TreeExplorer then git.purge_state() TreeExplorer:destroy() diff --git a/lua/nvim-tree/view.lua b/lua/nvim-tree/view.lua index 865a6eeeead..9b1dfea610e 100644 --- a/lua/nvim-tree/view.lua +++ b/lua/nvim-tree/view.lua @@ -354,6 +354,12 @@ function M.abandon_all_windows() end end +function M.purge_state() + M.close_all_tabs() + M.abandon_all_windows() + view_state.Active.cursors = {} +end + ---@param opts table|nil ---@return boolean function M.is_visible(opts) diff --git a/scripts/check-nvim-src.sh b/scripts/check-nvim-src.sh new file mode 100755 index 00000000000..09030902db1 --- /dev/null +++ b/scripts/check-nvim-src.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env sh + +# common script to check the availability of neovim source +# - NOP if $DIR_NVIM_SRC is defined +# - sets $DIR_NVIM_SRC to /tmp/src/neovim-stable if present +# - otherwise prompts with instructions +# script must be sourced to receive the exported $DIR_NVIM_SRC + +DIR_NVIM_SRC_DEF="/tmp/src/neovim-stable" + +if [ -z "${DIR_NVIM_SRC}" ] && [ -d "${DIR_NVIM_SRC_DEF}" ]; then + export DIR_NVIM_SRC="${DIR_NVIM_SRC_DEF}" +fi + +if [ ! -d "${DIR_NVIM_SRC}" ]; then + cat << EOM + +Nvim stable source is required to run ${0} + +Unavailable: ${DIR_NVIM_SRC_DEF} or \$DIR_NVIM_SRC=${DIR_NVIM_SRC} + +Please: + mkdir -p ${DIR_NVIM_SRC_DEF} + curl -L 'https://github.com/neovim/neovim/archive/refs/tags/stable.tar.gz' | tar zx --directory $(dirname "${DIR_NVIM_SRC_DEF}") + or use your own e.g. + export DIR_NVIM_SRC="\${HOME}/src/neovim" + +EOM +exit 1 +fi diff --git a/scripts/functionaltest.sh b/scripts/functionaltest.sh new file mode 100755 index 00000000000..95d69e29105 --- /dev/null +++ b/scripts/functionaltest.sh @@ -0,0 +1,170 @@ +#!/usr/bin/env sh + +# Nvim func test driver, see test/func/README.md +# exits with number of tests failed +# this will be eventually be shipped as part of the vim runtime https://github.com/neovim/neovim/issues/34592 + +set -e + +if [ ! -d "test/nvt/func" ]; then + echo "Must be run from nvim-tree root" 1>&2 + exit 1 +fi + +# define $DIR_NVIM_SRC +. scripts/check-nvim-src.sh + +# absolute path of nvim-tree source +export NVT_DIR_SRC="${PWD}" + +# TODO just hardcode this +# absolute path of the test instance cwd +export NVT_FUNC_DIR_CWD="/tmp/nvt_func" + +# optional argument of -l +export NVT_DIR_LIVE= + +# parent directory under which nvim-tree is linked under Nvim source +# uses src (not test or runtime) to prevent luals from finding it +# src can be found in build/Xtest_xdg, which contains only links to runtime, src and test +# pack/dist/opt is added underneath to satisfy :packadd convention +export NVT_FUNC_PACKPATH="${DIR_NVIM_SRC}/src/nvt" + +# passed to make functionaltest; underneath Nvim source +export TEST_FILE= + +# absolute link to nvim-tree source root under Nvim source +dir_src_linked="${NVT_FUNC_PACKPATH}/pack/dist/opt/nvim-tree.lua" + +# number of test failures +failures=0 + +# a, t or l +mode= + +# TODO clarify messages +usage() { + echo "Usage: ${0} [-a] [-h] [-t ] [-l []]" + echo + echo " OPTION:" + echo " -a Execute all tests" + echo " -t Executes single test file or all under directory, passed as TEST_FILE to Nvim make functionaltest" + echo " -l Live environment, optionally using a data directory" + echo " -h Show this help" +} + +# +# CLI args +# + +# TODO remove, this is too complex +# set $mode to $1, fail if another $mode already set +mode_set() { + if [ -n "${mode}" ] && [ "${mode}" != "${1}" ]; then + echo "specify only one of -a, -t or -l" >&2 + exit 1 + fi + mode="${1}" +} + +while getopts "ahlt:" o; do + case "${o}" in + a) + mode_set "${o}" + TEST_FILE="${dir_src_linked}/test/nvt/func" + ;; + h) + usage + exit 0 + ;; + l) + shift + mode_set "${o}" + NVT_DIR_LIVE="${1}" + ;; + t) + mode_set "${o}" + TEST_FILE="${dir_src_linked}/${OPTARG}" + ;; + *) + usage >&2 + exit 1 + ;; + esac +done +if [ -z "${mode}" ]; then + usage >&2 + exit 1 +fi + +# +# test harness +# + +# before all tests: links plugin and tests in their appropriate places under Nvim source root +setup() { + teardown + + # nvim-tree source link + mkdir -p "$(dirname "${dir_src_linked}")" + ln -sv "${PWD}" "${dir_src_linked}" +} + +# after all tests: remove plugin and test links from Nvim source root +teardown() { + # nvim-tree source link + rm -fv "${dir_src_linked}" + + # test/live instance cwd + rm -rf "${NVT_FUNC_DIR_CWD}" +} + +# TODO run with the compiled nvim version under test +live() { + nvim \ + --clean \ + --noplugin \ + -u "test/nvt/func/init_live.lua" \ + -i NONE \ + --cmd "set shortmess+=IS background=light noswapfile noautoindent startofline laststatus=1 undodir=. directory=. viewdir=. backupdir=. belloff= wildoptions-=pum joinspaces noshowcmd noruler nomore redrawdebug=invalid shada=!,'100,<50,s10,h statusline=%<%f\ %{%nvim_eval_statusline('%h%w%m%r',\ {'maxwidth':\ 30}).width\ >\ 0\ ?\ '%h%w%m%r\ '\ :\ ''%}%=%{%\ &showcmdloc\ ==\ 'statusline'\ ?\ '%-10.S\ '\ :\ ''\ %}%{%\ exists('b:keymap_name')\ ?\ '<'..b:keymap_name..'>\ '\ :\ ''\ %}%{%\ &ruler\ ?\ (\ &rulerformat\ ==\ ''\ ?\ '%-14.(%l,%c%V%)\ %P'\ :\ &rulerformat\ )\ :\ ''\ %}" \ + --cmd "comclear | mapclear | mapclear!" \ + --cmd "set packpath^=${NVT_FUNC_PACKPATH}" \ + --cmd "lua dofile('${DIR_NVIM_SRC}/runtime/colors/vim.lua')" \ + --cmd "unlet g:colors_name" \ + --cmd "packadd nvim-tree.lua" \ + || failures=1 +} + +run_tests() { + # cmake must be run from nvim source root + cd "${DIR_NVIM_SRC}" + + # export TEST_COLOURS=false + + # append nvim-tree lua/test to package.path, so that tests themselves may access nvim-tree source + export TEST_ARGS="--lpath=${dir_src_linked}/?.lua --lpath=${dir_src_linked}/lua/?.lua --lpath=${dir_src_linked}/lua/?/init.lua" + + # don't exit on failure, just note it + make functionaltest || failures=$((failures + 1)) +} + +# +# execute +# + +setup + +case "${mode}" in + l) + live + ;; + a|t) + run_tests + ;; + *) + ;; +esac + +teardown + +exit "${failures}" diff --git a/scripts/luals-check.sh b/scripts/luals-check.sh index a57b4546403..7ccd2b8b930 100755 --- a/scripts/luals-check.sh +++ b/scripts/luals-check.sh @@ -1,19 +1,49 @@ #!/usr/bin/env sh -# Performs a lua-language-server check on all lua files. +# Performs a lua-language-server check. # $VIMRUNTIME specifies neovim runtime path, defaults to "/usr/share/nvim/runtime" if unset. +# $DIR_NVIM_SRC will be checked and set for test files. # # Call with codestyle-check param to enable only codestyle-check # # lua-language-server is inconsisent about which parameters must be absolute paths therefore we pass every path as absolute -if [ $# -eq 1 ] && [ "${1}" != "codestyle-check" ] || [ $# -gt 1 ] ; then - echo "usage: ${0} [codestyle-check]" 1>&2 - exit 1 +# TODO change to an optional -c argument +usage() { + echo "usage: ${0} [codestyle-check] " 1>&2 +} + +if [ $# -eq 2 ]; then + if [ "${1}" != "codestyle-check" ]; then + usage + exit 1 + fi + STYLE=true + shift fi +case "${1}" in + lua|scripts|test) + TARGET="${1}" + ;; + *) + usage + exit 0 + ;; +esac + DIR_NVT="${PWD}" +if [ "${TARGET}" = "test" ]; then + # neovim and nvim-tree source needed for tests; check everything + . scripts/check-nvim-src.sh + export VIMRUNTIME="${DIR_NVIM_SRC}/runtime" + DIR_CHECK="${DIR_NVT}" +else + # otherwise just the source + DIR_CHECK="${DIR_NVT}/${TARGET}" +fi + if [ ! -f "${DIR_NVT}/scripts/luals-check.sh" ]; then echo "Must be run from nvim-tree root" 1>&2 exit 1 @@ -38,28 +68,21 @@ rm -rf "${DIR_OUT}" mkdir "${DIR_OUT}" # create the luarc.json for the requested check -case "${1}" in - "codestyle-check") - jq \ - '.diagnostics.neededFileStatus[] = "None" | .diagnostics.neededFileStatus."codestyle-check" = "Any"' \ - "${DIR_NVT}/.luarc.json" > "${LUARC}" - - ;; - *) - cp "${DIR_NVT}/.luarc.json" "${LUARC}" - ;; -esac +if [ -n "${STYLE}" ]; then + jq \ + '.diagnostics.neededFileStatus[] = "None" | .diagnostics.neededFileStatus."codestyle-check" = "Any"' \ + "${DIR_NVT}/.luarc.json" > "${LUARC}" +else + cp "${DIR_NVT}/.luarc.json" "${LUARC}" +fi -for SRC in lua scripts; do - DIR_SRC="${DIR_NVT}/${SRC}" - FILE_OUT="${DIR_OUT}/out.${SRC}.log" - echo "Checking ${SRC}/" +FILE_OUT="${DIR_OUT}/out.${TARGET}.log" +echo "Checking ${TARGET}: ${DIR_CHECK} -> ${FILE_OUT}" - lua-language-server --check="${DIR_SRC}" --configpath="${LUARC}" --checklevel=Information --logpath="${DIR_OUT}" --loglevel=error 2>&1 | tee "${FILE_OUT}" +lua-language-server --check="${DIR_CHECK}" --configpath="${LUARC}" --checklevel=Information --logpath="${DIR_OUT}" --loglevel=error 2>&1 | tee "${FILE_OUT}" - if ! grep --quiet "Diagnosis completed, no problems found" "${FILE_OUT}"; then - RC=1 - fi -done +if ! grep --quiet "Diagnosis completed, no problems found" "${FILE_OUT}"; then + RC=1 +fi exit "${RC}" diff --git a/scripts/vimdoc.sh b/scripts/vimdoc.sh index cb8f8e6b27a..4c8751a9cbc 100755 --- a/scripts/vimdoc.sh +++ b/scripts/vimdoc.sh @@ -15,33 +15,13 @@ if [ $# -ne 1 ] || [ "${1}" != "doc" ]; then exit 1 fi -DIR_NVIM_SRC_DEF="/tmp/src/neovim-stable" - if [ ! -d "lua/nvim-tree" ]; then echo "Must be run from nvim-tree root" 1>&2 exit 1 fi -if [ -z "${DIR_NVIM_SRC}" ] && [ -d "${DIR_NVIM_SRC_DEF}" ]; then - export DIR_NVIM_SRC="${DIR_NVIM_SRC_DEF}" -fi - -if [ ! -d "${DIR_NVIM_SRC}" ]; then - cat << EOM - -Nvim stable source is required to run ${0} - -Unavailable: ${DIR_NVIM_SRC_DEF} or \$DIR_NVIM_SRC=${DIR_NVIM_SRC} - -Please: - mkdir -p ${DIR_NVIM_SRC_DEF} - curl -L 'https://github.com/neovim/neovim/archive/refs/tags/stable.tar.gz' | tar zx --directory $(dirname "${DIR_NVIM_SRC_DEF}") - or use your own e.g. - export DIR_NVIM_SRC="\${HOME}/src/neovim" - -EOM -exit 1 -fi +# define $DIR_NVIM_SRC +. scripts/check-nvim-src.sh cleanup() { # remove source link diff --git a/test/nvt/func/README.md b/test/nvt/func/README.md new file mode 100644 index 00000000000..e429b06971a --- /dev/null +++ b/test/nvt/func/README.md @@ -0,0 +1,146 @@ +# Generating Test Data + +Execute `scripts/functionaltest.sh -l [ ]` + +This will open an interactive vim session that closely matches the test headless session, using the init: `test/func/init_live.lua`. Add any test specific config to `require("nvim-tree").setup` when needed. + +If a `data` directory is given, it will be recursively copied to cwd `/tmp/nvt_func/data` otherwise uses empty cwd `/tmp/nvt_func` + +Test's `data` directory will be used if present. + +- `u` to dump the entire screen to `/tmp/nvt_func/dump.txt` and open it +- `c` to dump with identical lines collapsed +- `r` to resize the screen to 80 x 24, to match tests +- `o` to execute `api.tree.open()` + +## Text Only: `attr_ids = {}` + +1. Perform the actions to be tested +2. Dump the screen `u` or `c>` to `/tmp/nvt_func/dump.txt` +3. Add the dump to `screen:expect` `grid`, with `attr_ids = {}` + +The dump will have `|` appended to each line, with a caret `^` at the cursor position. + +## Text And Highlight + +Additional work is required to add highlight groups. + +Write the test as per Text Only and execute; it will fail. + +Use `Snapshot: screen:expect([[` contents from test log as the `grid`, visually validating that it matches the screen dump. + +# Test Inputs + +## Synchronisation + +The Nvim functional test framework's inputs are handled asynchronously via the commonly used `test.functional.testnvim` `feed` (non-deferred). It will execute all the inputs _immediately_ without waiting for them to complete, most notably lua callbacks such as nvim-tree mappings as well as some visual mode operations. See `:help api-fast` + +Prefer `test.nvt.func.util` `feed_sync` to feed keys synchronously (deferred), blocking the test thread and instance until the instance's main loop is complete. + +# Test Assertions + +## Screen Grid + +`Screen:expect` `grid` is the main assertion mechanism, validating the contents of the entire screen, optionally including highlights and extmarks, referred to as `attr_ids`. + +Nvim tests almost always asserts highlights and extmarks however they can be difficult to prepare and read. nvim-tree usually asserts only text, asserting highlights only when necessary for the test e.g. copy, git status. + +Unique nvim-tree highlight groups `attr_ids` are defined during `test.nvt.func.fixtures` `create_session`. An addition variant for each group when in the cursor line suffixed with `CL` e.g. `NvimTreeFileIconCL` for file icons within the cursor line. + +`expect` will continually validate the contents of the screen after each update until it matches the expected. This allows for for asynchronous updates such as git/filesystem changes, however it will timeout and fail if the expected state is not reached after `timeout` ms, default 3500. + +### Text Only + +Set `attr_ids = {}` when calling `expect` to direct a text only assertion. + +See [Generating Test Data](TODO link) to dump the screen contents to a file. + +Contents are inserted directly into a text block `grid` e.g. + +```lua + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +   d2 │~ | + ^  f1 │~ | +  f2 │~ | +~ │~ |*17 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/f1 added to clipboard. | + ]], + }) +``` + +Note the 17 repeated lines suffixed with `*17` which is optional; the entire screen may be inserted. + +### Complete + +It is impractical to generate the `grid` manually, due to the number of `attr_ids` that must be specified, combined and turned on and off. + +- Create a passing `screen:expect` with text only +- Remove `attr_ids = {}` and re-run the test +- On failure, the test will print the expected `grid` to use e.g. + +```lua +Snapshot: +screen:expect([[ + {NvimTreeSignColumn: }{NvimTreeRootFolder:/tmp/nvt_func/data/..}{NvimTreeNormal: }{NvimTreeWinSeparator:│} | + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeFolderName:d1}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeFolderName:d2}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosedCL:^ }{NvimTreeFileIconCL:}{NvimTreeNormalCL: }{NvimTreeCopiedHLCL:f1}{NvimTreeNormalCL: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f2 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeEndOfBuffer:~ }{NvimTreeWinSeparator:│}{1:~ }|*17 + {NvimTreeStatusLine:NvimTree_1 [-] }{2:[No Name] }| + [NvimTree] /tmp/nvt_func/data/f1 added to clipboard. | +]]) +``` + +## Command Line + +`Screen:expect` `cmdline` may be used to assert command line state. `test.functional.ui.screen.Opts` `ext_cmdline` must be enabled, e.g. + +```lua +screen = nf.create_session({}, nil, nil, { ext_cmdline = true }) +--- +screen:expect({ + cmdline = { { prompt = "Create ", content = { { "/tmp/nvt_func/data/" } }, pos = 19, } }, +}) +``` + +## Messages + +Messages may be usually be asserted simply via `Screen:expect` `grid` + +In the case of complex or multiline messages, `test.functional.ui.screen.Opts` `ext_messages` may be used to capture messages for finer grained assertion. This will: + +- implicitly set `ext_cmdline` +- remove the command line from the screen `grid`; which you may need to adjust to match + +e.g. +```lua +screen = nf.create_session({}, nil, nil, { ext_messages = true, }) +--- +screen:expect({ + messages = { { content = { { "[NvimTree] trash.cmd 'foobarbaz' is not an executable.", "WarningMsg" } }, history = true, kind = "echomsg" }, } +}) +``` + +## nvim-tree Events + +Specific nvim-tree events may be subscribed to via `test.nvt.func.fixtures` `event_subscribe`, recorded as `nvt.functest.event[]` and the entire collection asserted in order via `test.nvt.func.asserts` `events_received` + +After assertion, the collected events are cleared. + +e.g. +```lua +local Event = require("nvim-tree._meta.api.events").Event +nf.event_subscribe(Event.FileRemoved) +nf.event_subscribe(Event.WillRemoveFile) +--- +na.events_received({ + { event_type = "WillRemoveFile", payload = { fname = "/tmp/nvt_func/data/d 1/d 1 f 1", }, }, + { event_type = "FileRemoved", payload = { fname = "/tmp/nvt_func/data/d 1/d 1 f 1", }, }, +}) +``` diff --git a/test/nvt/func/asserts.lua b/test/nvt/func/asserts.lua new file mode 100644 index 00000000000..db1eef936da --- /dev/null +++ b/test/nvt/func/asserts.lua @@ -0,0 +1,64 @@ +local n = require("test.functional.testnvim")() +local t = require("test.testutil") + +local M = {} + +---Assert path exists and is a directory +---@param path string absolute +function M.dir_exists(path) + local stat, err = vim.uv.fs_stat(path) + t.eq(nil, err, path) + t.eq("directory", stat and stat.type, path) +end + +---Assert path exists and is a file +---@param path string absolute +function M.file_exists(path) + local stat, err = vim.uv.fs_stat(path) + t.eq(nil, err, path) + t.eq("file", stat and stat.type, path) +end + +---Assert path does not exist +---@param path string absolute +function M.not_path_exists(path) + local stat = vim.uv.fs_stat(path) + t.eq(nil, stat, path) +end + +---Assert events received during a functest, recorded in session global NVT_FUNCTEST_EVENTS +---Must be subscribed via event_subscribe +---Clears events as per ext_messages +---@param events test.nvt.func.fixtures.Event[] +function M.events_received(events) + t.eq(events, n.exec_lua(function() + local ev = vim.deepcopy(NVT_FUNCTEST_EVENTS) + NVT_FUNCTEST_EVENTS = {} + return ev + end)) +end + +---buffer with name exists and is loaded +---@param name string buffer name +---@return boolean +local function buf_loaded(name) + return vim.tbl_contains(n.api.nvim_list_bufs(), + function(buf) + return n.api.nvim_buf_get_name(buf) == name and n.api.nvim_buf_is_loaded(buf) + end, { predicate = true } + ) +end + +---Assert buffer with name exists and is loaded +---@param name string buffer name +function M.buf_exists(name) + t.eq(true, buf_loaded(name), "buffer '" .. name .. "' should be present and loaded") +end + +---Assert buffer with name does not exist +---@param name string buffer name +function M.not_buf_exists(name) + t.eq(false, buf_loaded(name), "buffer '" .. name .. "' should not be present") +end + +return M diff --git a/test/nvt/func/fixtures.lua b/test/nvt/func/fixtures.lua new file mode 100644 index 00000000000..f2561bceb3f --- /dev/null +++ b/test/nvt/func/fixtures.lua @@ -0,0 +1,149 @@ +local n = require("test.functional.testnvim")() +local Screen = require("test.functional.ui.screen") +local nu = require("test.nvt.func.util") + + +---Globals used within test session context, noted in this test context only for type definition +---@type boolean +NVT_FUNCTEST_CONTEXT = true +---@type test.nvt.func.fixtures.Event[] +NVT_FUNCTEST_EVENTS = {} +---@type string[] +NVT_FUNCTEST_CLIPBOARD_LINES = {} + + +local M = {} + + +---@class (exact) test.nvt.func.fixtures.create_session.Opts +---@field data? string relative path of data directory to copy for test instance cwd +---@field config? nvim_tree.config passed to nvim-tree.setup +---@field width? integer default 80 +---@field height? integer default 24 +---@field screen_opts? test.functional.ui.screen.Opts + +---Create a new neovim functest session with +---- nvim-tree.lua package added +---- fresh test data directory created and cd'd into +---- nvim-tree setup called +---- nvim-tree HL attr_ids added +---- dummy clipboard setup +---@param opts? test.nvt.func.fixtures.create_session.Opts +---@return test.functional.ui.screen +function M.create_session(opts) + opts = opts or {} + + -- instantiate a new session (nvim instance) and its screen + n.clear({ + args = { + "--cmd", "set packpath^=" .. nu.env("NVT_FUNC_PACKPATH"), + "--cmd", "packadd nvim-tree.lua", + } + }) + local screen = Screen.new(opts.width or 80, opts.height or 24, opts.screen_opts) + + -- create the test instance cwd + local cwd = nu.setup_cwd(opts.data) + + -- add the nvim-tree package, setup globals and cd + n.exec_lua(function() + -- setup globals + NVT_FUNCTEST_CONTEXT = true + NVT_FUNCTEST_EVENTS = {} + NVT_FUNCTEST_CLIPBOARD_LINES = {} + + -- test intance dir + vim.api.nvim_set_current_dir(cwd) + + -- setup nvim-tree with requested config + require("nvim-tree").setup(opts.config) + end) + + -- TODO this is causing performance issues when running all as a single test execution, and they persist + -- create attr_ids for all nvim-tree test highlight groups + M.add_nvt_attr_ids(screen) + + -- instantiate the test clipboard + M.use_dummy_clipboard() + + return screen +end + +---@class (exact) test.nvt.func.fixtures.Event +---@field event_type string value of nvim_tree.api.events.Event +---@field payload table deep copy + +---Subscribe to an event, to be recorded in session global NVT_FUNCTEST_EVENTS nvt.functest.event[] +---Assert events via events_received +---@param event_type nvim_tree.api.events.Event [nvim_tree_events_kind] +function M.event_subscribe(event_type) + n.exec_lua(function() + local api = require("nvim-tree.api") + + api.events.subscribe(event_type, function(payload) + table.insert(NVT_FUNCTEST_EVENTS, { + event_type = event_type, + payload = vim.deepcopy(payload), + }) + end) + end) +end + +---Clear all recorded events in NVT_FUNCTEST_EVENTS +function M.events_clear() + n.exec_lua(function() + NVT_FUNCTEST_EVENTS = {} + end) +end + +---Setup a dummy clipboard named NVT_FUNCTEST_CLIPBOARD containing the global NVT_FUNCTEST_CLIPBOARD_LINES +function M.use_dummy_clipboard() + n.exec_lua(function() + vim.g.clipboard = { + name = "NVT_FUNCTEST_CLIPBOARD", + copy = { + ["+"] = function(lines) + NVT_FUNCTEST_CLIPBOARD_LINES = lines + end + }, + paste = { + ["+"] = function() + return NVT_FUNCTEST_CLIPBOARD_LINES + end + }, + } + end) +end + +---Set concrete attr_ids for all nvim-tree highlight groups +---Must be called after nvim-tree setup +---These groups are uniquely defined in the test context by appearance/init.lua highlight_functest +---@param screen test.functional.ui.screen +function M.add_nvt_attr_ids(screen) + local attr_ids = {} + + for name, hl in pairs(n.api.nvim_get_hl(0, { create = false, link = true, })) do + if name:match("^NvimTree.*") then + attr_ids[name] = { foreground = hl.fg, background = hl.bg } + end + end + + screen:add_extra_attr_ids(attr_ids) +end + +---Feed keys synchronously (deferred), blocking the test thread and instance until the instance's main loop is complete. +---This is needed as api-fast input will execute lua callbacks (such as nvim-tree mappings) then execute the next input without waiting for the callback to complete. +---Calls feedkeys() (non-deferred, api-fast) then poke_eventloop() for each input passed. +---See :help api-fast +---@param inputs string[] +function M.feed_sync(inputs) + for _, v in ipairs(inputs) do + -- api-fast, does not wait for callbacks or input to complete + n.feed(v) + + -- defer until complete + n.poke_eventloop() + end +end + +return M diff --git a/test/nvt/func/fs/clear_clipboard/clear_clipboard_spec.lua b/test/nvt/func/fs/clear_clipboard/clear_clipboard_spec.lua new file mode 100644 index 00000000000..ec5239167be --- /dev/null +++ b/test/nvt/func/fs/clear_clipboard/clear_clipboard_spec.lua @@ -0,0 +1,123 @@ +local t = require("test.testutil") +local n = require("test.functional.testnvim")() +local nf = require("test.nvt.func.fixtures") + +-- 0.12 global compatibility +---@diagnostic disable-next-line: inject-field, undefined-global +t.describe, t.it, t.before_each = t.describe or describe, t.it or it, t.before_each or before_each + + +--- @type test.functional.ui.screen +local screen + + +t.before_each(function() + screen = nf.create_session({ + data = "test/nvt/func/fs/clear_clipboard/data", + screen_opts = { ext_messages = true, }, + }) +end) + + +t.describe("fs.clear_clipboard", function() + t.it("empty", function() + nf.feed_sync({ + ":NvimTreeOpen", + }) + + n.exec_lua(function() + require("nvim-tree.api").fs.clear_clipboard() + end) + + screen:expect({ + messages = { { content = { { "[NvimTree] Clipboard has been emptied." } }, history = true, kind = "echomsg" }, } + }) + end) + + + t.it("many", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "", + "", + "c", + "", + "x", + "", + "c", + "", + "x", + }) + + screen:expect({ + messages = { + { content = { { "[NvimTree] /tmp/nvt_func/data/d1/d1f1 added to clipboard." } }, history = true, kind = "echomsg" }, + { content = { { "[NvimTree] /tmp/nvt_func/data/d1/d1f2 added to clipboard." } }, history = true, kind = "echomsg" }, + { content = { { "[NvimTree] /tmp/nvt_func/data/d2 added to clipboard." } }, history = true, kind = "echomsg" }, + { content = { { "[NvimTree] /tmp/nvt_func/data/d3 added to clipboard." } }, history = true, kind = "echomsg" }, + }, + grid = [[ +{NvimTreeSignColumn: }{NvimTreeRootFolder:/tmp/nvt_func/data/..}{NvimTreeNormal: }{NvimTreeWinSeparator:│} | +{NvimTreeSignColumn: }{NvimTreeFolderArrowOpen: }{NvimTreeOpenedFolderIcon:}{NvimTreeNormal: }{NvimTreeOpenedFolderName:d1}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeSignColumn: }{NvimTreeIndentMarker: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: }{NvimTreeCopiedHL:d1f1}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeSignColumn: }{NvimTreeIndentMarker: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: }{NvimTreeCutHL:d1f2}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeCopiedHL:d2}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeSignColumn: }{NvimTreeFolderArrowClosedCL:^ }{NvimTreeClosedFolderIconCL:}{NvimTreeNormalCL: }{NvimTreeCutHLCL:d3}{NvimTreeNormalCL: }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f1 }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f2 }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeEndOfBuffer:~ }{NvimTreeWinSeparator:│}{1:~ }|*15 +{NvimTreeStatusLine:NvimTree_1 [-] }{2:[No Name] }| +]], + }) + + n.exec_lua(function() + require("nvim-tree.api").fs.print_clipboard() + end) + + screen:expect({ + messages = { { + history = true, + kind = "echomsg", + content = { { [[[NvimTree] +Cut + * /tmp/nvt_func/data/d1/d1f2 + * /tmp/nvt_func/data/d3 +Copy + * /tmp/nvt_func/data/d1/d1f1 + * /tmp/nvt_func/data/d2 +]], } }, + } }, + }) + + n.exec_lua(function() + require("nvim-tree.api").fs.clear_clipboard() + end) + + screen:expect({ + messages = { { content = { { "[NvimTree] Clipboard has been emptied.", } }, history = true, kind = "echomsg", } }, + }) + + n.exec_lua(function() + require("nvim-tree.api").fs.print_clipboard() + end) + + screen:expect({ + messages = { { content = { { "[NvimTree]\n\n", } }, history = true, kind = "echomsg", } }, + grid = [[ +{NvimTreeSignColumn: }{NvimTreeRootFolder:/tmp/nvt_func/data/..}{NvimTreeNormal: }{NvimTreeWinSeparator:│} | +{NvimTreeSignColumn: }{NvimTreeFolderArrowOpen: }{NvimTreeOpenedFolderIcon:}{NvimTreeNormal: }{NvimTreeOpenedFolderName:d1}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeSignColumn: }{NvimTreeIndentMarker: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: d1f1 }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeSignColumn: }{NvimTreeIndentMarker: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: d1f2 }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeFolderName:d2}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeSignColumn: }{NvimTreeFolderArrowClosedCL:^ }{NvimTreeClosedFolderIconCL:}{NvimTreeNormalCL: }{NvimTreeFolderNameCL:d3}{NvimTreeNormalCL: }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f1 }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f2 }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeEndOfBuffer:~ }{NvimTreeWinSeparator:│}{1:~ }|*15 +{NvimTreeStatusLine:NvimTree_1 [-] }{2:[No Name] }| + ]], + }) + end) +end) + +-- vim:colorcolumn=80 diff --git a/test/nvt/func/fs/clear_clipboard/data/d1/d1f1 b/test/nvt/func/fs/clear_clipboard/data/d1/d1f1 new file mode 100644 index 00000000000..13d1560f267 --- /dev/null +++ b/test/nvt/func/fs/clear_clipboard/data/d1/d1f1 @@ -0,0 +1 @@ +d1f1 diff --git a/test/nvt/func/fs/clear_clipboard/data/d1/d1f2 b/test/nvt/func/fs/clear_clipboard/data/d1/d1f2 new file mode 100644 index 00000000000..6ec000bf6df --- /dev/null +++ b/test/nvt/func/fs/clear_clipboard/data/d1/d1f2 @@ -0,0 +1 @@ +d1f2 diff --git a/test/nvt/func/fs/clear_clipboard/data/d2/d2f1 b/test/nvt/func/fs/clear_clipboard/data/d2/d2f1 new file mode 100644 index 00000000000..f901b65feae --- /dev/null +++ b/test/nvt/func/fs/clear_clipboard/data/d2/d2f1 @@ -0,0 +1 @@ +d2f1 diff --git a/test/nvt/func/fs/clear_clipboard/data/d3/d3f1 b/test/nvt/func/fs/clear_clipboard/data/d3/d3f1 new file mode 100644 index 00000000000..832bad2faee --- /dev/null +++ b/test/nvt/func/fs/clear_clipboard/data/d3/d3f1 @@ -0,0 +1 @@ +d3f1 diff --git a/test/nvt/func/fs/clear_clipboard/data/f1 b/test/nvt/func/fs/clear_clipboard/data/f1 new file mode 100644 index 00000000000..8e1e71d5ce3 --- /dev/null +++ b/test/nvt/func/fs/clear_clipboard/data/f1 @@ -0,0 +1 @@ +f1 diff --git a/test/nvt/func/fs/clear_clipboard/data/f2 b/test/nvt/func/fs/clear_clipboard/data/f2 new file mode 100644 index 00000000000..9de77c18733 --- /dev/null +++ b/test/nvt/func/fs/clear_clipboard/data/f2 @@ -0,0 +1 @@ +f2 diff --git a/test/nvt/func/fs/copy/data/d1/d1f1 b/test/nvt/func/fs/copy/data/d1/d1f1 new file mode 100644 index 00000000000..13d1560f267 --- /dev/null +++ b/test/nvt/func/fs/copy/data/d1/d1f1 @@ -0,0 +1 @@ +d1f1 diff --git a/test/nvt/func/fs/copy/data/d1/d1f2 b/test/nvt/func/fs/copy/data/d1/d1f2 new file mode 100644 index 00000000000..6ec000bf6df --- /dev/null +++ b/test/nvt/func/fs/copy/data/d1/d1f2 @@ -0,0 +1 @@ +d1f2 diff --git a/test/nvt/func/fs/copy/data/d2/d2f1 b/test/nvt/func/fs/copy/data/d2/d2f1 new file mode 100644 index 00000000000..f901b65feae --- /dev/null +++ b/test/nvt/func/fs/copy/data/d2/d2f1 @@ -0,0 +1 @@ +d2f1 diff --git a/test/nvt/func/fs/copy/data/f1 b/test/nvt/func/fs/copy/data/f1 new file mode 100644 index 00000000000..8e1e71d5ce3 --- /dev/null +++ b/test/nvt/func/fs/copy/data/f1 @@ -0,0 +1 @@ +f1 diff --git a/test/nvt/func/fs/copy/data/f2 b/test/nvt/func/fs/copy/data/f2 new file mode 100644 index 00000000000..9de77c18733 --- /dev/null +++ b/test/nvt/func/fs/copy/data/f2 @@ -0,0 +1 @@ +f2 diff --git a/test/nvt/func/fs/copy/node_spec.lua b/test/nvt/func/fs/copy/node_spec.lua new file mode 100644 index 00000000000..8ddee701ca3 --- /dev/null +++ b/test/nvt/func/fs/copy/node_spec.lua @@ -0,0 +1,316 @@ +local t = require("test.testutil") +local n = require("test.functional.testnvim")() +local nf = require("test.nvt.func.fixtures") + +-- 0.12 global compatibility +---@diagnostic disable-next-line: inject-field, undefined-global +t.describe, t.it, t.before_each = t.describe or describe, t.it or it, t.before_each or before_each + +--- @type test.functional.ui.screen +local screen + + +t.before_each(function() + screen = nf.create_session({ data = "test/nvt/func/fs/copy/data", }) +end) + + +t.describe("fs.copy", function() + t.it("single file", function() + n.fn.setreg("1", "foo") + n.fn.setreg("+", "foo") + + nf.feed_sync({ + ":NvimTreeOpen", + "", + "c", + }) + + t.eq("foo", n.fn.getreg("1")) + t.eq("/tmp/nvt_func/data/f1", n.fn.getreg("+")) + + screen:expect({ + grid = [[ + {NvimTreeSignColumn: }{NvimTreeRootFolder:/tmp/nvt_func/data/..}{NvimTreeNormal: }{NvimTreeWinSeparator:│} | + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeFolderName:d1}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeFolderName:d2}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosedCL:^ }{NvimTreeFileIconCL:}{NvimTreeNormalCL: }{NvimTreeCopiedHLCL:f1}{NvimTreeNormalCL: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f2 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeEndOfBuffer:~ }{NvimTreeWinSeparator:│}{1:~ }|*17 + {NvimTreeStatusLine:NvimTree_1 [-] }{2:[No Name] }| + [NvimTree] /tmp/nvt_func/data/f1 added to clipboard. | + ]], + }) + end) + + + t.it("api single file", function() + n.fn.setreg("1", "foo") + n.fn.setreg("+", "foo") + + nf.feed_sync({ + ":NvimTreeOpen", + "", + }) + + n.exec_lua(function() + require("nvim-tree.api").fs.copy.node() + end) + + t.eq("foo", n.fn.getreg("1")) + t.eq("/tmp/nvt_func/data/f1", n.fn.getreg("+")) + + screen:expect({ + grid = [[ + {NvimTreeSignColumn: }{NvimTreeRootFolder:/tmp/nvt_func/data/..}{NvimTreeNormal: }{NvimTreeWinSeparator:│} | + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeFolderName:d1}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeFolderName:d2}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosedCL:^ }{NvimTreeFileIconCL:}{NvimTreeNormalCL: }{NvimTreeCopiedHLCL:f1}{NvimTreeNormalCL: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f2 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeEndOfBuffer:~ }{NvimTreeWinSeparator:│}{1:~ }|*17 + {NvimTreeStatusLine:NvimTree_1 [-] }{2:[No Name] }| + [NvimTree] /tmp/nvt_func/data/f1 added to clipboard. | + ]], + }) + end) + + t.it("single dir", function() + n.fn.setreg("+", "foo") + + nf.feed_sync({ + ":NvimTreeOpen", + "", + "c", + }) + + t.eq("/tmp/nvt_func/data/d2/", n.fn.getreg("+")) + + screen:expect({ + grid = [[ + {NvimTreeSignColumn: }{NvimTreeRootFolder:/tmp/nvt_func/data/..}{NvimTreeNormal: }{NvimTreeWinSeparator:│} | + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeFolderName:d1}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosedCL:^ }{NvimTreeClosedFolderIconCL:}{NvimTreeNormalCL: }{NvimTreeCopiedHLCL:d2}{NvimTreeNormalCL: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f1 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f2 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeEndOfBuffer:~ }{NvimTreeWinSeparator:│}{1:~ }|*17 + {NvimTreeStatusLine:NvimTree_1 [-] }{2:[No Name] }| + [NvimTree] /tmp/nvt_func/data/d2 added to clipboard. | + ]], + }) + end) + + + t.it("toggle one", function() + n.fn.setreg("+", "foo") + + nf.feed_sync({ + ":NvimTreeOpen", + "", + "c", + }) + + t.eq("/tmp/nvt_func/data/f1", n.fn.getreg("+")) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +   d2 │~ | + ^  f1 │~ | +  f2 │~ | +~ │~ |*17 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/f1 added to clipboard. | + ]], + }) + + nf.feed_sync({ + "c", + }) + + t.eq("", n.fn.getreg("+")) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +   d2 │~ | + ^  f1 │~ | +  f2 │~ | +~ │~ |*17 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/f1 removed from clipboard. | + ]], + }) + end) + + + t.it("toggle many", function() + n.fn.setreg("+", "foo") + + nf.feed_sync({ + ":NvimTreeOpen", + "", + "c", + }) + + t.eq("/tmp/nvt_func/data/d1/", n.fn.getreg("+")) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | + ^  d1 │~ | +   d2 │~ | +  f1 │~ | +  f2 │~ | +~ │~ |*17 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/d1 added to clipboard. | + ]], + }) + + nf.feed_sync({ + "", + "", + "c", + }) + + t.eq("/tmp/nvt_func/data/d2/", n.fn.getreg("+")) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | + ^  d2 │~ | +  f1 │~ | +  f2 │~ | +~ │~ |*17 +NvimTree_1 [-] [No Name] | +[NvimTree] 1 nodes added to clipboard. | + ]], + }) + end) + + + t.it("no system clipboard", function() + n.exec_lua(function() + require("nvim-tree").setup({ + actions = { + use_system_clipboard = false, + }, + }) + end) + + n.fn.setreg("1", "foo") + n.fn.setreg("+", "foo") + -- + nf.feed_sync({ + ":NvimTreeOpen", + "", + "c", + }) + + t.eq("/tmp/nvt_func/data/f1", n.fn.getreg("1")) + t.eq("foo", n.fn.getreg("+")) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +   d2 │~ | + ^  f1 │~ | +  f2 │~ | + ~ │~ |*17 + NvimTree_1 [-] [No Name] | + [NvimTree] /tmp/nvt_func/data/f1 added to clipboard. | + ]], + }) + end) + + + t.it("overrides cut", function() + n.fn.setreg("1", "foo") + n.fn.setreg("+", "foo") + + nf.feed_sync({ + ":NvimTreeOpen", + "", + "x", + }) + + t.eq("foo", n.fn.getreg("1")) + t.eq("/tmp/nvt_func/data/f1", n.fn.getreg("+")) + + screen:expect({ + grid = [[ + {NvimTreeSignColumn: }{NvimTreeRootFolder:/tmp/nvt_func/data/..}{NvimTreeNormal: }{NvimTreeWinSeparator:│} | + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeFolderName:d1}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeFolderName:d2}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosedCL:^ }{NvimTreeFileIconCL:}{NvimTreeNormalCL: }{NvimTreeCutHLCL:f1}{NvimTreeNormalCL: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f2 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeEndOfBuffer:~ }{NvimTreeWinSeparator:│}{1:~ }|*17 + {NvimTreeStatusLine:NvimTree_1 [-] }{2:[No Name] }| + [NvimTree] /tmp/nvt_func/data/f1 added to clipboard. | + ]], + }) + + nf.feed_sync({ + "c", + }) + + screen:expect({ + grid = [[ + {NvimTreeSignColumn: }{NvimTreeRootFolder:/tmp/nvt_func/data/..}{NvimTreeNormal: }{NvimTreeWinSeparator:│} | + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeFolderName:d1}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeFolderName:d2}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosedCL:^ }{NvimTreeFileIconCL:}{NvimTreeNormalCL: }{NvimTreeCopiedHLCL:f1}{NvimTreeNormalCL: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f2 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeEndOfBuffer:~ }{NvimTreeWinSeparator:│}{1:~ }|*17 + {NvimTreeStatusLine:NvimTree_1 [-] }{2:[No Name] }| + [NvimTree] /tmp/nvt_func/data/f1 added to clipboard. | + ]], + }) + end) + + + t.it("dir overrides file", function() + n.fn.setreg("+", "foo") + + nf.feed_sync({ + ":NvimTreeOpen", + "E", + "", + "", + "", + "c", + }) + + t.eq([[ +/tmp/nvt_func/data/d1/d1f2 +/tmp/nvt_func/data/d2/ +/tmp/nvt_func/data/f1]], n.fn.getreg("+")) + + screen:expect({ + grid = [[ + {NvimTreeSignColumn: }{NvimTreeRootFolder:/tmp/nvt_func/data/..}{NvimTreeNormal: }{NvimTreeWinSeparator:│} | + {NvimTreeSignColumn: }{NvimTreeFolderArrowOpen: }{NvimTreeOpenedFolderIcon:}{NvimTreeNormal: }{NvimTreeOpenedFolderName:d1}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeIndentMarker: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: d1f1 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeIndentMarker: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: }{NvimTreeCopiedHL:d1f2}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowOpen: }{NvimTreeOpenedFolderIcon:}{NvimTreeNormal: }{NvimTreeCopiedHL:d2}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeIndentMarker: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: d2f1 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosedCL:^ }{NvimTreeFileIconCL:}{NvimTreeNormalCL: }{NvimTreeCopiedHLCL:f1}{NvimTreeNormalCL: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f2 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeEndOfBuffer:~ }{NvimTreeWinSeparator:│}{1:~ }|*14 + {NvimTreeStatusLine:NvimTree_1 [-] }{2:[No Name] }| + [NvimTree] 3 nodes added to clipboard. | + ]], + }) + end) +end) + +-- vim:colorcolumn=80 diff --git a/test/nvt/func/fs/create/data/d1/d1f1 b/test/nvt/func/fs/create/data/d1/d1f1 new file mode 100644 index 00000000000..13d1560f267 --- /dev/null +++ b/test/nvt/func/fs/create/data/d1/d1f1 @@ -0,0 +1 @@ +d1f1 diff --git a/test/nvt/func/fs/create/data/f1 b/test/nvt/func/fs/create/data/f1 new file mode 100644 index 00000000000..8e1e71d5ce3 --- /dev/null +++ b/test/nvt/func/fs/create/data/f1 @@ -0,0 +1 @@ +f1 diff --git a/test/nvt/func/fs/create/dir_spec.lua b/test/nvt/func/fs/create/dir_spec.lua new file mode 100644 index 00000000000..a5eae7b5629 --- /dev/null +++ b/test/nvt/func/fs/create/dir_spec.lua @@ -0,0 +1,279 @@ +local t = require("test.testutil") +local n = require("test.functional.testnvim")() +local nf = require("test.nvt.func.fixtures") +local na = require("test.nvt.func.asserts") + +-- 0.12 global compatibility +---@diagnostic disable-next-line: inject-field, undefined-global +t.describe, t.it, t.before_each = t.describe or describe, t.it or it, t.before_each or before_each + +--- @type test.functional.ui.screen +local screen + + +t.before_each(function() + screen = nf.create_session({ data = "test/nvt/func/fs/create/data", }) + + local Event = require("nvim-tree._meta.api.events").Event + nf.event_subscribe(Event.FolderCreated) +end) + + +t.describe("fs.create dir", function() + t.it("single direct", function() + nf.feed_sync({ + ":NvimTreeOpen", + "a", + "direct/", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | + ^  direct │~ | +  f1 │~ | +~ │~ |*18 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/direct/ was properly created | + ]], + }) + + na.dir_exists("/tmp/nvt_func/data/direct") + + na.events_received({ + { event_type = "FolderCreated", payload = { folder_name = "/tmp/nvt_func/data/direct/", }, }, + }) + end) + + + t.it("single indirect", function() + nf.feed_sync({ + ":NvimTreeOpen", + "a", + "d1/indirect/", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | + ^   indirect │~ | +  d1f1 │~ | +  f1 │~ | +~ │~ |*17 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/d1/indirect/ was properly created | + ]], + }) + + na.dir_exists("/tmp/nvt_func/data/d1/indirect") + + na.events_received({ + { event_type = "FolderCreated", payload = { folder_name = "/tmp/nvt_func/data/d1/indirect/", }, }, + }) + end) + + + t.it("api single indirect", function() + nf.feed_sync({ + ":NvimTreeOpen", + }) + + -- schedule this as it blocks on vim.ui.input + n.exec_lua(function() + vim.schedule(require("nvim-tree.api").fs.create) + end) + + -- wait for the input + n.poke_eventloop() + + nf.feed_sync({ + "d1/indirect/", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | + ^   indirect │~ | +  d1f1 │~ | +  f1 │~ | +~ │~ |*17 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/d1/indirect/ was properly created | + ]], + }) + + na.dir_exists("/tmp/nvt_func/data/d1/indirect") + + na.events_received({ + { event_type = "FolderCreated", payload = { folder_name = "/tmp/nvt_func/data/d1/indirect/", }, }, + }) + end) + + + t.it("multiple direct", function() + nf.feed_sync({ + ":NvimTreeOpen", + "a", + "direct1/direct2/", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +   direct1 │~ | + ^   direct2 │~ | +  f1 │~ | +~ │~ |*17 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/direct1/direct2/ was properly created | + ]], + }) + + na.dir_exists("/tmp/nvt_func/data/direct1") + na.dir_exists("/tmp/nvt_func/data/direct1/direct2") + + na.events_received({ + -- BUG this should be "/tmp/nvt_func/data/direct1/" + { event_type = "FolderCreated", payload = { folder_name = "/tmp/nvt_func/data/direct1/direct2/", }, }, + { event_type = "FolderCreated", payload = { folder_name = "/tmp/nvt_func/data/direct1/direct2/", }, }, + }) + end) + + + t.it("multiple indirect", function() + nf.feed_sync({ + ":NvimTreeOpen", + "a", + "d1/indirect1/indirect2/", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +   indirect1 │~ | + ^   indirect2 │~ | +  d1f1 │~ | +  f1 │~ | +~ │~ |*16 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/d1/indirect1/indirect2/ was properly created | + ]], + }) + + na.dir_exists("/tmp/nvt_func/data/d1/indirect1") + na.dir_exists("/tmp/nvt_func/data/d1/indirect1/indirect2") + + na.events_received({ + -- BUG this should be "/tmp/nvt_func/data/d1/indirect1/" + { event_type = "FolderCreated", payload = { folder_name = "/tmp/nvt_func/data/d1/indirect1/indirect2/", }, }, + { event_type = "FolderCreated", payload = { folder_name = "/tmp/nvt_func/data/d1/indirect1/indirect2/", }, }, + }) + end) + + + t.it("fails when file exists", function() + nf.feed_sync({ + ":NvimTreeOpen", + "a", + "d1/", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + ^/tmp/nvt_func/data/.. │ | +   d1 │~ | +  f1 │~ | +~ │~ |*19 +NvimTree_1 [-] [No Name] | +[NvimTree] Cannot create: file already exists | + ]], + }) + + na.events_received({}) + end) + + + t.it("fails when file exists", function() + nf.feed_sync({ + ":NvimTreeOpen", + "a", + "d1/d1f1/", + }) + + -- BUG this fails but shows message "/tmp/nvt_func/data/d1/d1f1/ was properly created" and focuses the file + -- add similar test for multiple dirs when fixed + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | + ^  d1f1 │~ | +  f1 │~ | +~ │~ |*18 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/d1/d1f1/ was properly created | + ]], + }) + + na.events_received({}) + end) + + + t.it("fails on invalid path", function() + nf.feed_sync({ + ":NvimTreeOpen", + "a", + "", + "/foo/bar", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + ^/tmp/nvt_func/data/.. │ | +   d1 │~ | +  f1 │~ | +~ │~ |*19 +NvimTree_1 [-] [No Name] | +[NvimTree] Could not create folder /foo | + ]], + }) + + na.events_received({}) + end) + + + t.it("fails when dir exists", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "a", + "", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | + ^  d1 │~ | +  f1 │~ | +~ │~ |*19 +NvimTree_1 [-] [No Name] | + | + ]], + }) + end) +end) + +-- vim:colorcolumn=80 diff --git a/test/nvt/func/fs/create/file_spec.lua b/test/nvt/func/fs/create/file_spec.lua new file mode 100644 index 00000000000..2e4ec7eb958 --- /dev/null +++ b/test/nvt/func/fs/create/file_spec.lua @@ -0,0 +1,258 @@ +local t = require("test.testutil") +local n = require("test.functional.testnvim")() +local nf = require("test.nvt.func.fixtures") +local na = require("test.nvt.func.asserts") + +-- 0.12 global compatibility +---@diagnostic disable-next-line: inject-field, undefined-global +t.describe, t.it, t.before_each = t.describe or describe, t.it or it, t.before_each or before_each + +--- @type test.functional.ui.screen +local screen + + +t.before_each(function() + screen = nf.create_session({ data = "test/nvt/func/fs/create/data", }) + + local Event = require("nvim-tree._meta.api.events").Event + nf.event_subscribe(Event.FileCreated) + nf.event_subscribe(Event.WillCreateFile) + nf.event_subscribe(Event.FolderCreated) +end) + + +t.describe("fs.create file", function() + t.it("direct", function() + nf.feed_sync({ + ":NvimTreeOpen", + "a", + "direct", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | + ^  direct │~ | +  f1 │~ | +~ │~ |*18 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/direct was properly created | + ]], + }) + + na.file_exists("/tmp/nvt_func/data/direct") + + na.events_received({ + { event_type = "WillCreateFile", payload = { fname = "/tmp/nvt_func/data/direct", }, }, + { event_type = "FileCreated", payload = { fname = "/tmp/nvt_func/data/direct", }, }, + }) + end) + + + t.it("indirect", function() + nf.feed_sync({ + ":NvimTreeOpen", + "a", + "d1/indirect", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +  d1f1 │~ | + ^  indirect │~ | +  f1 │~ | +~ │~ |*17 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/d1/indirect was properly created | + ]], + }) + + na.file_exists("/tmp/nvt_func/data/d1/indirect") + + na.events_received({ + { event_type = "WillCreateFile", payload = { fname = "/tmp/nvt_func/data/d1/indirect", }, }, + { event_type = "FileCreated", payload = { fname = "/tmp/nvt_func/data/d1/indirect", }, }, + }) + end) + + + t.it("api indirect", function() + nf.feed_sync({ + ":NvimTreeOpen", + }) + + -- schedule this as it blocks on vim.ui.input + n.exec_lua(function() + vim.schedule(require("nvim-tree.api").fs.create) + end) + + -- wait for the input + n.poke_eventloop() + + nf.feed_sync({ + "d1/indirect", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +  d1f1 │~ | + ^  indirect │~ | +  f1 │~ | +~ │~ |*17 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/d1/indirect was properly created | + ]], + }) + + na.file_exists("/tmp/nvt_func/data/d1/indirect") + + na.events_received({ + { event_type = "WillCreateFile", payload = { fname = "/tmp/nvt_func/data/d1/indirect", }, }, + { event_type = "FileCreated", payload = { fname = "/tmp/nvt_func/data/d1/indirect", }, }, + }) + end) + + + t.it("fails when file exists", function() + nf.feed_sync({ + ":NvimTreeOpen", + "a", + "f1", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + ^/tmp/nvt_func/data/.. │ | +   d1 │~ | +  f1 │~ | +~ │~ |*19 +NvimTree_1 [-] [No Name] | +[NvimTree] Cannot create: file already exists | + ]], + }) + + na.file_exists("/tmp/nvt_func/data/f1") + + na.events_received({}) + end) + + + t.it("fails when dir exists", function() + nf.feed_sync({ + ":NvimTreeOpen", + "a", + "d1", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + ^/tmp/nvt_func/data/.. │ | +   d1 │~ | +  f1 │~ | +~ │~ |*19 +NvimTree_1 [-] [No Name] | +[NvimTree] Cannot create: file already exists | + ]], + }) + + na.dir_exists("/tmp/nvt_func/data/d1") + + na.events_received({}) + end) + + + t.it("indirect creates dirs", function() + nf.feed_sync({ + ":NvimTreeOpen", + "a", + "newdir/newfile", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +   newdir │~ | + ^  newfile │~ | +  f1 │~ | +~ │~ |*17 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/newdir/newfile was properly created | + ]], + }) + + na.dir_exists("/tmp/nvt_func/data/d1") + + na.events_received({ + -- BUG this should be "/tmp/nvt_func/data/newdir/" + { event_type = "FolderCreated", payload = { folder_name = "/tmp/nvt_func/data/newdir/newfile", }, }, + { event_type = "WillCreateFile", payload = { fname = "/tmp/nvt_func/data/newdir/newfile", }, }, + { event_type = "FileCreated", payload = { fname = "/tmp/nvt_func/data/newdir/newfile", }, }, + }) + end) + + + t.it("fails on invalid path", function() + nf.feed_sync({ + ":NvimTreeOpen", + "a", + "", + }) + + n.feed( + "/foo" + ) + + -- BUG this fails but shows message "/foo was properly created" + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +  f1 │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | + | +[NvimTree] Couldn't create file /foo | +[NvimTree] /foo was properly created | +Press ENTER or type command to continue^ | + ]], + }) + + n.feed( + "" + ) + + na.events_received({ + { event_type = "WillCreateFile", payload = { fname = "/foo", }, }, + }) + end) +end) + +-- vim:colorcolumn=80 diff --git a/test/nvt/func/fs/create/prompt_spec.lua b/test/nvt/func/fs/create/prompt_spec.lua new file mode 100644 index 00000000000..0f5bc48d000 --- /dev/null +++ b/test/nvt/func/fs/create/prompt_spec.lua @@ -0,0 +1,91 @@ +local t = require("test.testutil") +local n = require("test.functional.testnvim")() +local nf = require("test.nvt.func.fixtures") + +-- 0.12 global compatibility +---@diagnostic disable-next-line: inject-field, undefined-global +t.describe, t.it, t.before_each = t.describe or describe, t.it or it, t.before_each or before_each + +--- @type test.functional.ui.screen +local screen + + +t.before_each(function() + screen = nf.create_session({ + data = "test/nvt/func/fs/create/data", + screen_opts = { ext_cmdline = true }, + }) +end) + + +t.describe("fs.create prompt", function() + t.it("tree focused", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "a", + }) + + screen:expect({ + cmdline = { { prompt = "Create ", content = { { "/tmp/nvt_func/data/d1/" } }, pos = 22, } }, + }) + end) + + + t.it("api tree not focused", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "", + }) + + -- schedule this as it blocks on vim.ui.input + n.exec_lua(function() + vim.schedule(require("nvim-tree.api").fs.create) + end) + + -- wait for the input + n.poke_eventloop() + + screen:expect({ + cmdline = { { prompt = "Create ", content = { { "/tmp/nvt_func/data/d1/" } }, pos = 22, } }, + }) + end) + + + t.it("file focused", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "a", + }) + + screen:expect({ + cmdline = { { prompt = "Create ", content = { { "/tmp/nvt_func/data/" } }, pos = 19, } }, + }) + end) + + + t.it("cancelled", function() + nf.feed_sync({ + ":NvimTreeOpen", + "a", + "foo", + "", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + ^/tmp/nvt_func/data/.. │ | +   d1 │~ | +  f1 │~ | +~ │~ |*19 +NvimTree_1 [-] [No Name] | + | + ]], + }) + end) +end) + +-- vim:colorcolumn=80 diff --git a/test/nvt/func/fs/cut/data/d1/d1f1 b/test/nvt/func/fs/cut/data/d1/d1f1 new file mode 100644 index 00000000000..13d1560f267 --- /dev/null +++ b/test/nvt/func/fs/cut/data/d1/d1f1 @@ -0,0 +1 @@ +d1f1 diff --git a/test/nvt/func/fs/cut/data/d1/d1f2 b/test/nvt/func/fs/cut/data/d1/d1f2 new file mode 100644 index 00000000000..6ec000bf6df --- /dev/null +++ b/test/nvt/func/fs/cut/data/d1/d1f2 @@ -0,0 +1 @@ +d1f2 diff --git a/test/nvt/func/fs/cut/data/d2/d2f1 b/test/nvt/func/fs/cut/data/d2/d2f1 new file mode 100644 index 00000000000..f901b65feae --- /dev/null +++ b/test/nvt/func/fs/cut/data/d2/d2f1 @@ -0,0 +1 @@ +d2f1 diff --git a/test/nvt/func/fs/cut/data/f1 b/test/nvt/func/fs/cut/data/f1 new file mode 100644 index 00000000000..8e1e71d5ce3 --- /dev/null +++ b/test/nvt/func/fs/cut/data/f1 @@ -0,0 +1 @@ +f1 diff --git a/test/nvt/func/fs/cut/data/f2 b/test/nvt/func/fs/cut/data/f2 new file mode 100644 index 00000000000..9de77c18733 --- /dev/null +++ b/test/nvt/func/fs/cut/data/f2 @@ -0,0 +1 @@ +f2 diff --git a/test/nvt/func/fs/cut/node_spec.lua b/test/nvt/func/fs/cut/node_spec.lua new file mode 100644 index 00000000000..6bb8a737650 --- /dev/null +++ b/test/nvt/func/fs/cut/node_spec.lua @@ -0,0 +1,387 @@ +local t = require("test.testutil") +local n = require("test.functional.testnvim")() +local nf = require("test.nvt.func.fixtures") + +-- 0.12 global compatibility +---@diagnostic disable-next-line: inject-field, undefined-global +t.describe, t.it, t.before_each = t.describe or describe, t.it or it, t.before_each or before_each + + +-- BUG single operations report "added to clipboard", not "cut to clipboard" as per bulk operations + + +--- @type test.functional.ui.screen +local screen + + +t.before_each(function() + screen = nf.create_session({ data = "test/nvt/func/fs/cut/data", }) +end) + + +t.describe("fs.cut", function() + t.it("single file", function() + n.fn.setreg("1", "foo") + n.fn.setreg("+", "foo") + + nf.feed_sync({ + ":NvimTreeOpen", + "", + "x", + }) + + t.eq("foo", n.fn.getreg("1")) + t.eq("/tmp/nvt_func/data/f1", n.fn.getreg("+")) + + screen:expect({ + grid = [[ + {NvimTreeSignColumn: }{NvimTreeRootFolder:/tmp/nvt_func/data/..}{NvimTreeNormal: }{NvimTreeWinSeparator:│} | + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeFolderName:d1}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeFolderName:d2}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosedCL:^ }{NvimTreeFileIconCL:}{NvimTreeNormalCL: }{NvimTreeCutHLCL:f1}{NvimTreeNormalCL: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f2 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeEndOfBuffer:~ }{NvimTreeWinSeparator:│}{1:~ }|*17 + {NvimTreeStatusLine:NvimTree_1 [-] }{2:[No Name] }| + [NvimTree] /tmp/nvt_func/data/f1 added to clipboard. | + ]], + }) + end) + + + t.it("api single file", function() + n.fn.setreg("1", "foo") + n.fn.setreg("+", "foo") + + nf.feed_sync({ + ":NvimTreeOpen", + "", + }) + + n.exec_lua(function() + require("nvim-tree.api").fs.cut() + end) + + t.eq("foo", n.fn.getreg("1")) + t.eq("/tmp/nvt_func/data/f1", n.fn.getreg("+")) + + screen:expect({ + grid = [[ + {NvimTreeSignColumn: }{NvimTreeRootFolder:/tmp/nvt_func/data/..}{NvimTreeNormal: }{NvimTreeWinSeparator:│} | + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeFolderName:d1}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeFolderName:d2}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosedCL:^ }{NvimTreeFileIconCL:}{NvimTreeNormalCL: }{NvimTreeCutHLCL:f1}{NvimTreeNormalCL: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f2 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeEndOfBuffer:~ }{NvimTreeWinSeparator:│}{1:~ }|*17 + {NvimTreeStatusLine:NvimTree_1 [-] }{2:[No Name] }| + [NvimTree] /tmp/nvt_func/data/f1 added to clipboard. | + ]], + }) + end) + + + t.it("single dir", function() + n.fn.setreg("+", "foo") + + nf.feed_sync({ + ":NvimTreeOpen", + "", + "x", + }) + + t.eq("/tmp/nvt_func/data/d2/", n.fn.getreg("+")) + + screen:expect({ + grid = [[ + {NvimTreeSignColumn: }{NvimTreeRootFolder:/tmp/nvt_func/data/..}{NvimTreeNormal: }{NvimTreeWinSeparator:│} | + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeFolderName:d1}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosedCL:^ }{NvimTreeClosedFolderIconCL:}{NvimTreeNormalCL: }{NvimTreeCutHLCL:d2}{NvimTreeNormalCL: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f1 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f2 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeEndOfBuffer:~ }{NvimTreeWinSeparator:│}{1:~ }|*17 + {NvimTreeStatusLine:NvimTree_1 [-] }{2:[No Name] }| + [NvimTree] /tmp/nvt_func/data/d2 added to clipboard. | + ]], + }) + end) + + t.it("toggle one", function() + n.fn.setreg("+", "foo") + + nf.feed_sync({ + ":NvimTreeOpen", + "", + "x", + }) + + t.eq("/tmp/nvt_func/data/f1", n.fn.getreg("+")) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +   d2 │~ | + ^  f1 │~ | +  f2 │~ | +~ │~ |*17 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/f1 added to clipboard. | + ]], + }) + + nf.feed_sync({ + "x", + }) + + t.eq("", n.fn.getreg("+")) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +   d2 │~ | + ^  f1 │~ | +  f2 │~ | +~ │~ |*17 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/f1 removed from clipboard. | + ]], + }) + end) + + + t.it("toggle many", function() + n.fn.setreg("+", "foo") + + nf.feed_sync({ + ":NvimTreeOpen", + "", + "x", + }) + + t.eq("/tmp/nvt_func/data/d1/", n.fn.getreg("+")) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | + ^  d1 │~ | +   d2 │~ | +  f1 │~ | +  f2 │~ | +~ │~ |*17 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/d1 added to clipboard. | + ]], + }) + + nf.feed_sync({ + "", + "", + "x", + }) + + t.eq("/tmp/nvt_func/data/d2/", n.fn.getreg("+")) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | + ^  d2 │~ | +  f1 │~ | +  f2 │~ | +~ │~ |*17 +NvimTree_1 [-] [No Name] | +[NvimTree] 1 nodes cut to clipboard. | + ]], + }) + end) + + + t.it("no system clipboard", function() + n.exec_lua(function() + require("nvim-tree").setup({ + actions = { + use_system_clipboard = false, + }, + }) + end) + + n.fn.setreg("1", "foo") + n.fn.setreg("+", "foo") + -- + nf.feed_sync({ + ":NvimTreeOpen", + "", + "x", + }) + + t.eq("/tmp/nvt_func/data/f1", n.fn.getreg("1")) + t.eq("foo", n.fn.getreg("+")) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +   d2 │~ | + ^  f1 │~ | +  f2 │~ | + ~ │~ |*17 + NvimTree_1 [-] [No Name] | + [NvimTree] /tmp/nvt_func/data/f1 added to clipboard. | + ]], + }) + end) + + + t.it("single overrides copy", function() + n.fn.setreg("1", "foo") + n.fn.setreg("+", "foo") + + nf.feed_sync({ + ":NvimTreeOpen", + "", + "c", + }) + + t.eq("foo", n.fn.getreg("1")) + t.eq("/tmp/nvt_func/data/f1", n.fn.getreg("+")) + + screen:expect({ + grid = [[ + {NvimTreeSignColumn: }{NvimTreeRootFolder:/tmp/nvt_func/data/..}{NvimTreeNormal: }{NvimTreeWinSeparator:│} | + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeFolderName:d1}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeFolderName:d2}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosedCL:^ }{NvimTreeFileIconCL:}{NvimTreeNormalCL: }{NvimTreeCopiedHLCL:f1}{NvimTreeNormalCL: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f2 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeEndOfBuffer:~ }{NvimTreeWinSeparator:│}{1:~ }|*17 + {NvimTreeStatusLine:NvimTree_1 [-] }{2:[No Name] }| + [NvimTree] /tmp/nvt_func/data/f1 added to clipboard. | + ]], + }) + + nf.feed_sync({ + "x", + }) + + screen:expect({ + grid = [[ + {NvimTreeSignColumn: }{NvimTreeRootFolder:/tmp/nvt_func/data/..}{NvimTreeNormal: }{NvimTreeWinSeparator:│} | + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeFolderName:d1}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeFolderName:d2}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosedCL:^ }{NvimTreeFileIconCL:}{NvimTreeNormalCL: }{NvimTreeCutHLCL:f1}{NvimTreeNormalCL: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f2 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeEndOfBuffer:~ }{NvimTreeWinSeparator:│}{1:~ }|*17 + {NvimTreeStatusLine:NvimTree_1 [-] }{2:[No Name] }| + [NvimTree] /tmp/nvt_func/data/f1 added to clipboard. | + ]], + }) + end) + + + t.it("multiple overrides copy", function() + n.fn.setreg("1", "foo") + n.fn.setreg("+", "foo") + + nf.feed_sync({ + ":NvimTreeOpen", + "", + "c", + "", + "", + "", + "c", + "", + "c", + }) + + t.eq("foo", n.fn.getreg("1")) + t.eq([[ +/tmp/nvt_func/data/d1/ +/tmp/nvt_func/data/d2/d2f1 +/tmp/nvt_func/data/f1]], n.fn.getreg("+")) + + screen:expect({ + grid = [[ +{NvimTreeSignColumn: }{NvimTreeRootFolder:/tmp/nvt_func/data/..}{NvimTreeNormal: }{NvimTreeWinSeparator:│} | +{NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeCopiedHL:d1}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeSignColumn: }{NvimTreeFolderArrowOpen: }{NvimTreeOpenedFolderIcon:}{NvimTreeNormal: }{NvimTreeOpenedFolderName:d2}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeSignColumn: }{NvimTreeIndentMarker: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: }{NvimTreeCopiedHL:d2f1}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeSignColumn: }{NvimTreeFolderArrowClosedCL:^ }{NvimTreeFileIconCL:}{NvimTreeNormalCL: }{NvimTreeCopiedHLCL:f1}{NvimTreeNormalCL: }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f2 }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeEndOfBuffer:~ }{NvimTreeWinSeparator:│}{1:~ }|*16 +{NvimTreeStatusLine:NvimTree_1 [-] }{2:[No Name] }| +[NvimTree] /tmp/nvt_func/data/f1 added to clipboard. | + ]], + }) + + nf.feed_sync({ + "gg", + "", + "", + "", + "x", + }) + + screen:expect({ + grid = [[ +{NvimTreeSignColumn: }{NvimTreeRootFolder:/tmp/nvt_func/data/..}{NvimTreeNormal: }{NvimTreeWinSeparator:│} | +{NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeClosedFolderIcon:}{NvimTreeNormal: }{NvimTreeCutHL:d1}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeSignColumn: }{NvimTreeFolderArrowOpen: }{NvimTreeOpenedFolderIcon:}{NvimTreeNormal: }{NvimTreeCutHL:d2}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeSignColumn: }{NvimTreeIndentMarker: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: }{NvimTreeCopiedHL:d2f1}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: }{NvimTreeCutHL:f1}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeSignColumn: }{NvimTreeFolderArrowClosedCL:^ }{NvimTreeFileIconCL:}{NvimTreeNormalCL: }{NvimTreeCutHLCL:f2}{NvimTreeNormalCL: }{NvimTreeWinSeparator:│}{1:~ }| +{NvimTreeEndOfBuffer:~ }{NvimTreeWinSeparator:│}{1:~ }|*16 +{NvimTreeStatusLine:NvimTree_1 [-] }{2:[No Name] }| +[NvimTree] 4 nodes cut to clipboard. | + ]], + }) + + t.eq("foo", n.fn.getreg("1")) + t.eq([[ +/tmp/nvt_func/data/d1/ +/tmp/nvt_func/data/d2/ +/tmp/nvt_func/data/f1 +/tmp/nvt_func/data/f2]], n.fn.getreg("+")) + end) + + + t.it("dir overrides file", function() + n.fn.setreg("+", "foo") + + nf.feed_sync({ + ":NvimTreeOpen", + "E", + "", + "", + "", + "x", + }) + + t.eq([[ +/tmp/nvt_func/data/d1/d1f2 +/tmp/nvt_func/data/d2/ +/tmp/nvt_func/data/f1]], n.fn.getreg("+")) + + screen:expect({ + grid = [[ + {NvimTreeSignColumn: }{NvimTreeRootFolder:/tmp/nvt_func/data/..}{NvimTreeNormal: }{NvimTreeWinSeparator:│} | + {NvimTreeSignColumn: }{NvimTreeFolderArrowOpen: }{NvimTreeOpenedFolderIcon:}{NvimTreeNormal: }{NvimTreeOpenedFolderName:d1}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeIndentMarker: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: d1f1 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeIndentMarker: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: }{NvimTreeCutHL:d1f2}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowOpen: }{NvimTreeOpenedFolderIcon:}{NvimTreeNormal: }{NvimTreeCutHL:d2}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeIndentMarker: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: d2f1 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosedCL:^ }{NvimTreeFileIconCL:}{NvimTreeNormalCL: }{NvimTreeCutHLCL:f1}{NvimTreeNormalCL: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f2 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeEndOfBuffer:~ }{NvimTreeWinSeparator:│}{1:~ }|*14 + {NvimTreeStatusLine:NvimTree_1 [-] }{2:[No Name] }| + [NvimTree] 3 nodes cut to clipboard. | + ]], + }) + end) +end) + +-- vim:colorcolumn=80 diff --git a/test/nvt/func/fs/move/data/dst/f2 b/test/nvt/func/fs/move/data/dst/f2 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/nvt/func/fs/move/data/src/f1 b/test/nvt/func/fs/move/data/src/f1 new file mode 100644 index 00000000000..8e1e71d5ce3 --- /dev/null +++ b/test/nvt/func/fs/move/data/src/f1 @@ -0,0 +1 @@ +f1 diff --git a/test/nvt/func/fs/move/node_spec.lua b/test/nvt/func/fs/move/node_spec.lua new file mode 100644 index 00000000000..2a1a783627c --- /dev/null +++ b/test/nvt/func/fs/move/node_spec.lua @@ -0,0 +1,101 @@ +local t = require("test.testutil") +local n = require("test.functional.testnvim")() +local nf = require("test.nvt.func.fixtures") +local na = require("test.nvt.func.asserts") + +-- 0.12 global compatibility +---@diagnostic disable-next-line: inject-field, undefined-global +t.describe, t.it, t.before_each = t.describe or describe, t.it or it, t.before_each or before_each + + +--- @type test.functional.ui.screen +local screen + + +t.before_each(function() + screen = nf.create_session({ data = "test/nvt/func/fs/move/data", }) +end) + + +t.describe("fs.move", function() + t.it("nothing", function() + n.fn.setreg("+", "") + + nf.feed_sync({ + ":NvimTreeOpen", + "gp", + "E", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + ^/tmp/nvt_func/data/.. │ | +   dst │~ | +  f2 │~ | +   src │~ | +  f1 │~ | +~ │~ |*17 +NvimTree_1 [-] [No Name] | +:NvimTreeOpen | + ]], + }) + end) + + + t.it("inexistent", function() + n.fn.setreg("+", "/tmp/nvt_func/data/inexistent") + + nf.feed_sync({ + ":NvimTreeOpen", + "gp", + "E", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + ^/tmp/nvt_func/data/.. │ | +   dst │~ | +  f2 │~ | +   src │~ | +  f1 │~ | +~ │~ |*17 +NvimTree_1 [-] [No Name] | +:NvimTreeOpen | + ]], + }) + end) + + + t.it("present", function() + n.fn.setreg("+", "/tmp/nvt_func/data/src/f1") + + nf.feed_sync({ + ":NvimTreeOpen", + "E", + "", + "gp", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   dst │~ | + ^  f1 │~ | +  f2 │~ | +   src │~ | +~ │~ |*17 +NvimTree_1 [-] [No Name] | +:NvimTreeOpen | + ]], + }) + + na.dir_exists("/tmp/nvt_func/data/dst") + na.file_exists("/tmp/nvt_func/data/dst/f1") + na.not_path_exists("/tmp/nvt_func/data/src/f1") + end) +end) + +-- vim:colorcolumn=80 diff --git a/test/nvt/func/fs/paste/copied_spec.lua b/test/nvt/func/fs/paste/copied_spec.lua new file mode 100644 index 00000000000..907203ceec1 --- /dev/null +++ b/test/nvt/func/fs/paste/copied_spec.lua @@ -0,0 +1,727 @@ +local t = require("test.testutil") +local nf = require("test.nvt.func.fixtures") +local na = require("test.nvt.func.asserts") + +-- 0.12 global compatibility +---@diagnostic disable-next-line: inject-field, undefined-global +t.describe, t.it, t.before_each = t.describe or describe, t.it or it, t.before_each or before_each + +--- @type test.functional.ui.screen +local screen + + +-- common copy/paste codepaths are tested here except for API + + +t.before_each(function() + screen = nf.create_session({ + data = "test/nvt/func/fs/paste/data", + screen_opts = { ext_cmdline = true }, + }) +end) + + +t.describe("fs.paste copied", function() + t.it("single file", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "c", + "", + "p", + }) + + screen:expect({ + cmdline = {}, + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +   d2 │~ | +   d3 │~ | +  d3f1 │~ | + ^  f1 │~ | +  f1 │~ | +  f2 │~ | +~ │~ |*14 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/f1 added to clipboard. | + ]], + }) + + na.file_exists("/tmp/nvt_func/data/f1") + na.file_exists("/tmp/nvt_func/data/d3/f1") + end) + + + t.it("multiple files", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "", + "", + "c", + "", + "", + "p", + }) + + screen:expect({ + cmdline = {}, + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +   d2 │~ | +   d3 │~ | +  d3f1 │~ | +  f1 │~ | + ^  f2 │~ | +  f1 │~ | +  f2 │~ | +~ │~ |*13 +NvimTree_1 [-] [No Name] | +[NvimTree] 2 nodes added to clipboard. | + ]], + }) + + na.file_exists("/tmp/nvt_func/data/f1") + na.file_exists("/tmp/nvt_func/data/f2") + na.file_exists("/tmp/nvt_func/data/d3/f1") + na.file_exists("/tmp/nvt_func/data/d3/f2") + end) + + + t.it("single dir", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "c", + "", + "p", + "", + }) + + screen:expect({ + cmdline = {}, + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +   d2 │~ | + ^   d1 │~ | +  d1f1 │~ | +  d1f2 │~ | +   d3 │~ | +  f2 │~ | +   d3 │~ | +  f1 │~ | +  f2 │~ | +~ │~ |*11 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/d1 added to clipboard. | + ]], + }) + + na.dir_exists("/tmp/nvt_func/data/d2/d1") + na.file_exists("/tmp/nvt_func/data/d2/d1/d1f1") + na.file_exists("/tmp/nvt_func/data/d2/d1/d1f2") + + na.dir_exists("/tmp/nvt_func/data/d1") + end) + + + t.it("single dir conflict resolved", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "c", + "", + "p", + }) + + screen:expect({ + cmdline = { { prompt = "Rename to ", content = { { "/tmp/nvt_func/data/d1" } }, pos = 21, } }, + }) + + nf.feed_sync({ + "_copy", + "", + }) + + screen:expect({ + cmdline = { { abort = true } }, + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | + ^  d1_copy │~ | +  d1f1 │~ | +  d1f2 │~ | +   d2 │~ | +   d3 │~ | +  f1 │~ | +  f2 │~ | +~ │~ |*13 +NvimTree_1 [-] [No Name] | + | + ]], + }) + + na.dir_exists("/tmp/nvt_func/data/d1") + na.file_exists("/tmp/nvt_func/data/d1/d1f1") + na.file_exists("/tmp/nvt_func/data/d1/d1f2") + + na.dir_exists("/tmp/nvt_func/data/d1_copy") + na.file_exists("/tmp/nvt_func/data/d1_copy/d1f1") + na.file_exists("/tmp/nvt_func/data/d1_copy/d1f1") + end) + + + t.it("single dir conflict cancelled", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "c", + "", + "p", + }) + + screen:expect({ + cmdline = { { prompt = "Rename to ", content = { { "/tmp/nvt_func/data/d1" } }, pos = 21, } }, + }) + + nf.feed_sync({ + "", + }) + + screen:expect({ + cmdline = { { abort = true } }, + }) + + nf.feed_sync({ + "gg", + "E", + }) + + screen:expect({ + cmdline = {}, + attr_ids = {}, + grid = [[ + ^/tmp/nvt_func/data/.. │ | +   d1 │~ | +  d1f1 │~ | +  d1f2 │~ | +   d2 │~ | +   d3 │~ | +  d3f1 │~ | +  f2 │~ | +   d3 │~ | +  d3f1 │~ | +  f1 │~ | +  f2 │~ | +~ │~ |*10 +NvimTree_1 [-] [No Name] | + | + ]], + }) + end) + + + t.it("single dir conflict second conflict overwrite", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "c", + "", + "p", + }) + + screen:expect({ + cmdline = { { prompt = "Rename to ", content = { { "/tmp/nvt_func/data/d1" } }, pos = 21, } }, + }) + + nf.feed_sync({ + "2" + }) + + screen:expect({ + cmdline = { { prompt = "Overwrite /tmp/nvt_func/data/d2 ? R(ename)/y/n: ", content = { { "" } }, pos = 0, } }, + }) + + nf.feed_sync({ + "y", + }) + + screen:expect({ + cmdline = { { abort = true } }, + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +   d2 │~ | +   d3 │~ | +  f1 │~ | +  f2 │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | + | +[NvimTree] Could not copy /tmp/nvt_func/data/d1 - EEXIST: file already exists: /| +tmp/nvt_func/data/d2 | +Press ENTER or type command to continue^ | + ]], + }) + end) + + + t.it("single dir conflict second conflict cancel", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "c", + "", + "p", + }) + + screen:expect({ + cmdline = { { prompt = "Rename to ", content = { { "/tmp/nvt_func/data/d1" } }, pos = 21, } }, + }) + + nf.feed_sync({ + "2", + }) + + screen:expect({ + cmdline = { { prompt = "Overwrite /tmp/nvt_func/data/d2 ? R(ename)/y/n: ", content = { { "" } }, pos = 0, } }, + }) + + nf.feed_sync({ + "n", + "gg", + "E", + }) + + screen:expect({ + cmdline = { { abort = true } }, + grid = [[ + {NvimTreeSignColumn: }{NvimTreeRootFolderCL:^/tmp/nvt_func/data/..}{NvimTreeNormalCL: }{NvimTreeWinSeparator:│} | + {NvimTreeSignColumn: }{NvimTreeFolderArrowOpen: }{NvimTreeOpenedFolderIcon:}{NvimTreeNormal: }{NvimTreeOpenedFolderName:d1}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeIndentMarker: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: d1f1 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeIndentMarker: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: d1f2 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowOpen: }{NvimTreeOpenedFolderIcon:}{NvimTreeNormal: }{NvimTreeOpenedFolderName:d2}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeIndentMarker: }{NvimTreeFolderArrowOpen: }{NvimTreeOpenedFolderIcon:}{NvimTreeNormal: }{NvimTreeOpenedFolderName:d3}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeIndentMarker: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: d3f1 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeIndentMarker: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f2 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowOpen: }{NvimTreeOpenedFolderIcon:}{NvimTreeNormal: }{NvimTreeOpenedFolderName:d3}{NvimTreeNormal: }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeIndentMarker: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: d3f1 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f1 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeSignColumn: }{NvimTreeFolderArrowClosed: }{NvimTreeFileIcon:}{NvimTreeNormal: f2 }{NvimTreeWinSeparator:│}{1:~ }| + {NvimTreeEndOfBuffer:~ }{NvimTreeWinSeparator:│}{1:~ }|*10 + {NvimTreeStatusLine:NvimTree_1 [-] }{2:[No Name] }| + | + ]], + }) + end) + + + t.it("single dir conflict second conflict resolved", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "c", + "", + "p", + }) + + screen:expect({ + cmdline = { { prompt = "Rename to ", content = { { "/tmp/nvt_func/data/d1" } }, pos = 21, } }, + }) + + nf.feed_sync({ + "2", + }) + + screen:expect({ + cmdline = { { prompt = "Overwrite /tmp/nvt_func/data/d2 ? R(ename)/y/n: ", content = { { "" } }, pos = 0, } }, + }) + + nf.feed_sync({ + "R", + }) + + screen:expect({ + cmdline = { { prompt = "Rename to ", content = { { "/tmp/nvt_func/data/d2" } }, pos = 21, } }, + }) + + nf.feed_sync({ + "1_copy", + "", + }) + + screen:expect({ + cmdline = { { abort = true } }, + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | + ^  d1_copy │~ | +  d1f1 │~ | +  d1f2 │~ | +   d2 │~ | +   d3 │~ | +  f1 │~ | +  f2 │~ | +~ │~ |*13 +NvimTree_1 [-] [No Name] | + | + ]], + }) + end) + + + t.it("multiple", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "c", + "", + "c", + "", + "p", + "", + "", + }) + + screen:expect({ + cmdline = {}, + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +   d2 │~ | + ^   d1 │~ | +  d1f1 │~ | +  d1f2 │~ | +   d3 │~ | +  f1 │~ | +  f2 │~ | +   d3 │~ | +  f1 │~ | +  f2 │~ | +~ │~ |*10 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/f1 added to clipboard. | + ]], + }) + + na.file_exists("/tmp/nvt_func/data/d2/f1") + na.dir_exists("/tmp/nvt_func/data/d2/d3") + na.file_exists("/tmp/nvt_func/data/d2/d3/d3f1") + end) + + + t.it("multiple conflict no rename", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "", + "G", + "c", + "", + "c", + "p", + }) + + screen:expect({ + cmdline = { { prompt = "2 file(s) already exist. R(ename suffix)/y/n: ", content = { { "" } }, pos = 0, } }, + }) + + nf.feed_sync({ + "n", + }) + + screen:expect({ + cmdline = { { abort = true } }, + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +   d2 │~ | +   d1 │~ | +   d3 │~ | + ^  f1 │~ | +  f2 │~ | +   d3 │~ | +  f1 │~ | +  f2 │~ | +~ │~ |*12 +NvimTree_1 [-] [No Name] | + | + ]], + }) + + na.file_exists("/tmp/nvt_func/data/d2/f1") + na.file_exists("/tmp/nvt_func/data/d2/f2") + na.dir_exists("/tmp/nvt_func/data/d2/d3") + na.file_exists("/tmp/nvt_func/data/d2/d3/d3f1") + end) + + + t.it("multiple conflict rename suffix", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "", + "G", + "c", + "", + "c", + "p", + }) + + screen:expect({ + cmdline = { { prompt = "2 file(s) already exist. R(ename suffix)/y/n: ", content = { { "" } }, pos = 0, } }, + }) + + nf.feed_sync({ + "r", + }) + + screen:expect({ + cmdline = { { prompt = "Suffix: ", content = { { "" } }, pos = 0, } }, + }) + + nf.feed_sync({ + ".renamed", + }) + + screen:expect({ + cmdline = { { abort = true } }, + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +   d2 │~ | +   d1 │~ | +   d3 │~ | +   d3.renamed │~ | +  f1 │~ | +  f2 │~ | + ^  f2.renamed │~ | +   d3 │~ | +  f1 │~ | +  f2 │~ | +~ │~ |*10 +NvimTree_1 [-] [No Name] | + | + ]], + }) + + na.dir_exists("/tmp/nvt_func/data/d2/d1") + na.dir_exists("/tmp/nvt_func/data/d2/d3") + na.file_exists("/tmp/nvt_func/data/d2/f1") + na.file_exists("/tmp/nvt_func/data/d2/f2") + + na.file_exists("/tmp/nvt_func/data/d2/f2.renamed") + na.dir_exists("/tmp/nvt_func/data/d2/d3.renamed") + na.file_exists("/tmp/nvt_func/data/d2/d3.renamed/d3f1") + end) + + + t.it("multiple conflict rename cancel", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "", + "G", + "c", + "", + "c", + "p", + }) + + screen:expect({ + cmdline = { { prompt = "2 file(s) already exist. R(ename suffix)/y/n: ", content = { { "" } }, pos = 0, } }, + }) + + nf.feed_sync({ + "", + "", + }) + + screen:expect({ + cmdline = { { abort = true } }, + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +   d2 │~ | +   d1 │~ | +   d3 │~ | + ^  f1 │~ | +  f2 │~ | +   d3 │~ | +  f1 │~ | +  f2 │~ | +~ │~ |*12 +NvimTree_1 [-] [No Name] | + | + ]], + }) + + na.dir_exists("/tmp/nvt_func/data/d2/d1") + na.dir_exists("/tmp/nvt_func/data/d2/d3") + na.file_exists("/tmp/nvt_func/data/d2/d3/d3f1") + na.file_exists("/tmp/nvt_func/data/d2/f1") + na.file_exists("/tmp/nvt_func/data/d2/f2") + end) + + + t.it("multiple conflict rename second conflict", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "a", + "f2.renamed", + }) + + na.file_exists("/tmp/nvt_func/data/d2/f2.renamed") + + nf.feed_sync({ + "W", + "gg", + "", + "", + "G", + "c", + "", + "c", + "p", + }) + + screen:expect({ + cmdline = { { prompt = "2 file(s) already exist. R(ename suffix)/y/n: ", content = { { "" } }, pos = 0, } }, + }) + + nf.feed_sync({ + "", + ".renamed", + }) + + screen:expect({ + cmdline = { { prompt = "Overwrite /tmp/nvt_func/data/d2/f2.renamed ? R(ename)/y/n: ", content = { { "" } }, pos = 0, } }, + }) + + nf.feed_sync({ + "", + ".again", + }) + + + screen:expect({ + cmdline = { { abort = true } }, + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +   d2 │~ | +   d1 │~ | +   d3 │~ | +   d3.renamed │~ | +  f1 │~ | +  f2 │~ | +  f2.renamed │~ | + ^  f2.renamed.again │~ | +   d3 │~ | +  f1 │~ | +  f2 │~ | +~ │~ |*9 +NvimTree_1 [-] [No Name] | + | + ]], + }) + -- + na.dir_exists("/tmp/nvt_func/data/d2/d1") + na.dir_exists("/tmp/nvt_func/data/d2/d3") + na.file_exists("/tmp/nvt_func/data/d2/f1") + na.file_exists("/tmp/nvt_func/data/d2/f2") + + na.file_exists("/tmp/nvt_func/data/d2/f2.renamed") + na.file_exists("/tmp/nvt_func/data/d2/f2.renamed.again") + na.dir_exists("/tmp/nvt_func/data/d2/d3.renamed") + na.file_exists("/tmp/nvt_func/data/d2/d3.renamed/d3f1") + end) + + + t.it("multiple conflict overwrite", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "", + "G", + "c", + "", + "c", + "p", + }) + + -- BUG: this should read as per single prompt: "2 file(s) already exist. R(ename suffix)/y/n: " + screen:expect({ + cmdline = { { prompt = "2 file(s) already exist. R(ename suffix)/y/n: ", content = { { "" } }, pos = 0, } }, + }) + + nf.feed_sync({ + "y", + }) + + screen:expect({ + cmdline = { { abort = true } }, + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d1 │~ | +   d2 │~ | +   d1 │~ | +   d3 │~ | +  f1 │~ | +  f2 │~ | +   d3 │~ | +  f1 │~ | +  f2 │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | +~ │~ | + | +[NvimTree] Could not copy /tmp/nvt_func/data/d3 - EEXIST: file already exists: /| +tmp/nvt_func/data/d2/d3 | +Press ENTER or type command to continue^ | + ]], + }) + + na.dir_exists("/tmp/nvt_func/data/d2/d1") + na.dir_exists("/tmp/nvt_func/data/d2/d3") + na.file_exists("/tmp/nvt_func/data/d2/f1") + na.file_exists("/tmp/nvt_func/data/d2/f2") + end) +end) + +-- vim:colorcolumn=80 diff --git a/test/nvt/func/fs/paste/cut_spec.lua b/test/nvt/func/fs/paste/cut_spec.lua new file mode 100644 index 00000000000..0c415fd7a5b --- /dev/null +++ b/test/nvt/func/fs/paste/cut_spec.lua @@ -0,0 +1,212 @@ +local t = require("test.testutil") +local n = require("test.functional.testnvim")() +local nf = require("test.nvt.func.fixtures") +local na = require("test.nvt.func.asserts") + +-- 0.12 global compatibility +---@diagnostic disable-next-line: inject-field, undefined-global +t.describe, t.it, t.before_each = t.describe or describe, t.it or it, t.before_each or before_each + +--- @type test.functional.ui.screen +local screen + + +-- common copy/paste codepaths are tested in copied_spec.lua except for API + + +t.before_each(function() + screen = nf.create_session({ data = "test/nvt/func/fs/paste/data", }) + + local Event = require("nvim-tree._meta.api.events").Event + nf.event_subscribe(Event.NodeRenamed) +end) + + +t.describe("fs.paste cut", function() + t.it("single dir", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "x", + "", + "p", + "", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d2 │~ | + ^   d1 │~ | +  d1f1 │~ | +  d1f2 │~ | +   d3 │~ | +  f2 │~ | +   d3 │~ | +  f1 │~ | +  f2 │~ | +~ │~ |*12 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/d1 added to clipboard. | + ]], + }) + + na.dir_exists("/tmp/nvt_func/data/d2/d1") + na.file_exists("/tmp/nvt_func/data/d2/d1/d1f1") + na.file_exists("/tmp/nvt_func/data/d2/d1/d1f2") + + na.not_path_exists("/tmp/nvt_func/data/d1") + + na.events_received({ { + event_type = "NodeRenamed", + payload = { + old_name = "/tmp/nvt_func/data/d1", + new_name = "/tmp/nvt_func/data/d2/d1", + }, + }, }) + end) + + + t.it("single file", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "x", + "", + "", + "", + "p", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │f1 | +   d1 │~ | +   d2 │~ | +   d3 │~ | +  d3f1 │~ | + ^  f1 │~ | +  f2 │~ | +~ │~ |*15 +NvimTree_1 [-] /tmp/nvt_func/data/d3/f1 | +[NvimTree] /tmp/nvt_func/data/f1 added to clipboard. | + ]], + }) + + na.file_exists("/tmp/nvt_func/data/d3/f1") + + na.not_path_exists("/tmp/nvt_func/data/f1") + + na.events_received({ { + event_type = "NodeRenamed", + payload = { + old_name = "/tmp/nvt_func/data/f1", + new_name = "/tmp/nvt_func/data/d3/f1", + }, + }, }) + + na.buf_exists("/tmp/nvt_func/data/d3/f1") + end) + + + t.it("api node under cursor", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "x", + "", + }) + + n.exec_lua(function() + require("nvim-tree.api").fs.paste() + end) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d2 │~ | + ^   d1 │~ | +   d3 │~ | +  f2 │~ | +   d3 │~ | +  f1 │~ | +  f2 │~ | +~ │~ |*14 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/d1 added to clipboard. | + ]], + }) + + na.dir_exists("/tmp/nvt_func/data/d2/d1") + na.file_exists("/tmp/nvt_func/data/d2/d1/d1f1") + na.file_exists("/tmp/nvt_func/data/d2/d1/d1f2") + + na.not_path_exists("/tmp/nvt_func/data/d1") + + na.events_received({ { + event_type = "NodeRenamed", + payload = { + old_name = "/tmp/nvt_func/data/d1", + new_name = "/tmp/nvt_func/data/d2/d1", + }, + }, }) + end) + + + t.it("api node specified", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "x", + }) + + n.exec_lua(function() + -- BUG #3362, replace with commented below when complete + local explorer = require("nvim-tree.core").get_explorer() + if explorer then + local node = explorer.nodes[3] -- d3 + explorer.clipboard:paste(node) + end + + -- local api = require("nvim-tree.api") + -- local explorer = api.tree.get_nodes() + -- local node = explorer.nodes[3] -- d3 + -- api.fs.paste(node) + end) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d2 │~ | +   d3 │~ | + ^   d1 │~ | +  d3f1 │~ | +  f1 │~ | +  f2 │~ | +~ │~ |*15 +NvimTree_1 [-] [No Name] | +[NvimTree] /tmp/nvt_func/data/d1 added to clipboard. | + ]], + }) + + na.dir_exists("/tmp/nvt_func/data/d3/d1") + na.file_exists("/tmp/nvt_func/data/d3/d1/d1f1") + na.file_exists("/tmp/nvt_func/data/d3/d1/d1f2") + + na.not_path_exists("/tmp/nvt_func/data/d1") + + na.events_received({ { + event_type = "NodeRenamed", + payload = { + old_name = "/tmp/nvt_func/data/d1", + new_name = "/tmp/nvt_func/data/d3/d1", + }, + }, }) + end) +end) + +-- vim:colorcolumn=80 diff --git a/test/nvt/func/fs/paste/data/d1/d1f1 b/test/nvt/func/fs/paste/data/d1/d1f1 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/nvt/func/fs/paste/data/d1/d1f2 b/test/nvt/func/fs/paste/data/d1/d1f2 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/nvt/func/fs/paste/data/d2/d3/d3f1 b/test/nvt/func/fs/paste/data/d2/d3/d3f1 new file mode 100644 index 00000000000..832bad2faee --- /dev/null +++ b/test/nvt/func/fs/paste/data/d2/d3/d3f1 @@ -0,0 +1 @@ +d3f1 diff --git a/test/nvt/func/fs/paste/data/d2/f2 b/test/nvt/func/fs/paste/data/d2/f2 new file mode 100644 index 00000000000..9de77c18733 --- /dev/null +++ b/test/nvt/func/fs/paste/data/d2/f2 @@ -0,0 +1 @@ +f2 diff --git a/test/nvt/func/fs/paste/data/d3/d3f1 b/test/nvt/func/fs/paste/data/d3/d3f1 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/nvt/func/fs/paste/data/f1 b/test/nvt/func/fs/paste/data/f1 new file mode 100644 index 00000000000..8e1e71d5ce3 --- /dev/null +++ b/test/nvt/func/fs/paste/data/f1 @@ -0,0 +1 @@ +f1 diff --git a/test/nvt/func/fs/paste/data/f2 b/test/nvt/func/fs/paste/data/f2 new file mode 100644 index 00000000000..9de77c18733 --- /dev/null +++ b/test/nvt/func/fs/paste/data/f2 @@ -0,0 +1 @@ +f2 diff --git a/test/nvt/func/fs/print_clipboard/data/d1/d1f1 b/test/nvt/func/fs/print_clipboard/data/d1/d1f1 new file mode 100644 index 00000000000..13d1560f267 --- /dev/null +++ b/test/nvt/func/fs/print_clipboard/data/d1/d1f1 @@ -0,0 +1 @@ +d1f1 diff --git a/test/nvt/func/fs/print_clipboard/data/d1/d1f2 b/test/nvt/func/fs/print_clipboard/data/d1/d1f2 new file mode 100644 index 00000000000..6ec000bf6df --- /dev/null +++ b/test/nvt/func/fs/print_clipboard/data/d1/d1f2 @@ -0,0 +1 @@ +d1f2 diff --git a/test/nvt/func/fs/print_clipboard/data/d2/d2f1 b/test/nvt/func/fs/print_clipboard/data/d2/d2f1 new file mode 100644 index 00000000000..f901b65feae --- /dev/null +++ b/test/nvt/func/fs/print_clipboard/data/d2/d2f1 @@ -0,0 +1 @@ +d2f1 diff --git a/test/nvt/func/fs/print_clipboard/data/d3/d3f1 b/test/nvt/func/fs/print_clipboard/data/d3/d3f1 new file mode 100644 index 00000000000..832bad2faee --- /dev/null +++ b/test/nvt/func/fs/print_clipboard/data/d3/d3f1 @@ -0,0 +1 @@ +d3f1 diff --git a/test/nvt/func/fs/print_clipboard/data/f1 b/test/nvt/func/fs/print_clipboard/data/f1 new file mode 100644 index 00000000000..8e1e71d5ce3 --- /dev/null +++ b/test/nvt/func/fs/print_clipboard/data/f1 @@ -0,0 +1 @@ +f1 diff --git a/test/nvt/func/fs/print_clipboard/data/f2 b/test/nvt/func/fs/print_clipboard/data/f2 new file mode 100644 index 00000000000..9de77c18733 --- /dev/null +++ b/test/nvt/func/fs/print_clipboard/data/f2 @@ -0,0 +1 @@ +f2 diff --git a/test/nvt/func/fs/print_clipboard/print_clipboard_spec.lua b/test/nvt/func/fs/print_clipboard/print_clipboard_spec.lua new file mode 100644 index 00000000000..5a8c4d7b6e9 --- /dev/null +++ b/test/nvt/func/fs/print_clipboard/print_clipboard_spec.lua @@ -0,0 +1,83 @@ +local t = require("test.testutil") +local n = require("test.functional.testnvim")() +local nf = require("test.nvt.func.fixtures") + +-- 0.12 global compatibility +---@diagnostic disable-next-line: inject-field, undefined-global +t.describe, t.it, t.before_each = t.describe or describe, t.it or it, t.before_each or before_each + + +--- @type test.functional.ui.screen +local screen + + +t.before_each(function() + screen = nf.create_session({ + data = "test/nvt/func/fs/print_clipboard/data", + screen_opts = { ext_messages = true }, + }) +end) + + +t.describe("fs.print_clipboard", function() + t.it("none", function() + nf.feed_sync({ + ":NvimTreeOpen", + }) + + n.exec_lua(function() + require("nvim-tree.api").fs.print_clipboard() + end) + + screen:expect({ + messages = { { content = { { "[NvimTree]\n\n", } }, history = true, kind = "echomsg", } }, + }) + end) + + + t.it("many", function() + nf.feed_sync({ + ":NvimTreeOpen", + "", + "", + "", + "c", + "", + "x", + "", + "c", + "", + "x", + }) + + screen:expect({ + messages = { + { content = { { "[NvimTree] /tmp/nvt_func/data/d1/d1f1 added to clipboard." } }, history = true, kind = "echomsg" }, + { content = { { "[NvimTree] /tmp/nvt_func/data/d1/d1f2 added to clipboard." } }, history = true, kind = "echomsg" }, + { content = { { "[NvimTree] /tmp/nvt_func/data/d2 added to clipboard." } }, history = true, kind = "echomsg" }, + { content = { { "[NvimTree] /tmp/nvt_func/data/d3 added to clipboard." } }, history = true, kind = "echomsg" }, + }, + }) + + n.exec_lua(function() + require("nvim-tree.api").fs.print_clipboard() + end) + + screen:expect({ + messages = { { + history = true, + kind = "echomsg", + content = { { [[[NvimTree] +Cut + * /tmp/nvt_func/data/d1/d1f2 + * /tmp/nvt_func/data/d3 +Copy + * /tmp/nvt_func/data/d1/d1f1 + * /tmp/nvt_func/data/d2 +]], } }, + } }, + }) + end) +end) + +-- vim:colorcolumn=80 diff --git a/test/nvt/func/fs/trash/data/d 1/d 1 f 1 b/test/nvt/func/fs/trash/data/d 1/d 1 f 1 new file mode 100644 index 00000000000..5515d8cd4d4 --- /dev/null +++ b/test/nvt/func/fs/trash/data/d 1/d 1 f 1 @@ -0,0 +1 @@ +d 1 f 1 diff --git a/test/nvt/func/fs/trash/data/d 2/d 2 f 1 b/test/nvt/func/fs/trash/data/d 2/d 2 f 1 new file mode 100644 index 00000000000..622cb1eb8e7 --- /dev/null +++ b/test/nvt/func/fs/trash/data/d 2/d 2 f 1 @@ -0,0 +1 @@ +d 2 f 1 diff --git a/test/nvt/func/fs/trash/data/f 1 b/test/nvt/func/fs/trash/data/f 1 new file mode 100644 index 00000000000..95635f9f126 --- /dev/null +++ b/test/nvt/func/fs/trash/data/f 1 @@ -0,0 +1 @@ +f 1 diff --git a/test/nvt/func/fs/trash/data/trash_fail.sh b/test/nvt/func/fs/trash/data/trash_fail.sh new file mode 100755 index 00000000000..4534a4f3e60 --- /dev/null +++ b/test/nvt/func/fs/trash/data/trash_fail.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env sh + +if [ "${1}" = "/tmp/nvt_func/data/f 1" ]; then + rm "${1}" + exit 0 +else + echo -n "synthetic trash failure" >&2 + exit 1 +fi diff --git a/test/nvt/func/fs/trash/node_spec.lua b/test/nvt/func/fs/trash/node_spec.lua new file mode 100644 index 00000000000..fb024cf5d41 --- /dev/null +++ b/test/nvt/func/fs/trash/node_spec.lua @@ -0,0 +1,308 @@ +local t = require("test.testutil") +local nf = require("test.nvt.func.fixtures") +local na = require("test.nvt.func.asserts") + +-- 0.12 global compatibility +---@diagnostic disable-next-line: inject-field, undefined-global +t.describe, t.it, t.before_each = t.describe or describe, t.it or it, t.before_each or before_each + + +--- @type test.functional.ui.screen +local screen + + +---@param config nvim_tree.config +---@return test.functional.ui.screen +local function create_trash_session(config) + screen = nf.create_session({ + config = config, + data = "test/nvt/func/fs/trash/data", + screen_opts = { ext_messages = true, ext_cmdline = true, }, + }) + + local Event = require("nvim-tree._meta.api.events").Event + nf.event_subscribe(Event.FileRemoved) + nf.event_subscribe(Event.WillRemoveFile) + nf.event_subscribe(Event.FolderRemoved) + + return screen +end + + +t.describe("fs.trash", function() + t.it("single file buffer deleted", function() + screen = create_trash_session({ + trash = { + cmd = "rm -rf ", + } + }) + + nf.feed_sync({ + ":NvimTreeOpen", + "", + "", + "", + "", + ":NvimTreeFocus", + "D", + }) + + screen:expect({ + cmdline = { { prompt = "Trash d 1 f 1? y/N: ", content = { { "" } }, pos = 0, } }, + }) + + nf.feed_sync({ + "y", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. | +   d 1 | + ^  d 2 | +  f 1 | +  trash_fail.sh | +~ |*19 + ]], + }) + + na.not_path_exists("/tmp/nvt_func/data/d 1/d 1 f 1") + na.not_buf_exists("/tmp/nvt_func/data/d 1/d 1 f 1") + + na.events_received({ + { event_type = "WillRemoveFile", payload = { fname = "/tmp/nvt_func/data/d 1/d 1 f 1", }, }, + { event_type = "FileRemoved", payload = { fname = "/tmp/nvt_func/data/d 1/d 1 f 1", }, }, + }) + end) + + + t.it("single dir", function() + screen = create_trash_session({ + trash = { + cmd = "rm -rf ", + } + }) + + nf.feed_sync({ + ":NvimTreeOpen", + "", + "D", + }) + + screen:expect({ + cmdline = { { prompt = "Trash d 1? y/N: ", content = { { "" } }, pos = 0, } }, + }) + + nf.feed_sync({ + "y", + "gg", + "E", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + ^/tmp/nvt_func/data/.. │ | +   d 2 │~ | +  d 2 f 1 │~ | +  f 1 │~ | +  trash_fail.sh │~ | +~ │~ |*18 +NvimTree_1 [-] [No Name] | + ]], + }) + + na.not_path_exists("/tmp/nvt_func/data/d 1") + + na.events_received({ + { event_type = "FolderRemoved", payload = { folder_name = "/tmp/nvt_func/data/d 1", }, }, + }) + end) + + + t.it("many", function() + screen = create_trash_session({ + trash = { + cmd = "rm -rf ", + } + }) + + nf.feed_sync({ + ":NvimTreeOpen", + "", + "", + "", + "", + "", + "D", + }) + + screen:expect({ + cmdline = { { prompt = "Trash 3 selected? y/N: ", content = { { "" } }, pos = 0, } }, + showmode = { { "-- VISUAL LINE --", 5, "ModeMsg" } }, + }) + + nf.feed_sync({ + "y", + "gg", + "E", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + ^/tmp/nvt_func/data/.. │ | +   d 1 │~ | +  trash_fail.sh │~ | +~ │~ |*20 +NvimTree_1 [-] [No Name] | + ]], + }) + + na.events_received({ + { event_type = "WillRemoveFile", payload = { fname = "/tmp/nvt_func/data/d 1/d 1 f 1", }, }, + { event_type = "WillRemoveFile", payload = { fname = "/tmp/nvt_func/data/f 1", }, }, + { event_type = "FileRemoved", payload = { fname = "/tmp/nvt_func/data/d 1/d 1 f 1", }, }, + { event_type = "FolderRemoved", payload = { folder_name = "/tmp/nvt_func/data/d 2", }, }, + { event_type = "FileRemoved", payload = { fname = "/tmp/nvt_func/data/f 1", }, }, + }) + + na.not_path_exists("/tmp/nvt_func/data/d 1/d 1 f 1") + na.not_path_exists("/tmp/nvt_func/data/d 2") + na.not_path_exists("/tmp/nvt_func/data/f 1") + end) + + + t.it("invalid trash.cmd", function() + screen = create_trash_session({ + trash = { + cmd = "foobarbaz", + } + }) + + nf.feed_sync({ + ":NvimTreeOpen", + "", + "", + "", + "D", + }) + + screen:expect({ + cmdline = { { prompt = "Trash d 1 f 1? y/N: ", content = { { "" } }, pos = 0, } }, + }) + + nf.feed_sync({ + "y", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d 1 │~ | + ^  d 1 f 1 │~ | +   d 2 │~ | +  f 1 │~ | +  trash_fail.sh │~ | +~ │~ |*17 +NvimTree_1 [-] [No Name] | + ]], + messages = { { content = { { "[NvimTree] trash.cmd 'foobarbaz' is not an executable.", "WarningMsg" } }, history = true, kind = "echomsg" }, } + }) + + na.file_exists("/tmp/nvt_func/data/d 1/d 1 f 1") + + na.events_received({}) + end) + + + t.it("ignore root", function() + screen = create_trash_session({}) + + nf.feed_sync({ + ":NvimTreeOpen", + "D", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + ^/tmp/nvt_func/data/.. │ | +   d 1 │~ | +   d 2 │~ | +  f 1 │~ | +  trash_fail.sh │~ | +~ │~ |*18 +NvimTree_1 [-] [No Name] | + ]], + }) + + na.dir_exists("/tmp/nvt_func/data/d 1") + na.file_exists("/tmp/nvt_func/data/d 1/d 1 f 1") + na.dir_exists("/tmp/nvt_func/data/d 2") + na.file_exists("/tmp/nvt_func/data/d 2/d 2 f 1") + na.file_exists("/tmp/nvt_func/data/f 1") + + na.events_received({}) + end) + + + t.it("many multiple failures", function() + screen = create_trash_session({ + trash = { + -- fails unless "/tmp/nvt_func/data/f 1", prints identical error message as vim.fn.jobstart completion time is nondeterministic + cmd = "./trash_fail.sh", + } + }) + + nf.feed_sync({ + ":NvimTreeOpen", + "", + "", + "", + "", + "", + "D", + }) + + screen:expect({ + cmdline = { { prompt = "Trash 3 selected? y/N: ", content = { { "" } }, pos = 0, } }, + showmode = { { "-- VISUAL LINE --", 5, "ModeMsg" } }, + }) + + nf.feed_sync({ + "y", + }) + + screen:expect({ + attr_ids = {}, + grid = [[ + /tmp/nvt_func/data/.. │ | +   d 1 │~ | +  d 1 f 1 │~ | +   d 2 │~ | + ^  trash_fail.sh │~ | +~ │~ |*18 +NvimTree_1 [-] [No Name] | + ]], + messages = { + { content = { { "[NvimTree] trash failed: synthetic trash failure; please see :help nvim-tree.trash", "WarningMsg" } }, history = true, kind = "echomsg" }, + { content = { { "[NvimTree] trash failed: synthetic trash failure; please see :help nvim-tree.trash", "WarningMsg" } }, history = true, kind = "echomsg" } + }, + }) + + na.events_received({ + { event_type = "WillRemoveFile", payload = { fname = "/tmp/nvt_func/data/d 1/d 1 f 1", }, }, + { event_type = "WillRemoveFile", payload = { fname = "/tmp/nvt_func/data/f 1", }, }, + { event_type = "FileRemoved", payload = { fname = "/tmp/nvt_func/data/f 1", }, }, + }) + + na.file_exists("/tmp/nvt_func/data/d 1/d 1 f 1") + na.dir_exists("/tmp/nvt_func/data/d 2") + na.not_path_exists("/tmp/nvt_func/data/f 1") + end) +end) + +-- vim:colorcolumn=80 diff --git a/test/nvt/func/init_live.lua b/test/nvt/func/init_live.lua new file mode 100644 index 00000000000..f1459d786eb --- /dev/null +++ b/test/nvt/func/init_live.lua @@ -0,0 +1,88 @@ +local nu = require("test.nvt.func.util") + +-- match the test screen size: r to reapply if the terminal is not cooperating +local function screen_resize() + vim.o.columns = 80 + vim.o.lines = 24 +end + +---dump the screen to /tmp/nvt_live_dump.txt: d for raw, c for collapsed +---pipe is appended to each line +---caret is inserted at the cursor position +---@param collapse boolean identical lines collapsed with "*n" appended +local function live_dump(collapse) + local dump_path = "/tmp/nvt_live_dump.txt" + + local function cap_lines() + -- absolute cursor position + local cursor = { col = vim.fn.screencol(), row = vim.fn.screenrow(), } + + -- capture all screen lines + local lines = {} + for row = 1, vim.o.lines do + local line = "" + for col = 1, vim.o.columns do + if row == cursor.row and col == cursor.col then + line = line .. "^" + end + line = line .. vim.fn.screenstring(row, col) + end + lines[row] = line .. "|" + end + + return lines + end + + local function compress_lines(lines) + local ret = {} + local n = 0 + + for i = 1, #lines + 1 do + if lines[i - 1] == lines[i] then + n = n + 1 + else + if n > 0 then + table.insert(ret, lines[i - 1] .. "*" .. n + 1) + else + table.insert(ret, lines[i - 1]) + end + n = 0 + end + end + + return ret + end + + local lines = cap_lines() + + if collapse then + lines = compress_lines(lines) + end + + -- dump to file + vim.fn.writefile(lines, dump_path) + vim.api.nvim_command("echo 'dumped screen to " .. dump_path .. "'") + + -- try to open it + vim.ui.open(dump_path) +end + +---set the screen size to match tests +screen_resize() + +-- use dark background for readability under dark and light +vim.o.background = "dark" + +-- test instance cwd +local cwd = nu.setup_cwd(os.getenv("NVT_DIR_LIVE")) +vim.api.nvim_set_current_dir(cwd) + +-- mappings +vim.keymap.set("n", "u", function() live_dump(false) end, { remap = false, }) +vim.keymap.set("n", "c", function() live_dump(true) end, { remap = false, }) +vim.keymap.set("n", "r", screen_resize, { remap = false, }) +vim.keymap.set("n", "o", function() require("nvim-tree.api").tree.open() end, { remap = false, }) + +-- add test specific setup here +require("nvim-tree").setup({ +}) diff --git a/test/nvt/func/util.lua b/test/nvt/func/util.lua new file mode 100644 index 00000000000..8d1f8391beb --- /dev/null +++ b/test/nvt/func/util.lua @@ -0,0 +1,51 @@ +---General test utils that to not require test infra, for use by tests and live + +local M = {} + +---Return the value of an environment variable, asserting presence +---return @string +function M.env(name) + local val = os.getenv(name) + assert(val, "$" .. name .. " not set") + return val +end + +---Execute vim.system and waits +---Assers RC=0 only for nvim > 0.12 +---@param cmd string[] Command to execute +function M.sys(cmd) + if vim.system then + local out = vim.system(cmd):wait() + assert(out.code == 0, vim.inspect(out)) + else + -- vim.system and n.system are not available for neovim 0.12 func tests + -- this will fail silently + require("test.functional.testnvim")().fn.system(cmd) + end +end + +---Create /tmp/nvt_func or populated /tmp/nvt_func/data +---@param dir_data? string relative path of data directory to copy for test instance cwd +---@return string absolute path +function M.setup_cwd(dir_data) + local cwd = M.env("NVT_FUNC_DIR_CWD") + + -- always blow away and recreate the cwd of the test instance + M.sys({ "rm", "-rf", cwd }) + M.sys({ "mkdir", "-p", cwd }) + + -- if data is present, recursively copy it and use that as cwd + if dir_data then + local abs_dir_data = M.env("NVT_DIR_SRC") .. "/" .. dir_data + local stat, err = vim.uv.fs_stat(abs_dir_data) + assert(not err, err) + assert(stat and stat.type == "directory", abs_dir_data .. " not a directory") + + cwd = cwd .. "/data" + M.sys({ "cp", "-p", "-r", abs_dir_data, cwd }) + end + + return cwd +end + +return M