Meh I'll figure out submodules later

This commit is contained in:
mustard 2025-09-16 01:01:02 +02:00
parent 4ca9d44a90
commit 8cb281f436
352 changed files with 66107 additions and 0 deletions

View file

@ -0,0 +1,44 @@
require("plenary.reload").reload_module "plenary"
local Job = require "plenary.job"
local profiler = require "plenary.profile.lua_profiler"
profiler.start()
local start = vim.fn.reltime()
local finish = nil
local results = {}
local j = Job:new {
command = "fdfind",
cwd = "~/plugins/",
enable_handlers = false,
on_stdout = function(_, data)
table.insert(results, data)
end,
-- on_exit = vim.schedule_wrap(function()
-- finish = vim.fn.reltime(start)
-- end),
}
pcall(function()
j:sync(2000, 5)
end)
finish = vim.fn.reltime(start)
profiler.stop()
profiler.report "/home/tj/tmp/temp.txt"
if finish == nil then
print "Did not finish :'("
else
print("finished in:", vim.fn.reltimestr(finish))
end
collectgarbage()
print(collectgarbage "count")

View file

@ -0,0 +1,5 @@
set rtp+=.
runtime plugin/plenary.vim
nnoremap ,,x :luafile %<CR>

View file

@ -0,0 +1,53 @@
require("plenary.async").tests.add_to_env()
describe("async", function()
a.it("void functions can call wrapped functions", function()
local stat = 0
local saved_arg
local wrapped = a.wrap(function(inc, callback)
stat = stat + inc
callback()
end, 2)
local voided = a.void(function(arg)
wrapped(1)
wrapped(2)
wrapped(3)
stat = stat + 1
saved_arg = arg
end)
voided "hello"
assert(stat == 7)
assert(saved_arg == "hello")
end)
a.it("void functions can call wrapped functions with ignored arguments", function()
local stat = 0
local saved_arg
local wrapped = a.wrap(function(inc, nil1, nil2, callback)
assert(type(inc) == "number")
assert(nil1 == nil)
assert(nil2 == nil)
assert(type(callback) == "function")
stat = stat + inc
callback()
end, 4)
local voided = a.void(function(arg)
wrapped(1)
wrapped(2, nil)
wrapped(3, nil, nil)
stat = stat + 1
saved_arg = arg
end)
voided "hello"
assert(stat == 7)
assert(saved_arg == "hello")
end)
end)

View file

@ -0,0 +1,219 @@
require("plenary.async").tests.add_to_env()
local channel = a.control.channel
local eq = assert.are.same
local apcall = a.util.apcall
describe("channel", function()
describe("oneshot", function()
a.it("should work when rx is used first", function()
local tx, rx = channel.oneshot()
a.run(function()
local got = rx()
eq("sent value", got)
end)
tx "sent value"
end)
a.it("should work when tx is used first", function()
local tx, rx = channel.oneshot()
tx "sent value"
local got = rx()
eq("sent value", got)
end)
a.it("should work with multiple returns", function()
local tx, rx = channel.oneshot()
a.run(function()
local got, got2 = rx()
eq("sent value", got)
eq("another sent value", got2)
end)
tx("sent value", "another sent value")
end)
a.it("should work when sending a falsey value", function()
local tx, rx = channel.oneshot()
tx(false)
local res = rx()
eq(res, false)
local stat, ret = apcall(rx)
eq(stat, false)
local stat, ret = apcall(rx)
eq(stat, false)
end)
a.it("should work when sending a nil value", function()
local tx, rx = channel.oneshot()
tx(nil)
local res = rx()
eq(res, nil)
local stat, ret = apcall(rx)
eq(stat, false)
local stat, ret = apcall(rx)
eq(stat, false)
end)
a.it("should error when sending mulitple times", function()
local tx, rx = channel.oneshot()
tx()
local stat = pcall(tx)
eq(stat, false)
end)
a.it("should block receiving multiple times", function()
local tx, rx = channel.oneshot()
tx(true)
rx()
local stat = apcall(rx)
eq(stat, false)
end)
end)
describe("mpsc", function()
a.it("should wait multiple recv before any send", function()
local sender, receiver = channel.mpsc()
local expected_count = 10
a.run(function()
for i = 1, expected_count do
a.util.sleep(250)
sender.send(i)
end
end)
local receive_count = 0
while receive_count < expected_count do
receive_count = receive_count + 1
local i = receiver.recv()
eq(receive_count, i)
end
end)
a.it("should queues multiple sends before any read", function()
local sender, receiver = channel.mpsc()
local counter = 0
a.run(function()
counter = counter + 1
sender.send(10)
counter = counter + 1
sender.send(20)
end)
a.util.sleep(1000)
eq(10, receiver.recv())
eq(20, receiver.recv())
eq(2, counter)
end)
a.it("should queues multiple sends from multiple producers before any read", function()
local sender, receiver = channel.mpsc()
local counter = 0
a.run(function()
counter = counter + 1
sender.send(10)
counter = counter + 1
sender.send(20)
end)
a.run(function()
counter = counter + 1
sender.send(30)
counter = counter + 1
sender.send(40)
end)
a.util.sleep(1000)
local read_counter = 0
a.util.block_on(function()
for _ = 1, 4 do
receiver.recv()
read_counter = read_counter + 1
end
end, 1000)
eq(4, counter)
eq(4, read_counter)
end)
a.it("should read only the last value", function()
local sender, receiver = channel.mpsc()
local counter = 0
a.run(function()
counter = counter + 1
sender.send(10)
counter = counter + 1
sender.send(20)
end)
a.util.sleep(1000)
eq(20, receiver.last())
eq(2, counter)
end)
end)
describe("counter", function()
a.it("should work", function()
local tx, rx = channel.counter()
tx.send()
tx.send()
tx.send()
local counter = 0
a.run(function()
for i = 1, 3 do
rx.recv()
counter = counter + 1
end
end)
eq(counter, 3)
end)
a.it("should work when getting last", function()
local tx, rx = channel.counter()
tx.send()
tx.send()
tx.send()
local counter = 0
a.run(function()
rx.last()
counter = counter + 1
end)
eq(counter, 1)
end)
end)
end)

View file

@ -0,0 +1,158 @@
require("plenary.async").tests.add_to_env()
local Condvar = a.control.Condvar
local eq = assert.are.same
local join, run_all = a.util.join, a.util.run_all
describe("condvar", function()
a.it("should allow blocking", function()
local var = false
local condvar = Condvar.new()
a.run(function()
condvar:wait()
var = true
end)
eq(var, false)
condvar:notify_one()
eq(var, true)
end)
a.it("should be able to notify one when running", function()
local counter = 0
local condvar = Condvar.new()
local first = function()
condvar:wait()
counter = counter + 1
end
local second = function()
condvar:wait()
counter = counter + 1
end
local third = function()
condvar:wait()
counter = counter + 1
end
a.run(function()
join { first, second, third }
end)
eq(0, counter)
condvar:notify_one()
eq(1, counter)
condvar:notify_one()
eq(counter, 2)
condvar:notify_one()
eq(counter, 3)
end)
a.it("should allow notify_one to work when using await_all", function()
local counter = 0
local condvar = Condvar.new()
local first = function()
condvar:wait()
counter = counter + 1
end
local second = function()
condvar:wait()
counter = counter + 1
end
local third = function()
condvar:wait()
counter = counter + 1
end
run_all { first, second, third }
eq(0, counter)
condvar:notify_one()
eq(1, counter)
condvar:notify_one()
eq(counter, 2)
condvar:notify_one()
eq(counter, 3)
end)
a.it("should notify_all", function()
local counter = 0
local condvar = Condvar.new()
local first = function()
condvar:wait()
counter = counter + 1
end
local second = function()
condvar:wait()
counter = counter + 1
end
local third = function()
condvar:wait()
counter = counter + 1
end
run_all { first, second, third }
eq(0, counter)
condvar:notify_all()
eq(3, counter)
end)
a.it("notify all works multiple times", function()
local condvar = Condvar.new()
local counter = 0
a.run(function()
condvar:wait()
counter = counter + 1
end)
a.run(function()
condvar:wait()
counter = counter + 1
end)
eq(0, counter)
condvar:notify_all()
eq(2, counter)
a.run(function()
condvar:wait()
counter = 0
end)
condvar:notify_all()
eq(0, counter)
end)
end)

View file

@ -0,0 +1,91 @@
local Deque = require("plenary.async.structs").Deque
local eq = assert.are.same
-- just a helper to create the test deque
local function new_deque()
local deque = Deque.new()
eq(deque:len(), 0)
deque:pushleft(1)
eq(deque:len(), 1)
deque:pushleft(2)
eq(deque:len(), 2)
deque:pushright(3)
eq(deque:len(), 3)
deque:pushright(4)
eq(deque:len(), 4)
deque:pushright(5)
eq(deque:len(), 5)
return deque
end
describe("deque", function()
it("should allow pushing and popping and finding len", function()
new_deque()
end)
it("should be able to iterate from left", function()
local deque = new_deque()
local iter = deque:ipairs_left()
local i, v = iter()
eq(i, -2)
eq(v, 2)
i, v = iter()
eq(i, -1)
eq(v, 1)
i, v = iter()
eq(i, 0)
eq(v, 3)
i, v = iter()
eq(i, 1)
eq(v, 4)
i, v = iter()
eq(i, 2)
eq(v, 5)
end)
it("should be able to iterate from right", function()
local deque = new_deque()
local iter = deque:ipairs_right()
local i, v = iter()
eq(i, 2)
eq(v, 5)
i, v = iter()
eq(i, 1)
eq(v, 4)
i, v = iter()
eq(i, 0)
eq(v, 3)
i, v = iter()
eq(i, -1)
eq(v, 1)
i, v = iter()
eq(i, -2)
eq(v, 2)
end)
it("should allow clearing", function()
local deque = new_deque()
deque:clear()
assert(deque:is_empty())
end)
end)

View file

@ -0,0 +1,58 @@
require("plenary.async").tests.add_to_env()
local Semaphore = a.control.Semaphore
local eq = assert.are.same
describe("semaphore", function()
a.it("should validate arguments", function()
local status = pcall(Semaphore.new, -1)
eq(status, false)
local status = pcall(Semaphore.new)
eq(status, false)
end)
a.it("should acquire a permit if available", function()
local sem = Semaphore.new(1)
local permit = sem:acquire()
assert(permit ~= nil)
end)
a.it("should block if no permit is available", function()
local sem = Semaphore.new(1)
sem:acquire()
local completed = false
local blocking = function()
sem:acquire()
completed = true
end
a.run(blocking)
eq(completed, false)
end)
a.it("should give another permit when an acquired permit is released", function()
local sem = Semaphore.new(1)
local permit = sem:acquire()
permit:forget()
local next_permit = sem:acquire()
assert(next_permit ~= nil)
end)
a.it("should permit the next waiting client when a permit is released", function()
local sem = Semaphore.new(1)
local permit = sem:acquire()
local completed = false
local blocking = function()
sem:acquire()
completed = true
end
a.run(blocking)
permit:forget()
eq(completed, true)
end)
end)

View file

@ -0,0 +1,58 @@
require("plenary.async").tests.add_to_env()
a.describe("a.before_each", function()
local counter = 0
local set_counter_to_one = a.wrap(function(callback)
a.util.sleep(5)
counter = 1
end, 1)
a.before_each(a.void(function()
set_counter_to_one()
end))
a.it("should run in async context", function()
counter = counter + 1
assert.are.same(counter, 2)
end)
a.it("should run for all tests", function()
counter = counter + 2
assert.are.same(counter, 3)
end)
end)
a.describe("a.after_each", function()
local counter = 0
local set_counter_to_one = a.wrap(function(callback)
a.util.sleep(5)
counter = 1
end, 1)
a.after_each(a.void(function()
set_counter_to_one()
end))
a.it("should not run before first test", function()
counter = counter + 1
assert.are.same(counter, 1)
end)
a.it("should run before the second test", function()
counter = counter + 2
assert.are.same(counter, 3)
end)
a.it("should run before the third test", function()
counter = counter + 3
assert.are.same(counter, 4)
end)
end)
a.describe("a.pending", function()
a.pending("This test is disabled", function()
assert(false, "Should not run")
end)
end)

View file

@ -0,0 +1,77 @@
require("plenary.async").tests.add_to_env()
local block_on = a.util.block_on
local eq = assert.are.same
local id = a.util.id
describe("async await util", function()
describe("block_on", function()
a.it("should block_on", function()
local fn = function()
a.util.sleep(100)
return "hello"
end
local res = fn()
eq(res, "hello")
end)
a.it("should work even when failing", function()
local nonleaf = function()
eq(true, false)
end
local stat = pcall(block_on, nonleaf)
eq(stat, false)
end)
end)
describe("protect", function()
a.it("should be able to protect a non-leaf future", function()
local nonleaf = function()
error "This should error"
return "return"
end
local stat, ret = pcall(nonleaf)
eq(false, stat)
assert(ret:match "This should error")
end)
a.it("should be able to protect a non-leaf future that doesnt fail", function()
local nonleaf = function()
return "didnt fail"
end
local stat, ret = pcall(nonleaf)
eq(stat, true)
eq(ret, "didnt fail")
end)
end)
local function sleep(msec)
return function()
a.util.sleep(msec)
return msec
end
end
describe("race", function()
a.it("should return the first result", function()
local funcs = vim.tbl_map(sleep, { 300, 400, 100, 200 })
local result = a.util.race(funcs)
eq(result, 100)
end)
end)
describe("run_first", function()
a.it("should return the first result", function()
local async_functions = vim.tbl_map(function(num)
return function(callback)
return a.run(sleep(num), callback)
end
end, { 300, 400, 100, 200 })
local result = a.util.run_first(async_functions)
eq(result, 100)
end)
end)
end)

View file

@ -0,0 +1,75 @@
local Job = require "plenary.job"
local Timing = {}
function Timing:log(name)
self[name] = vim.loop.uptime()
end
function Timing:check(from, to, min_elapsed)
assert(self[from], "did not log " .. from)
assert(self[to], "did not log " .. to)
local elapsed = self[to] - self[from]
assert(
min_elapsed <= elapsed,
string.format("only took %s to get from %s to %s - expected at least %s", elapsed, from, to, min_elapsed)
)
end
describe("Async test", function()
it("can resume testing with vim.defer_fn", function()
local co = coroutine.running()
assert(co, "not running inside a coroutine")
local timing = setmetatable({}, { __index = Timing })
vim.defer_fn(function()
coroutine.resume(co)
end, 200)
timing:log "before"
coroutine.yield()
timing:log "after"
timing:check("before", "after", 0.1)
end)
it("can resume testing from job callback", function()
local co = coroutine.running()
assert(co, "not running inside a coroutine")
local timing = setmetatable({}, { __index = Timing })
Job:new({
command = "bash",
args = {
"-ce",
[[
sleep 0.2
echo hello
sleep 0.2
echo world
sleep 0.2
exit 42
]],
},
on_stdout = function(_, data)
timing:log(data)
end,
on_exit = function(_, exit_status)
timing:log "exit"
--This is required so that the rest of the test will run in a proper context
vim.schedule(function()
coroutine.resume(co, exit_status)
end)
end,
}):start()
timing:log "job started"
local exit_status = coroutine.yield()
timing:log "job finished"
assert.are.equal(exit_status, 42)
timing:check("job started", "job finished", 0.3)
timing:check("job started", "hello", 0.1)
timing:check("hello", "world", 0.1)
timing:check("world", "job finished", 0.1)
end)
end)

View file

@ -0,0 +1,203 @@
local context_manager = require "plenary.context_manager"
local debug_utils = require "plenary.debug_utils"
local Path = require "plenary.path"
local with = context_manager.with
local open = context_manager.open
local README_STR_PATH = vim.fn.fnamemodify(debug_utils.sourced_filepath(), ":h:h:h") .. "/README.md"
local README_FIRST_LINE = "# plenary.nvim"
describe("context_manager", function()
it("works with objects", function()
local obj_manager = {
enter = function(self)
self.result = 10
return self.result
end,
exit = function() end,
}
local result = with(obj_manager, function(obj)
return obj
end)
assert.are.same(10, result)
assert.are.same(obj_manager.result, result)
end)
it("works with coroutine", function()
local co = function()
coroutine.yield(10)
end
local result = with(co, function(obj)
return obj
end)
assert.are.same(10, result)
end)
it("does not work with coroutine with extra yields", function()
local co = function()
coroutine.yield(10)
-- Can't yield twice. That'd be bad and wouldn't make any sense.
coroutine.yield(10)
end
assert.has.error_match(function()
with(co, function(obj)
return obj
end)
end, "Should not yield anymore, otherwise that would make things complicated")
end)
it("reads from files with open", function()
local result = with(open(README_STR_PATH), function(reader)
return reader:read()
end)
assert.are.same(result, README_FIRST_LINE)
end)
it("reads from Paths with open", function()
local p = Path:new(README_STR_PATH)
local result = with(open(p), function(reader)
return reader:read()
end)
assert.are.same(result, README_FIRST_LINE)
end)
it("calls exit on error with objects", function()
local entered = false
local exited = false
local obj_manager = {
enter = function(self)
entered = true
end,
exit = function(self)
exited = true
end,
}
assert.has.error_match(function()
with(obj_manager, function(obj)
assert(false, "failed in callback")
end)
end, "failed in callback")
assert.is["true"](entered)
assert.is["true"](exited)
end)
it("calls exit on error with coroutines", function()
local entered = false
local exited = false
local co = function()
entered = true
coroutine.yield(nil)
exited = true
end
assert.has.error_match(function()
with(co, function(obj)
assert(false, "failed in callback")
end)
end, "failed in callback")
assert.is["true"](entered)
assert.is["true"](exited)
end)
it("fails from enter error with objects", function()
local exited = false
local obj_manager = {
enter = function(self)
assert(false, "failed in enter")
end,
exit = function(self)
exited = true
end,
}
local ran_callback = false
assert.has.error_match(function()
with(obj_manager, function(obj)
ran_callback = true
end)
end, "failed in enter")
assert.is["false"](ran_callback)
assert.is["false"](exited)
end)
it("fails from enter error with coroutines", function()
local exited = false
local co = function()
assert(false, "failed in enter")
coroutine.yield(nil)
exited = true
end
local ran_callback = false
assert.has.error_match(function()
with(co, function(obj)
ran_callback = true
end)
end, "Should have yielded in coroutine.")
assert.is["false"](ran_callback)
assert.is["false"](exited)
end)
it("fails from exit error with objects", function()
local entered = false
local obj_manager = {
enter = function(self)
entered = true
end,
exit = function(self)
assert(false, "failed in exit")
end,
}
local ran_callback = false
assert.has.error_match(function()
with(obj_manager, function(obj)
ran_callback = true
end)
end, "failed in exit")
assert.is["true"](entered)
assert.is["true"](ran_callback)
end)
it("fails from exit error with coroutines", function()
local entered = false
local co = function()
entered = true
coroutine.yield(nil)
assert(false, "failed in exit")
end
local ran_callback = false
assert.has.error_match(function()
with(co, function(obj)
ran_callback = true
end)
end, "Should be done")
assert.is["true"](entered)
assert.is["true"](ran_callback)
end)
end)

View file

@ -0,0 +1,221 @@
local curl = require "plenary.curl"
local eq = assert.are.same
local incl = function(p, s)
return (nil ~= string.find(s, p))
end
describe("CURL Wrapper:", function()
describe("request", function() -----------------------------------------------
it("sends and returns table.", function()
eq(
"table",
type(curl.request {
url = "https://postman-echo.com/get",
method = "get",
accept = "application/json",
})
)
end)
it("should accept the url as first argument.", function()
local res = curl.get("https://postman-echo.com/get", {
accept = "application/json",
})
eq(200, res.status)
end)
end)
describe("GET", function() --------------------------------------------------
it("sends and returns table.", function()
eq(
"table",
type(curl.get {
url = "https://postman-echo.com/get",
accept = "application/json",
})
)
end)
it("should accept the url as first argument.", function()
local res = curl.get("https://postman-echo.com/get", {
accept = "application/json",
})
eq(200, res.status) -- table has response status
end)
it("sends encoded URL query params.", function()
local query = { name = "john Doe", key = "123456" }
local response = curl.get("https://postman-echo.com/get", {
query = query,
})
eq(200, response.status)
eq(query, vim.fn.json_decode(response.body).args)
end)
it("downloads files to opts.output synchronously", function()
local file = "https://media2.giphy.com/media/bEMcuOG3hXVnihvB7x/giphy.gif"
local loc = "/tmp/giphy2.gif"
local res = curl.get(file, { output = loc })
eq(1, vim.fn.filereadable(loc), "should exists")
eq(200, res.status, "should return 200")
eq(0, res.exit, "should have exit code of 0")
vim.fn.delete(loc)
end)
it("downloads files to to opts.output asynchronous", function()
local res = nil
local succ = nil
local done = false
local file = "https://media2.giphy.com/media/notvalid.gif"
local loc = "/tmp/notvalid.gif"
curl.get(file, {
output = loc,
callback = function(out)
done = true
succ = out.status == 200
res = out
end,
})
vim.wait(60000, function()
return done
end)
eq(403, res.status, "It should return 403")
assert(not succ, "It should fail")
vim.fn.delete(loc)
end)
it("sends with basic-auth as string", function()
local url = "https://postman-echo.com/basic-auth"
local auth, res
auth = "postman:password"
res = curl.get(url, { auth = auth })
assert(incl("authenticated.*true", res.body))
eq(200, res.status)
auth = "tami5:123456"
res = curl.get(url, { auth = auth })
assert(not incl("authenticated.*true", res.body), "it should fail")
eq(401, res.status)
end)
it("sends with basic-auth as table", function()
local url = "https://postman-echo.com/basic-auth"
local res = curl.get(url, { auth = { postman = "password" } })
assert(incl("authenticated.*true", res.body))
eq(200, res.status)
end)
end)
describe("POST", function() --------------------------------------------------
it("sends raw string", function()
local res = curl.post("https://postman-echo.com/post", {
body = "John Doe",
})
assert(incl("John", res.body))
eq(200, res.status)
end)
it("sends lua table", function()
local res = curl.post("https://jsonplaceholder.typicode.com/posts", {
body = {
title = "Hello World",
body = "...",
},
})
eq(201, res.status)
end)
it("sends file", function()
local res = curl.post("https://postman-echo.com/post", {
body = "./README.md",
}).body
assert(incl("plenary.test_harness", res))
end)
it("sends and recives json body.", function()
local json = { title = "New", name = "YORK" }
local res = curl.post("https://postman-echo.com/post", {
body = vim.fn.json_encode(json),
headers = {
content_type = "application/json",
},
}).body
eq(json, vim.fn.json_decode(res).json)
end)
it("should not include the body twice", function()
local json = { title = "New", name = "YORK" }
local body = vim.fn.json_encode(json)
local res = curl.post("https://postman-echo.com/post", {
body = body,
headers = {
content_type = "application/json",
},
dry_run = true,
})
local joined_response = table.concat(res, " ")
local first_index = joined_response:find(body)
eq(nil, joined_response:find(body, first_index + 1))
end)
end)
describe("PUT", function() --------------------------------------------------
it("sends changes and get be back the new version.", function()
local cha = { title = "New Title" }
local res = curl.put("https://jsonplaceholder.typicode.com/posts/8", {
body = cha,
})
eq(cha.title, vim.fn.json_decode(res.body).title)
eq(200, res.status)
end)
end)
describe("PATCH", function() ------------------------------------------------
it("sends changes and get be back the new version.", function()
local cha = { title = "New Title" }
local res = curl.patch("https://jsonplaceholder.typicode.com/posts/8", {
body = cha,
})
eq(cha.title, vim.fn.json_decode(res.body).title)
eq(200, res.status)
end)
end)
describe("DELETE", function() ------------------------------------------------
it("sends delete request", function()
local res = curl.delete "https://jsonplaceholder.typicode.com/posts/8"
eq(200, res.status)
end)
end)
describe("DEBUG", function() --------------------------------------------------
it("dry_run return the curl command to be ran.", function()
local res = curl.delete("https://jsonplaceholder.typicode.com/posts/8", { dry_run = true })
assert(type(res) == "table")
end)
end)
describe("Issue #601", function() --------------------------------------------
it("should not use URL from previous call", function()
local url = "https://example.com"
local opts = { dry_run = true, dump = "" } -- dump would be random each time
local first = curl.get(url, opts)
eq(table.remove(first, #first), url, "expected url last")
local success, second = pcall(curl.get, opts)
if success then
eq(first, second, "should be same, but without url")
else
-- Failure is also acceptable
end
end)
end)
end)

View file

@ -0,0 +1,93 @@
local Enum = require "plenary.enum"
local function should_fail(fun)
local stat = pcall(fun)
assert(not stat, "Function should fail")
end
describe("Enum", function()
it("should be able to define specific values for members", function()
local E = Enum {
{ "Foo", 2 },
{ "Bar", 4 },
"Qux",
"Baz",
{ "Another", 11 },
}
assert(E.Foo.value == 2)
assert(E.Bar.value == 4)
assert(E.Qux.value == 5)
assert(E.Baz.value == 6)
assert(E.Another.value == 11)
assert(E[2] == "Foo")
assert(E[4] == "Bar")
assert(E[5] == "Qux")
assert(E[6] == "Baz")
assert(E[11] == "Another")
end)
it("should compare with itself", function()
local E1 = Enum {
"Foo",
{ "Qux", 11 },
"Bar",
"Baz",
}
local E2 = Enum {
"Foo",
"Bar",
"Baz",
}
assert(E1.Foo < E1.Qux)
assert(E1.Baz > E1.Bar)
assert(not (E1.Foo == E2.Foo))
should_fail(function()
return E1.Foo > E2.Foo
end)
should_fail(function()
return E2.Bar >= E1.Foo
end)
end)
it("should error when accessing invalid field", function()
local E = Enum {
"Foo",
"Bar",
"Baz",
}
should_fail(function()
return E.foo
end)
should_fail(function()
return E.bar
end)
end)
it("should fail if there is name or index clashing", function()
should_fail(function()
return Enum {
"Foo",
"Foo",
}
end)
should_fail(function()
return Enum {
"Foo",
{ "Bar", 1 },
}
end)
end)
it("should fail if there is a key that starts with lowercase", function()
should_fail(function()
return Enum {
"foo",
}
end)
end)
end)

View file

@ -0,0 +1,139 @@
local filetype = require "plenary.filetype"
describe("filetype", function()
describe("_get_extension_parts", function()
it("should find stuff with underscores", function()
assert.are.same({ "py" }, filetype._get_extension_parts "__init__.py")
end)
it("should find all possibilities", function()
assert.are.same({ "rst.txt", "txt" }, filetype._get_extension_parts "example.rst.txt")
assert.are.same({ "emacs.desktop", "desktop" }, filetype._get_extension_parts "example.emacs.desktop")
end)
end)
describe("detect_from_extension", function()
it("should work for md", function()
assert.are.same("markdown", filetype.detect_from_extension "Readme.md")
end)
it("should work for CMakeList.txt", function()
assert.are.same("text", filetype.detect_from_extension "CMakeLists.txt")
end)
it("should work with extensions with dot", function()
assert.are.same("rst", filetype.detect_from_extension "example.rst.txt")
assert.are.same("rst", filetype.detect_from_extension "example.rest.txt")
assert.are.same("yaml", filetype.detect_from_extension "example.yaml.sed")
assert.are.same("yaml", filetype.detect_from_extension "example.yml.mysql")
assert.are.same("erlang", filetype.detect_from_extension "asdf/example.app.src")
assert.are.same("cmake", filetype.detect_from_extension "/asdf/example.cmake.in")
assert.are.same("desktop", filetype.detect_from_extension "/asdf/asdf.desktop.in")
assert.are.same("xml", filetype.detect_from_extension "example.dll.config")
assert.are.same("haml", filetype.detect_from_extension "example.haml.deface")
assert.are.same("html", filetype.detect_from_extension "example.html.hl")
assert.are.same("yaml", filetype.detect_from_extension "example.model.lkml")
assert.are.same("rust", filetype.detect_from_extension "example.rs.in")
assert.are.same("sh", filetype.detect_from_extension "example.sh.in")
assert.are.same("json", filetype.detect_from_extension "example.tfstate.backup")
assert.are.same("yaml", filetype.detect_from_extension "example.view.lkml")
assert.are.same("xml", filetype.detect_from_extension "example.xml.dist")
assert.are.same("xml", filetype.detect_from_extension "example.xsp.metadata")
end)
it("should work for ext==ft even without a table value", function()
assert.are.same("bib", filetype.detect_from_extension "file.bib")
assert.are.same("bst", filetype.detect_from_extension "file.bst")
end)
end)
describe("detect_from_name", function()
it("should work for common filenames, like makefile", function()
assert.are.same("make", filetype.detect_from_name "Makefile")
assert.are.same("make", filetype.detect_from_name "makefile")
end)
it("should work for CMakeList.txt", function()
assert.are.same("cmake", filetype.detect_from_name "CMakeLists.txt")
end)
end)
describe("detect_from_modeline", function()
it("should work for modeline 2", function()
assert.are.same("help", filetype._parse_modeline " vim:tw=78:ts=8:noet:ft=help:norl:")
end)
it("should return nothing if ft not found in modeline", function()
assert.are.same("", filetype._parse_modeline "/* vim: set ts=8 sw=4 tw=0 noet : */")
end)
it("should return nothing for random line", function()
assert.are.same("", filetype._parse_modeline "return filetype")
end)
end)
describe("detect_from_shebang", function()
it("should work for shell", function()
assert.are.same("sh", filetype._parse_shebang "#!/bin/sh")
end)
it("should work for bash", function()
assert.are.same("sh", filetype._parse_shebang "#!/bin/bash")
end)
it("should work for usr/bin/env shell", function()
assert.are.same("sh", filetype._parse_shebang "#!/usr/bin/env sh")
end)
it("should work for env shell", function()
assert.are.same("sh", filetype._parse_shebang "#!/bin/env sh")
end)
it("should work for python", function()
assert.are.same("python", filetype._parse_shebang "#!/bin/python")
end)
it("should work for /usr/bin/python3", function()
assert.are.same("python", filetype._parse_shebang "#!/usr/bin/python3")
end)
it("should work for python3", function()
assert.are.same("python", filetype._parse_shebang "#!/bin/python3")
end)
it("should work for env python", function()
assert.are.same("python", filetype._parse_shebang "#!/bin/env python")
end)
it("should not work for random line", function()
assert.are.same("", filetype._parse_shebang 'local path = require"plenary.path"')
end)
end)
describe("detect", function()
it("should work for common filetypes, like python", function()
assert.are.same("python", filetype.detect "__init__.py")
end)
it("should work for common filenames, like makefile", function()
assert.are.same("make", filetype.detect "Makefile")
assert.are.same("make", filetype.detect "makefile")
end)
it("should work for CMakeList.txt", function()
assert.are.same("cmake", filetype.detect "CMakeLists.txt")
end)
it("should work for common files, even with .s, like .bashrc", function()
assert.are.same("sh", filetype.detect ".bashrc")
end)
it("should work fo custom filetypes, like fennel", function()
assert.are.same("fennel", filetype.detect "init.fnl")
end)
it("should work for custom filenames, like Cakefile", function()
assert.are.same("coffee", filetype.detect "Cakefile")
end)
end)
end)

View file

@ -0,0 +1,18 @@
local f = require "plenary.functional"
describe("functional", function()
describe("partial", function()
local function args(...)
assert.is.equal(4, select("#", ...))
return table.concat({ ... }, ",")
end
it("should bind correct parameters", function()
local expected = args(1, 2, 3, 4)
assert.is.equal(expected, f.partial(args)(1, 2, 3, 4))
assert.is.equal(expected, f.partial(args, 1)(2, 3, 4))
assert.is.equal(expected, f.partial(args, 1, 2)(3, 4))
assert.is.equal(expected, f.partial(args, 1, 2, 3)(4))
assert.is.equal(expected, f.partial(args, 1, 2, 3, 4)())
end)
end)
end)

View file

@ -0,0 +1,149 @@
local i = require "plenary.iterators"
local f = require "plenary.functional"
local eq = assert.are.same
local function check_keys(tbl, keys)
for _, key in ipairs(keys) do
if not tbl[key] then
error("Key " .. key .. " was not found")
end
end
end
describe("iterators", function()
it("should be able to create iterator from table", function()
local tbl = { first = 1, second = 2, third = 3, fourth = 4 }
local results = i.iter(tbl):tolist()
eq(#results, 4)
check_keys(tbl, { "first", "second", "third", "fourth" })
results = {
{ "first", 1 },
{ "second", 2 },
{ "third", 3 },
{ "fourth", 4 },
}
end)
it("should be able to create iterator from array", function()
local tbl = { 1, 2, 3, 4 }
local results = i.iter(tbl):tolist()
eq(#results, 4)
check_keys(tbl, { 1, 2, 3, 4 })
end)
it("should be able to fold", function()
local numbers = { 1, 2, 3, 4 }
local result = i.iter(numbers):fold(0, function(a, v)
return a + v
end)
eq(result, 10)
local strings = { "hello", "world", "this", "is", "a", "test" }
result = i.iter(strings):fold("", function(a, v)
return a .. v
end)
eq(result, "helloworldthisisatest")
end)
it("should be able to enumerate", function()
local tbl = { 1, 2, 3, 4 }
local results = i.iter(tbl)
:enumerate()
:map(function(idx, v)
return { idx, v }
end)
:tolist()
eq(#results, 4)
eq(results[1], { 1, 1 })
eq(results[2], { 2, 2 })
eq(results[3], { 3, 3 })
eq(results[4], { 4, 4 })
end)
it("should be able to find", function()
local tbl = { 1, 2, 3, 4 }
local tbl_iter = i.iter(tbl)
local res = tbl_iter:find(2)
eq(res, 2)
res = tbl_iter:find "will not find this"
assert(not res)
tbl = { 1, 2, 3, 4, "some random string", 6 }
eq(
i.iter(tbl):find(function(x)
return type(x) == "string"
end),
"some random string"
)
end)
it("should be table to chain", function()
local first = i.iter { 1, 2, 3 }
local second = i.iter { 4, 5, 6, 7 }
local third = i.iter { 8, 9, 10 }
local res = (first .. second .. third):tolist()
eq(res, i.range(10):tolist())
end)
it("should make a range", function()
eq({ 1, 2, 3, 4, 5 }, i.range(5):tolist())
end)
it("should be able to make a stateful", function()
local iter = i.range(3):stateful()
eq(iter(), 1)
eq(iter(), 2)
eq(iter(), 3)
assert(not iter())
assert(not iter())
assert(not iter())
local iter = i.range(3):stateful()
-- dump(iter:tolist())
eq(iter:tolist(), { 1, 2, 3 })
end)
it("should be able to flatten", function()
local iter = i.range(3)
:map(function(_)
return i.iter { 5, 7, 9 }
end)
:flatten()
:stateful()
eq(iter(), 5)
eq(iter(), 7)
eq(iter(), 9)
eq(iter(), 5)
eq(iter(), 7)
eq(iter(), 9)
eq(iter(), 5)
eq(iter(), 7)
eq(iter(), 9)
local iter = i.range(3)
:map(function(_)
return i.iter { 5, 7, 9 }
end)
:flatten()
eq(iter:tolist(), { 5, 7, 9, 5, 7, 9, 5, 7, 9 })
end)
it("should be able to flatten very nested stuff", function()
local iter = i.iter({ 5, 6, i.iter { i.iter { 5, 5 }, 7, 8 }, i.iter { 9, 10, i.iter { 1, 2 } } }):flatten()
eq(iter:tolist(), { 5, 6, 5, 5, 7, 8, 9, 10, 1, 2 })
end)
it("chaining nil should work", function()
local iter = i.iter(""):chain(i.iter { 5, 7, 9 })
eq(#iter:tolist(), 3)
end)
describe("generators", function()
it("should be able to split", function()
local iter = i.split(" hello person world ", " ")
eq(iter:tolist(), { "", "hello", "person", "world", "" })
iter = i.split("\n\n\nfirst\nsecond\nthird\n\n", "\n")
eq(iter:tolist(), { "", "", "", "first", "second", "third", "", "" })
end)
end)
end)

View file

@ -0,0 +1,28 @@
local Job = require "plenary.job"
describe("Job Validation", function()
it("does not require command when called with array method", function()
local ok, j = pcall(Job.new, Job, { "ls" })
assert(ok, "Accepts positional arguments")
assert(j.command == "ls")
end)
it("cannot use command and array syntax", function()
local ok = pcall(Job.new, Job, { "ls", command = "ls" })
assert(not ok, "cannot use command and array syntax")
end)
it("can parse command and args from array syntax", function()
local ok, j = pcall(Job.new, Job, { "ls", "-al" })
assert(ok, "Accepts positional arguments")
assert(j.command == "ls")
assert.are.same({ "-al" }, j.args)
end)
it("can parse command and multiple args from array syntax", function()
local ok, j = pcall(Job.new, Job, { "ls", "-al", "~" })
assert(ok, "Accepts positional arguments")
assert(j.command == "ls")
assert.are.same({ "-al", "~" }, j.args)
end)
end)

View file

@ -0,0 +1,864 @@
local Job = require "plenary.job"
local has_all_executables = function(execs)
for _, e in ipairs(execs) do
if vim.fn.executable(e) == 0 then
return false
end
end
return true
end
local tables_equal = function(t1, t2)
if #t1 ~= #t2 then
return false
end
for i, t1_v in ipairs(t1) do
if t2[i] ~= t1_v then
return false
end
end
return true
end
local wait_for_result = function(job, result)
if type(result) == "string" then
result = { result }
end
vim.wait(1000, function()
return tables_equal(job:result(), result)
end)
end
describe("Job", function()
describe("> cat manually >", function()
it("should split simple stdin", function()
local results = {}
local job = Job:new {
command = "cat",
on_stdout = function(_, data)
table.insert(results, data)
end,
}
job:start()
job:send "hello\n"
job:send "world\n"
wait_for_result(job, { "hello", "world" })
job:shutdown()
assert.are.same(job:result(), { "hello", "world" })
assert.are.same(job:result(), results)
end)
it("should allow empty strings", function()
local results = {}
local job = Job:new {
command = "cat",
on_stdout = function(_, data)
table.insert(results, data)
end,
}
job:start()
job:send "hello\n"
job:send "\n"
job:send "world\n"
job:send "\n"
wait_for_result(job, { "hello", "", "world", "" })
job:shutdown()
assert.are.same(job:result(), { "hello", "", "world", "" })
assert.are.same(job:result(), results)
end)
it("should split stdin across newlines", function()
local results = {}
local job = Job:new {
-- writer = "hello\nword\nthis is\ntj",
command = "cat",
on_stdout = function(_, data)
table.insert(results, data)
end,
}
job:start()
job:send "hello\nwor"
job:send "ld\n"
wait_for_result(job, { "hello", "world" })
job:shutdown()
assert.are.same(job:result(), { "hello", "world" })
assert.are.same(job:result(), results)
end)
it("should split stdin across newlines with no ending newline", function()
local results = {}
local job = Job:new {
-- writer = "hello\nword\nthis is\ntj",
command = "cat",
on_stdout = function(_, data)
table.insert(results, data)
end,
}
job:start()
job:send "hello\nwor"
job:send "ld"
wait_for_result(job, { "hello", "world" })
job:shutdown()
assert.are.same(job:result(), { "hello", "world" })
assert.are.same(job:result(), results)
end)
it("should return last line when there is ending newline", function()
local results = {}
local job = Job:new {
command = "printf",
args = { "test1\ntest2\n" },
on_stdout = function(_, data)
table.insert(results, data)
end,
}
job:sync()
assert.are.same(job:result(), { "test1", "test2" })
assert.are.same(job:result(), results)
end)
it("should return last line when there is no ending newline", function()
local results = {}
local job = Job:new {
command = "printf",
args = { "test1\ntest2" },
on_stdout = function(_, data)
table.insert(results, data)
end,
}
job:sync()
assert.are.same(job:result(), { "test1", "test2" })
assert.are.same(job:result(), results)
end)
end)
describe("env", function()
it("should be possible to set one env variable with an array", function()
local results = {}
local job = Job:new {
command = "env",
env = { "A=100" },
on_stdout = function(_, data)
table.insert(results, data)
end,
}
job:sync()
assert.are.same(job:result(), { "A=100" })
assert.are.same(job:result(), results)
end)
it("should be possible to set multiple env variables with an array", function()
local results = {}
local job = Job:new {
command = "env",
env = { "A=100", "B=test" },
on_stdout = function(_, data)
table.insert(results, data)
end,
}
job:sync()
assert.are.same(job:result(), { "A=100", "B=test" })
assert.are.same(job:result(), results)
end)
it("should be possible to set one env variable with a map", function()
local results = {}
local job = Job:new {
command = "env",
env = { "A=100" },
on_stdout = function(_, data)
table.insert(results, data)
end,
}
job:sync()
assert.are.same(job:result(), { "A=100" })
assert.are.same(job:result(), results)
end)
it("should be possible to set one env variable with spaces", function()
local results = {}
local job = Job:new {
command = "env",
env = { "A=This is a long env var" },
on_stdout = function(_, data)
table.insert(results, data)
end,
}
job:sync()
assert.are.same(job:result(), { "A=This is a long env var" })
assert.are.same(job:result(), results)
end)
it("should be possible to set one env variable with spaces and a map", function()
local results = {}
local job = Job:new {
command = "env",
env = { ["A"] = "This is a long env var" },
on_stdout = function(_, data)
table.insert(results, data)
end,
}
job:sync()
assert.are.same(job:result(), { "A=This is a long env var" })
assert.are.same(job:result(), results)
end)
it("should be possible to set multiple env variables with a map", function()
local results = {}
local job = Job:new {
command = "env",
env = { ["A"] = 100, ["B"] = "test" },
on_stdout = function(_, data)
table.insert(results, data)
end,
}
job:sync()
local expected = { "A=100", "B=test" }
local found = { false, false }
for k, v in ipairs(job:result()) do
for _, w in ipairs(expected) do
if v == w then
found[k] = true
end
end
end
assert.are.same({ true, true }, found)
assert.are.same(job:result(), results)
end)
it("should be possible to set multiple env variables with both, array and map", function()
local results = {}
local job = Job:new {
command = "env",
env = { ["A"] = 100, "B=test" },
on_stdout = function(_, data)
table.insert(results, data)
end,
}
job:sync()
local expected = { "A=100", "B=test" }
local found = { false, false }
for k, v in ipairs(job:result()) do
for _, w in ipairs(expected) do
if v == w then
found[k] = true
end
end
end
assert.are.same({ true, true }, found)
assert.are.same(job:result(), results)
end)
end)
describe("> simple ls >", function()
it("should match systemlist", function()
local ls_results = vim.fn.systemlist "ls -l"
local job = Job:new {
command = "ls",
args = { "-l" },
}
job:sync()
assert.are.same(job:result(), ls_results)
end)
it("should match larger systemlist", function()
local results = vim.fn.systemlist "find ."
local stdout_results = {}
local job = Job:new {
command = "find",
args = { "." },
on_stdout = function(_, line)
table.insert(stdout_results, line)
end,
}
job:sync()
assert.are.same(job:result(), results)
assert.are.same(job:result(), stdout_results)
end)
it("should not timeout when completing fast jobs", function()
local start = vim.loop.hrtime()
local job = Job:new { command = "ls" }
job:sync()
assert((vim.loop.hrtime() - start) / 1e9 < 1, "Should not take one second to complete")
end)
it("should return the return code as well", function()
local job = Job:new { command = "false" }
local _, ret = job:sync()
assert.are.same(1, job.code)
assert.are.same(1, ret)
end)
end)
describe("chain", function()
it("should always run the next job when using and_then", function()
local results = {}
local first_job = Job:new {
command = "env",
env = { ["a"] = "1" },
on_stdout = function(_, line)
table.insert(results, line)
end,
}
local second_job = Job:new {
command = "env",
env = { ["b"] = "2" },
on_stdout = function(_, line)
table.insert(results, line)
end,
}
local third_job = Job:new { command = "false" }
local fourth_job = Job:new {
command = "env",
env = { ["c"] = "3" },
on_stdout = function(_, line)
table.insert(results, line)
end,
}
first_job:and_then(second_job)
second_job:and_then(third_job)
third_job:and_then(fourth_job)
first_job:sync()
second_job:wait()
third_job:wait()
fourth_job:wait()
assert.are.same({ "a=1", "b=2", "c=3" }, results)
assert.are.same({ "a=1" }, first_job:result())
assert.are.same({ "b=2" }, second_job:result())
assert.are.same(1, third_job.code)
assert.are.same({ "c=3" }, fourth_job:result())
end)
it("should only run the next job on success when using and_then_on_success", function()
local results = {}
local first_job = Job:new {
command = "env",
env = { ["a"] = "1" },
on_stdout = function(_, line)
table.insert(results, line)
end,
}
local second_job = Job:new {
command = "env",
env = { ["b"] = "2" },
on_stdout = function(_, line)
table.insert(results, line)
end,
}
local third_job = Job:new { command = "false" }
local fourth_job = Job:new {
command = "env",
env = { ["c"] = "3" },
on_stdout = function(_, line)
table.insert(results, line)
end,
}
first_job:and_then_on_success(second_job)
second_job:and_then_on_success(third_job)
third_job:and_then_on_success(fourth_job)
first_job:sync()
second_job:wait()
third_job:wait()
assert.are.same({ "a=1", "b=2" }, results)
assert.are.same({ "a=1" }, first_job:result())
assert.are.same({ "b=2" }, second_job:result())
assert.are.same(1, third_job.code)
assert.are.same(nil, fourth_job.handle, "Job never started")
end)
it("should only run the next job on failure when using and_then_on_failure", function()
local results = {}
local first_job = Job:new {
command = "false",
}
local second_job = Job:new {
command = "env",
env = { ["a"] = "1" },
on_stdout = function(_, line)
table.insert(results, line)
end,
}
local third_job = Job:new {
command = "env",
env = { ["b"] = "2" },
on_stdout = function(_, line)
table.insert(results, line)
end,
}
first_job:and_then_on_failure(second_job)
second_job:and_then_on_failure(third_job)
local _, ret = first_job:sync()
second_job:wait()
assert.are.same(1, first_job.code)
assert.are.same(1, ret)
assert.are.same({ "a=1" }, results)
assert.are.same({ "a=1" }, second_job:result())
assert.are.same(nil, third_job.handle, "Job never started")
end)
it("should run all normal functions when using after", function()
local results = {}
local code = 0
local first_job = Job:new {
command = "env",
env = { ["a"] = "1" },
on_stdout = function(_, line)
table.insert(results, line)
end,
}
local second_job = Job:new { command = "false" }
first_job
:after(function()
code = code + 10
end)
:and_then(second_job)
second_job:after(function(_, c)
code = code + c
end)
first_job:sync()
second_job:wait()
assert.are.same({ "a=1" }, results)
assert.are.same({ "a=1" }, first_job:result())
assert.are.same(1, second_job.code)
assert.are.same(11, code)
end)
it("should run only on success normal functions when using after_success", function()
local results = {}
local code = 0
local first_job = Job:new {
command = "env",
env = { ["a"] = "1" },
on_stdout = function(_, line)
table.insert(results, line)
end,
}
local second_job = Job:new { command = "false" }
local third_job = Job:new { command = "true" }
first_job:after_success(function()
code = code + 10
end)
first_job:and_then_on_success(second_job)
second_job:after_success(function(_, c)
code = code + c
end)
second_job:and_then_on_success(third_job)
first_job:sync()
second_job:wait()
assert.are.same({ "a=1" }, results)
assert.are.same({ "a=1" }, first_job:result())
assert.are.same(1, second_job.code)
assert.are.same(10, code)
assert.are.same(nil, third_job.handle)
end)
it("should run only on failure normal functions when using after_failure", function()
local results = {}
local code = 0
local first_job = Job:new { command = "false" }
local second_job = Job:new {
command = "env",
env = { ["a"] = "1" },
on_stdout = function(_, line)
table.insert(results, line)
end,
}
local third_job = Job:new { command = "true" }
first_job:after_failure(function(_, c)
code = code + c
end)
first_job:and_then_on_failure(second_job)
second_job:after_failure(function()
code = code + 10
end)
second_job:and_then_on_failure(third_job)
local _, ret = first_job:sync()
second_job:wait()
assert.are.same({ "a=1" }, results)
assert.are.same({ "a=1" }, second_job:result())
assert.are.same(1, ret)
assert.are.same(1, first_job.code)
assert.are.same(1, code)
assert.are.same(0, second_job.code)
assert.are.same(nil, third_job.handle)
end)
end)
describe(".writer", function()
pending("should allow using things like fzf", function()
if not has_all_executables { "fzf", "fdfind" } then
return
end
local stdout_results = {}
local fzf = Job:new {
writer = Job:new {
command = "fdfind",
cwd = vim.fn.expand "~/plugins/plenary.nvim/",
},
command = "fzf",
args = { "--filter", "job.lua" },
cwd = vim.fn.expand "~/plugins/plenary.nvim/",
on_stdout = function(_, line)
table.insert(stdout_results, line)
end,
}
local results = fzf:sync()
assert.are.same(results, stdout_results)
-- 'job.lua' should be the best file from fzf.
-- So make sure we're processing correctly.
assert.are.same("lua/plenary/job.lua", results[1])
end)
it("should work with a table", function()
if not has_all_executables { "fzf" } then
return
end
local stdout_results = {}
local fzf = Job:new {
writer = { "hello", "world", "job.lua" },
command = "fzf",
args = { "--filter", "job.lua" },
on_stdout = function(_, line)
table.insert(stdout_results, line)
end,
}
local results = fzf:sync()
assert.are.same(results, stdout_results)
-- 'job.lua' should be the best file from fzf.
-- So make sure we're processing correctly.
assert.are.same("job.lua", results[1])
assert.are.same(1, #results)
end)
it("should work with a string", function()
if not has_all_executables { "fzf" } then
return
end
local stdout_results = {}
local fzf = Job:new {
writer = "hello\nworld\njob.lua",
command = "fzf",
args = { "--filter", "job.lua" },
on_stdout = function(_, line)
table.insert(stdout_results, line)
end,
}
local results = fzf:sync()
assert.are.same(results, stdout_results)
-- 'job.lua' should be the best file from fzf.
-- So make sure we're processing correctly.
assert.are.same("job.lua", results[1])
assert.are.same(1, #results)
end)
it("should work with a pipe", function()
if not has_all_executables { "fzf" } then
return
end
local input_pipe = vim.loop.new_pipe(false)
local stdout_results = {}
local fzf = Job:new {
writer = input_pipe,
command = "fzf",
args = { "--filter", "job.lua" },
on_stdout = function(_, line)
table.insert(stdout_results, line)
end,
}
fzf:start()
input_pipe:write "hello\n"
input_pipe:write "world\n"
input_pipe:write "job.lua\n"
input_pipe:close()
wait_for_result(fzf, "job.lua")
fzf:shutdown()
local results = fzf:result()
assert.are.same(results, stdout_results)
-- 'job.lua' should be the best file from fzf.
-- So make sure we're processing correctly.
assert.are.same("job.lua", results[1])
assert.are.same(1, #results)
end)
it("should work with a pipe, but no final newline", function()
if not has_all_executables { "fzf" } then
return
end
local input_pipe = vim.loop.new_pipe(false)
local stdout_results = {}
local fzf = Job:new {
writer = input_pipe,
command = "fzf",
args = { "--filter", "job.lua" },
on_stdout = function(_, line)
table.insert(stdout_results, line)
end,
}
fzf:start()
input_pipe:write "hello\n"
input_pipe:write "world\n"
input_pipe:write "job.lua"
input_pipe:close()
wait_for_result(fzf, "job.lua")
fzf:shutdown()
local results = fzf:result()
assert.are.same(results, stdout_results)
-- 'job.lua' should be the best file from fzf.
-- So make sure we're processing correctly.
assert.are.same("job.lua", results[1])
assert.are.same(1, #results)
end)
end)
describe(":wait()", function()
it("should respect timeout", function()
local j = Job:new {
command = "sleep",
args = { "10" },
}
local ok = pcall(j.sync, j, 500)
assert(not ok, "Job should fail")
end)
end)
describe("enable_.*", function()
it("should not add things to results when disabled", function()
local job = Job:new {
command = "ls",
args = { "-l" },
enable_recording = false,
}
local res = job:sync()
assert(res == nil, "No results should exist")
assert(job._stdout_results == nil, "No result table")
end)
it("should not call callbacks when disabled", function()
local was_called = false
local job = Job:new {
command = "ls",
args = { "-l" },
enable_handlers = false,
on_stdout = function()
was_called = true
end,
}
job:sync()
assert(not was_called, "Should not be called.")
assert(job._stdout_results == nil, "No result table")
end)
end)
describe("enable_.*", function()
it("should not add things to results when disabled", function()
local job = Job:new {
command = "ls",
args = { "-l" },
enable_recording = false,
}
local res = job:sync()
assert(res == nil, "No results should exist")
assert(job._stdout_results == nil, "No result table")
end)
it("should not call callbacks when disbaled", function()
local was_called = false
local job = Job:new {
command = "ls",
args = { "-l" },
enable_handlers = false,
on_stdout = function()
was_called = true
end,
}
job:sync()
assert(not was_called, "Should not be called.")
assert(job._stdout_results == nil, "No result table")
end)
end)
describe("validation", function()
it("requires options", function()
local ok = pcall(Job.new, { command = "ls" })
assert(not ok, "Requires options")
end)
it("requires command", function()
local ok = pcall(Job.new, Job, { cmd = "ls" })
assert(not ok, "Requires command")
end)
it("will not spawn jobs with invalid commands", function()
local ok = pcall(Job.new, Job, { command = "dasowlwl" })
assert(not ok, "Should not allow invalid executables")
end)
end)
describe("on_exit", function()
it("should only be called once for wait", function()
local count = 0
local job = Job:new {
command = "ls",
on_exit = function(...)
count = count + 1
end,
}
job:start()
job:wait()
assert.are.same(count, 1)
end)
it("should only be called once for shutdown", function()
local count = 0
local job = Job:new {
command = "ls",
on_exit = function(...)
count = count + 1
end,
}
job:start()
job:shutdown()
assert.are.same(count, 1)
end)
end)
end)

View file

@ -0,0 +1,87 @@
local json = require "plenary.json"
local eq = assert.are.same
describe("json", function()
it("replace comments with whitespace", function()
eq(json.json_strip_comments '//comment\n{"a":"b"}', ' \n{"a":"b"}')
eq(json.json_strip_comments '/*//comment*/{"a":"b"}', ' {"a":"b"}')
eq(json.json_strip_comments '{"a":"b"//comment\n}', '{"a":"b" \n}')
eq(json.json_strip_comments '{"a":"b"/*comment*/}', '{"a":"b" }')
eq(json.json_strip_comments '{"a"/*\n\n\ncomment\r\n*/:"b"}', '{"a" \n\n\n \r\n :"b"}')
eq(json.json_strip_comments '/*!\n * comment\n */\n{"a":"b"}', ' \n \n \n{"a":"b"}')
eq(json.json_strip_comments '{/*comment*/"a":"b"}', '{ "a":"b"}')
end)
it("remove comments", function()
local options = { whitespace = false }
eq(json.json_strip_comments('//comment\n{"a":"b"}', options), '\n{"a":"b"}')
eq(json.json_strip_comments('/*//comment*/{"a":"b"}', options), '{"a":"b"}')
eq(json.json_strip_comments('{"a":"b"//comment\n}', options), '{"a":"b"\n}')
eq(json.json_strip_comments('{"a":"b"/*comment*/}', options), '{"a":"b"}')
eq(json.json_strip_comments('{"a"/*\n\n\ncomment\r\n*/:"b"}', options), '{"a":"b"}')
eq(json.json_strip_comments('/*!\n * comment\n */\n{"a":"b"}', options), '\n{"a":"b"}')
eq(json.json_strip_comments('{/*comment*/"a":"b"}', options), '{"a":"b"}')
end)
it("doesn't strip comments inside strings", function()
eq(json.json_strip_comments '{"a":"b//c"}', '{"a":"b//c"}')
eq(json.json_strip_comments '{"a":"b/*c*/"}', '{"a":"b/*c*/"}')
eq(json.json_strip_comments '{"/*a":"b"}', '{"/*a":"b"}')
eq(json.json_strip_comments '{"\\"/*a":"b"}', '{"\\"/*a":"b"}')
end)
it("consider escaped slashes when checking for escaped string quote", function()
eq(json.json_strip_comments '{"\\\\":"https://foobar.com"}', '{"\\\\":"https://foobar.com"}')
eq(json.json_strip_comments '{"foo\\"":"https://foobar.com"}', '{"foo\\"":"https://foobar.com"}')
end)
it("line endings - no comments", function()
eq(json.json_strip_comments '{"a":"b"\n}', '{"a":"b"\n}')
eq(json.json_strip_comments '{"a":"b"\r\n}', '{"a":"b"\r\n}')
end)
it("line endings - single line comment", function()
eq(json.json_strip_comments '{"a":"b"//c\n}', '{"a":"b" \n}')
eq(json.json_strip_comments '{"a":"b"//c\r\n}', '{"a":"b" \r\n}')
end)
it("line endings - single line block comment", function()
eq(json.json_strip_comments '{"a":"b"/*c*/\n}', '{"a":"b" \n}')
eq(json.json_strip_comments '{"a":"b"/*c*/\r\n}', '{"a":"b" \r\n}')
end)
it("line endings - multi line block comment", function()
eq(json.json_strip_comments '{"a":"b",/*c\nc2*/"x":"y"\n}', '{"a":"b", \n "x":"y"\n}')
eq(json.json_strip_comments '{"a":"b",/*c\r\nc2*/"x":"y"\r\n}', '{"a":"b", \r\n "x":"y"\r\n}')
end)
it("line endings - works at EOF", function()
local options = { whitespace = false }
eq(json.json_strip_comments '{\r\n\t"a":"b"\r\n} //EOF', '{\r\n\t"a":"b"\r\n} ')
eq(json.json_strip_comments('{\r\n\t"a":"b"\r\n} //EOF', options), '{\r\n\t"a":"b"\r\n} ')
end)
it("handles weird escaping", function()
eq(
json.json_strip_comments [[{"x":"x \"sed -e \\\"s/^.\\\\{46\\\\}T//\\\" -e \\\"s/#033/\\\\x1b/g\\\"\""}]],
[[{"x":"x \"sed -e \\\"s/^.\\\\{46\\\\}T//\\\" -e \\\"s/#033/\\\\x1b/g\\\"\""}]]
)
end)
it("trailing commas", function()
eq(json.json_strip_comments '{"a":"b",}', '{"a":"b"}')
eq(json.json_strip_comments '{"a":{"b":"c",},}', '{"a":{"b":"c"}}')
eq(json.json_strip_comments '{"a":["b","c",],}', '{"a":["b","c"]}')
end)
it("trailing commas - ignored in strings and comments", function()
eq(json.json_strip_comments '{"a":"b,}"}', '{"a":"b,}"}')
end)
it("trailing commas - left when disabled in options", function()
local options = { trailing_commas = true }
eq(json.json_strip_comments('{"a":"b",}', options), '{"a":"b",}')
eq(json.json_strip_comments('{"a":{"b":"c",},}', options), '{"a":{"b":"c",},}')
eq(json.json_strip_comments('{"a":["b","c",],}', options), '{"a":["b","c",],}')
end)
end)

View file

@ -0,0 +1,762 @@
local Path = require "plenary.path"
local path = Path.path
local compat = require "plenary.compat"
describe("Path", function()
it("should find valid files", function()
local p = Path:new "README.md"
assert(p.filename == "README.md", p.filename)
assert.are.same(p.filename, "README.md")
end)
describe("absolute", function()
it(".absolute()", function()
local p = Path:new { "README.md", sep = "/" }
assert.are.same(p:absolute(), vim.fn.fnamemodify("README.md", ":p"))
end)
it("can determine absolute paths", function()
local p = Path:new { "/home/asdfasdf/", sep = "/" }
assert(p:is_absolute(), "Is absolute")
assert(p:absolute() == p.filename)
end)
it("can determine non absolute paths", function()
local p = Path:new { "./home/tj/", sep = "/" }
assert(not p:is_absolute(), "Is absolute")
end)
it("will normalize the path", function()
local p = Path:new { "lua", "..", "README.md", sep = "/" }
assert.are.same(p:absolute(), vim.fn.fnamemodify("README.md", ":p"))
end)
end)
it("can join paths by constructor or join path", function()
assert.are.same(Path:new("lua", "plenary"), Path:new("lua"):joinpath "plenary")
end)
it("can join paths with /", function()
assert.are.same(Path:new("lua", "plenary"), Path:new "lua" / "plenary")
end)
it("can join paths with paths", function()
assert.are.same(Path:new("lua", "plenary"), Path:new("lua", Path:new "plenary"))
end)
it("inserts slashes", function()
assert.are.same("lua" .. path.sep .. "plenary", Path:new("lua", "plenary").filename)
end)
describe(".exists()", function()
it("finds files that exist", function()
assert.are.same(true, Path:new("README.md"):exists())
end)
it("returns false for files that do not exist", function()
assert.are.same(false, Path:new("asdf.md"):exists())
end)
end)
describe(".is_dir()", function()
it("should find directories that exist", function()
assert.are.same(true, Path:new("lua"):is_dir())
end)
it("should return false when the directory does not exist", function()
assert.are.same(false, Path:new("asdf"):is_dir())
end)
it("should not show files as directories", function()
assert.are.same(false, Path:new("README.md"):is_dir())
end)
end)
describe(".is_file()", function()
it("should not allow directories", function()
assert.are.same(true, not Path:new("lua"):is_file())
end)
it("should return false when the file does not exist", function()
assert.are.same(true, not Path:new("asdf"):is_file())
end)
it("should show files as file", function()
assert.are.same(true, Path:new("README.md"):is_file())
end)
end)
describe(":new", function()
it("can be called with or without colon", function()
-- This will work, cause we used a colon
local with_colon = Path:new "lua"
local no_colon = Path.new "lua"
assert.are.same(with_colon, no_colon)
end)
end)
describe(":make_relative", function()
it("can take absolute paths and make them relative to the cwd", function()
local p = Path:new { "lua", "plenary", "path.lua" }
local absolute = vim.loop.cwd() .. path.sep .. p.filename
local relative = Path:new(absolute):make_relative()
assert.are.same(relative, p.filename)
end)
it("can take absolute paths and make them relative to a given path", function()
local root = path.sep == "\\" and "c:\\" or "/"
local r = Path:new { root, "home", "prime" }
local p = Path:new { "aoeu", "agen.lua" }
local absolute = r.filename .. path.sep .. p.filename
local relative = Path:new(absolute):make_relative(r.filename)
assert.are.same(relative, p.filename)
end)
it("can take double separator absolute paths and make them relative to the cwd", function()
local p = Path:new { "lua", "plenary", "path.lua" }
local absolute = vim.loop.cwd() .. path.sep .. path.sep .. p.filename
local relative = Path:new(absolute):make_relative()
assert.are.same(relative, p.filename)
end)
it("can take double separator absolute paths and make them relative to a given path", function()
local root = path.sep == "\\" and "c:\\" or "/"
local r = Path:new { root, "home", "prime" }
local p = Path:new { "aoeu", "agen.lua" }
local absolute = r.filename .. path.sep .. path.sep .. p.filename
local relative = Path:new(absolute):make_relative(r.filename)
assert.are.same(relative, p.filename)
end)
it("can take absolute paths and make them relative to a given path with trailing separator", function()
local root = path.sep == "\\" and "c:\\" or "/"
local r = Path:new { root, "home", "prime" }
local p = Path:new { "aoeu", "agen.lua" }
local absolute = r.filename .. path.sep .. p.filename
local relative = Path:new(absolute):make_relative(r.filename .. path.sep)
assert.are.same(relative, p.filename)
end)
it("can take absolute paths and make them relative to the root directory", function()
local root = path.sep == "\\" and "c:\\" or "/"
local p = Path:new { "home", "prime", "aoeu", "agen.lua" }
local absolute = root .. p.filename
local relative = Path:new(absolute):make_relative(root)
assert.are.same(relative, p.filename)
end)
it("can take absolute paths and make them relative to themselves", function()
local root = path.sep == "\\" and "c:\\" or "/"
local p = Path:new { root, "home", "prime", "aoeu", "agen.lua" }
local relative = Path:new(p.filename):make_relative(p.filename)
assert.are.same(relative, ".")
end)
it("should not truncate if path separator is not present after cwd", function()
local cwd = "tmp" .. path.sep .. "foo"
local p = Path:new { "tmp", "foo_bar", "fileb.lua" }
local relative = Path:new(p.filename):make_relative(cwd)
assert.are.same(p.filename, relative)
end)
it("should not truncate if path separator is not present after cwd and cwd ends in path sep", function()
local cwd = "tmp" .. path.sep .. "foo" .. path.sep
local p = Path:new { "tmp", "foo_bar", "fileb.lua" }
local relative = Path:new(p.filename):make_relative(cwd)
assert.are.same(p.filename, relative)
end)
end)
describe(":normalize", function()
it("can take path that has one character directories", function()
local orig = "/home/j/./p//path.lua"
local final = Path:new(orig):normalize()
assert.are.same(final, "/home/j/p/path.lua")
end)
it("can take paths with double separators change them to single separators", function()
local orig = "/lua//plenary/path.lua"
local final = Path:new(orig):normalize()
assert.are.same(final, "/lua/plenary/path.lua")
end)
-- this may be redundant since normalize just calls make_relative which is tested above
it("can take absolute paths with double seps" .. "and make them relative with single seps", function()
local orig = "/lua//plenary/path.lua"
local final = Path:new(orig):normalize()
assert.are.same(final, "/lua/plenary/path.lua")
end)
it("can remove the .. in paths", function()
local orig = "/lua//plenary/path.lua/foo/bar/../.."
local final = Path:new(orig):normalize()
assert.are.same(final, "/lua/plenary/path.lua")
end)
it("can normalize relative paths", function()
assert.are.same(Path:new("lua/plenary/path.lua"):normalize(), "lua/plenary/path.lua")
end)
it("can normalize relative paths containing ..", function()
assert.are.same(Path:new("lua/plenary/path.lua/../path.lua"):normalize(), "lua/plenary/path.lua")
end)
it("can normalize relative paths with initial ..", function()
local p = Path:new "../lua/plenary/path.lua"
p._cwd = "/tmp/lua"
assert.are.same("lua/plenary/path.lua", p:normalize())
end)
it("can normalize relative paths to absolute when initial .. count matches cwd parts", function()
local p = Path:new "../../tmp/lua/plenary/path.lua"
p._cwd = "/tmp/lua"
assert.are.same("/tmp/lua/plenary/path.lua", p:normalize())
end)
it("can normalize ~ when file is within home directory (trailing slash)", function()
local home = "/home/test/"
local p = Path:new { home, "./test_file" }
p.path.home = home
p._cwd = "/tmp/lua"
assert.are.same("~/test_file", p:normalize())
end)
it("can normalize ~ when file is within home directory (no trailing slash)", function()
local home = "/home/test"
local p = Path:new { home, "./test_file" }
p.path.home = home
p._cwd = "/tmp/lua"
assert.are.same("~/test_file", p:normalize())
end)
it("handles usernames with a dash at the end", function()
local home = "/home/mattr-"
local p = Path:new { home, "test_file" }
p.path.home = home
p._cwd = "/tmp/lua"
assert.are.same("~/test_file", p:normalize())
end)
it("handles filenames with the same prefix as the home directory", function()
local p = Path:new "/home/test.old/test_file"
p.path.home = "/home/test"
assert.are.same("/home/test.old/test_file", p:normalize())
end)
end)
describe(":shorten", function()
it("can shorten a path", function()
local long_path = "/this/is/a/long/path"
local short_path = Path:new(long_path):shorten()
assert.are.same(short_path, "/t/i/a/l/path")
end)
it("can shorten a path's components to a given length", function()
local long_path = "/this/is/a/long/path"
local short_path = Path:new(long_path):shorten(2)
assert.are.same(short_path, "/th/is/a/lo/path")
-- without the leading /
long_path = "this/is/a/long/path"
short_path = Path:new(long_path):shorten(3)
assert.are.same(short_path, "thi/is/a/lon/path")
-- where len is greater than the length of the final component
long_path = "this/is/an/extremely/long/path"
short_path = Path:new(long_path):shorten(5)
assert.are.same(short_path, "this/is/an/extre/long/path")
end)
it("can shorten a path's components when excluding parts", function()
local long_path = "/this/is/a/long/path"
local short_path = Path:new(long_path):shorten(nil, { 1, -1 })
assert.are.same(short_path, "/this/i/a/l/path")
-- without the leading /
long_path = "this/is/a/long/path"
short_path = Path:new(long_path):shorten(nil, { 1, -1 })
assert.are.same(short_path, "this/i/a/l/path")
-- where excluding positions greater than the number of parts
long_path = "this/is/an/extremely/long/path"
short_path = Path:new(long_path):shorten(nil, { 2, 4, 6, 8 })
assert.are.same(short_path, "t/is/a/extremely/l/path")
-- where excluding positions less than the negation of the number of parts
long_path = "this/is/an/extremely/long/path"
short_path = Path:new(long_path):shorten(nil, { -2, -4, -6, -8 })
assert.are.same(short_path, "this/i/an/e/long/p")
end)
it("can shorten a path's components to a given length and exclude positions", function()
local long_path = "/this/is/a/long/path"
local short_path = Path:new(long_path):shorten(2, { 1, -1 })
assert.are.same(short_path, "/this/is/a/lo/path")
long_path = "this/is/a/long/path"
short_path = Path:new(long_path):shorten(3, { 2, -2 })
assert.are.same(short_path, "thi/is/a/long/pat")
long_path = "this/is/an/extremely/long/path"
short_path = Path:new(long_path):shorten(5, { 3, -3 })
assert.are.same(short_path, "this/is/an/extremely/long/path")
end)
end)
describe("mkdir / rmdir", function()
it("can create and delete directories", function()
local p = Path:new "_dir_not_exist"
p:rmdir()
assert(not p:exists(), "After rmdir, it should not exist")
p:mkdir()
assert(p:exists())
p:rmdir()
assert(not p:exists())
end)
it("fails when exists_ok is false", function()
local p = Path:new "lua"
assert(not pcall(p.mkdir, p, { exists_ok = false }))
end)
it("fails when parents is not passed", function()
local p = Path:new("impossible", "dir")
assert(not pcall(p.mkdir, p, { parents = false }))
assert(not p:exists())
end)
it("can create nested directories", function()
local p = Path:new("impossible", "dir")
assert(pcall(p.mkdir, p, { parents = true }))
assert(p:exists())
p:rmdir()
Path:new("impossible"):rmdir()
assert(not p:exists())
assert(not Path:new("impossible"):exists())
end)
end)
describe("touch", function()
it("can create and delete new files", function()
local p = Path:new "test_file.lua"
assert(pcall(p.touch, p))
assert(p:exists())
p:rm()
assert(not p:exists())
end)
it("does not effect already created files but updates last access", function()
local p = Path:new "README.md"
local last_atime = p:_stat().atime.sec
local last_mtime = p:_stat().mtime.sec
local lines = p:readlines()
assert(pcall(p.touch, p))
print(p:_stat().atime.sec > last_atime)
print(p:_stat().mtime.sec > last_mtime)
assert(p:exists())
assert.are.same(lines, p:readlines())
end)
it("does not create dirs if nested in none existing dirs and parents not set", function()
local p = Path:new { "nested", "nested2", "test_file.lua" }
assert(not pcall(p.touch, p, { parents = false }))
assert(not p:exists())
end)
it("does create dirs if nested in none existing dirs", function()
local p1 = Path:new { "nested", "nested2", "test_file.lua" }
local p2 = Path:new { "nested", "asdf", ".hidden" }
local d1 = Path:new { "nested", "dir", ".hidden" }
assert(pcall(p1.touch, p1, { parents = true }))
assert(pcall(p2.touch, p2, { parents = true }))
assert(pcall(d1.mkdir, d1, { parents = true }))
assert(p1:exists())
assert(p2:exists())
assert(d1:exists())
Path:new({ "nested" }):rm { recursive = true }
assert(not p1:exists())
assert(not p2:exists())
assert(not d1:exists())
assert(not Path:new({ "nested" }):exists())
end)
end)
describe("rename", function()
it("can rename a file", function()
local p = Path:new "a_random_filename.lua"
assert(pcall(p.touch, p))
assert(p:exists())
assert(pcall(p.rename, p, { new_name = "not_a_random_filename.lua" }))
assert.are.same("not_a_random_filename.lua", p.filename)
p:rm()
end)
it("can handle an invalid filename", function()
local p = Path:new "some_random_filename.lua"
assert(pcall(p.touch, p))
assert(p:exists())
assert(not pcall(p.rename, p, { new_name = "" }))
assert(not pcall(p.rename, p))
assert.are.same("some_random_filename.lua", p.filename)
p:rm()
end)
it("can move to parent dir", function()
local p = Path:new "some_random_filename.lua"
assert(pcall(p.touch, p))
assert(p:exists())
assert(pcall(p.rename, p, { new_name = "../some_random_filename.lua" }))
assert.are.same(vim.loop.fs_realpath(Path:new("../some_random_filename.lua"):absolute()), p:absolute())
p:rm()
end)
it("cannot rename to an existing filename", function()
local p1 = Path:new "a_random_filename.lua"
local p2 = Path:new "not_a_random_filename.lua"
assert(pcall(p1.touch, p1))
assert(pcall(p2.touch, p2))
assert(p1:exists())
assert(p2:exists())
assert(not pcall(p1.rename, p1, { new_name = "not_a_random_filename.lua" }))
assert.are.same(p1.filename, "a_random_filename.lua")
p1:rm()
p2:rm()
end)
end)
describe("copy", function()
it("can copy a file", function()
local p1 = Path:new "a_random_filename.rs"
local p2 = Path:new "not_a_random_filename.rs"
assert(pcall(p1.touch, p1))
assert(p1:exists())
assert(pcall(p1.copy, p1, { destination = "not_a_random_filename.rs" }))
assert.are.same(p1.filename, "a_random_filename.rs")
assert.are.same(p2.filename, "not_a_random_filename.rs")
p1:rm()
p2:rm()
end)
it("can copy to parent dir", function()
local p = Path:new "some_random_filename.lua"
assert(pcall(p.touch, p))
assert(p:exists())
assert(pcall(p.copy, p, { destination = "../some_random_filename.lua" }))
assert(pcall(p.exists, p))
p:rm()
Path:new(vim.loop.fs_realpath "../some_random_filename.lua"):rm()
end)
it("cannot copy an existing file if override false", function()
local p1 = Path:new "a_random_filename.rs"
local p2 = Path:new "not_a_random_filename.rs"
assert(pcall(p1.touch, p1))
assert(pcall(p2.touch, p2))
assert(p1:exists())
assert(p2:exists())
assert(pcall(p1.copy, p1, { destination = "not_a_random_filename.rs", override = false }))
assert.are.same(p1.filename, "a_random_filename.rs")
assert.are.same(p2.filename, "not_a_random_filename.rs")
p1:rm()
p2:rm()
end)
it("fails when copying folders non-recursively", function()
local src_dir = Path:new "src"
src_dir:mkdir()
src_dir:joinpath("file1.lua"):touch()
local trg_dir = Path:new "trg"
local status = xpcall(function()
src_dir:copy { destination = trg_dir, recursive = false }
end, function() end)
-- failed as intended
assert(status == false)
src_dir:rm { recursive = true }
end)
it("can copy directories recursively", function()
-- vim.tbl_flatten doesn't work here as copy doesn't return a list
local flatten
flatten = function(ret, t)
for _, v in pairs(t) do
if type(v) == "table" then
flatten(ret, v)
else
table.insert(ret, v)
end
end
end
-- setup directories
local src_dir = Path:new "src"
local trg_dir = Path:new "trg"
src_dir:mkdir()
-- set up sub directory paths for creation and testing
local sub_dirs = { "sub_dir1", "sub_dir1/sub_dir2" }
local src_dirs = { src_dir }
local trg_dirs = { trg_dir }
-- {src, trg}_dirs is a table with all directory levels by {src, trg}
for _, dir in ipairs(sub_dirs) do
table.insert(src_dirs, src_dir:joinpath(dir))
table.insert(trg_dirs, trg_dir:joinpath(dir))
end
-- generate {file}_{level}.lua on every directory level in src
-- src
-- ├── file1_1.lua
-- ├── file2_1.lua
-- ├── .file3_1.lua
-- └── sub_dir1
-- ├── file1_2.lua
-- ├── file2_2.lua
-- ├── .file3_2.lua
-- └── sub_dir2
-- ├── file1_3.lua
-- ├── file2_3.lua
-- └── .file3_3.lua
local files = { "file1", "file2", ".file3" }
for _, file in ipairs(files) do
for level, dir in ipairs(src_dirs) do
local p = dir:joinpath(file .. "_" .. level .. ".lua")
assert(pcall(p.touch, p, { parents = true, exists_ok = true }))
assert(p:exists())
end
end
for _, hidden in ipairs { true, false } do
-- override = `false` should NOT copy as it was copied beforehand
for _, override in ipairs { true, false } do
local success = src_dir:copy { destination = trg_dir, recursive = true, override = override, hidden = hidden }
-- the files are already created because we iterate first with `override=true`
-- hence, we test here that no file ops have been committed: any value in tbl of tbls should be false
if not override then
local file_ops = {}
flatten(file_ops, success)
-- 3 layers with at at least 2 and at most 3 files (`hidden = true`)
local num_files = not hidden and 6 or 9
assert(#file_ops == num_files)
for _, op in ipairs(file_ops) do
assert(op == false)
end
else
for _, file in ipairs(files) do
for level, dir in ipairs(trg_dirs) do
local p = dir:joinpath(file .. "_" .. level .. ".lua")
-- file 3 is hidden
if not (file == files[3]) then
assert(p:exists())
else
assert(p:exists() == hidden)
end
end
end
end
-- only clean up once we tested that we dont want to copy
-- if `override=true`
if not override then
trg_dir:rm { recursive = true }
end
end
end
src_dir:rm { recursive = true }
end)
end)
describe("parents", function()
it("should extract the ancestors of the path", function()
local p = Path:new(vim.loop.cwd())
local parents = p:parents()
assert(compat.islist(parents))
for _, parent in pairs(parents) do
assert.are.same(type(parent), "string")
end
end)
it("should return itself if it corresponds to path.root", function()
local p = Path:new(Path.path.root(vim.loop.cwd()))
assert.are.same(p:parent(), p)
end)
end)
describe("read parts", function()
it("should read head of file", function()
local p = Path:new "LICENSE"
local data = p:head()
local should = [[MIT License
Copyright (c) 2020 TJ DeVries
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:]]
assert.are.same(should, data)
end)
it("should read the first line of file", function()
local p = Path:new "LICENSE"
local data = p:head(1)
local should = [[MIT License]]
assert.are.same(should, data)
end)
it("head should max read whole file", function()
local p = Path:new "LICENSE"
local data = p:head(1000)
local should = [[MIT License
Copyright (c) 2020 TJ DeVries
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.]]
assert.are.same(should, data)
end)
it("should read tail of file", function()
local p = Path:new "LICENSE"
local data = p:tail()
local should = [[The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.]]
assert.are.same(should, data)
end)
it("should read the last line of file", function()
local p = Path:new "LICENSE"
local data = p:tail(1)
local should = [[SOFTWARE.]]
assert.are.same(should, data)
end)
it("tail should max read whole file", function()
local p = Path:new "LICENSE"
local data = p:tail(1000)
local should = [[MIT License
Copyright (c) 2020 TJ DeVries
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.]]
assert.are.same(should, data)
end)
end)
describe("readbyterange", function()
it("should read bytes at given offset", function()
local p = Path:new "LICENSE"
local data = p:readbyterange(13, 10)
local should = "Copyright "
assert.are.same(should, data)
end)
it("supports negative offset", function()
local p = Path:new "LICENSE"
local data = p:readbyterange(-10, 10)
local should = "SOFTWARE.\n"
assert.are.same(should, data)
end)
end)
describe(":find_upwards", function()
it("finds files that exist", function()
local p = Path:new(debug.getinfo(1, "S").source:sub(2))
local found = p:find_upwards "README.md"
assert.are.same(found:absolute(), Path:new("README.md"):absolute())
end)
it("finds files that exist at the root", function()
local p = Path:new(debug.getinfo(1, "S").source:sub(2))
-- Temporarily set path.root to the root of this repository
local root = p.path.root
p.path.root = function(_)
return p:parent():parent():parent().filename
end
local found = p:find_upwards "README.md"
assert.are.same(found:absolute(), Path:new("README.md"):absolute())
p.path.root = root
end)
it("returns nil if no file is found", function()
local p = Path:new(debug.getinfo(1, "S").source:sub(2))
local found = p:find_upwards "MISSINGNO.md"
assert.are.same(found, nil)
end)
end)
end)
-- function TestPath:testIsDir()
-- end
-- function TestPath:testCanBeCalledWithoutColon()
-- end
-- -- @sideeffect
-- function TestPath:testMkdir()
-- end

View file

@ -0,0 +1,42 @@
local scandir = require "plenary.scandir"
local Path = require "plenary.path"
local eq = assert.are.same
describe("plenary.popup", function()
local allowed_imports = {
["plenary.window"] = true,
["plenary.window.border"] = true,
}
local matches_any_import = function(line)
local matched = string.match(line, [[require."(.*)"]])
if matched and not vim.startswith(matched, "plenary.popup") then
if not allowed_imports[matched] then
return true, string.format("Not an allowed import for popup: %s. Line: %s", matched, line)
end
end
return false, nil
end
-- Tests to make sure that we're matching both types of requires
it("should match these kinds of patterns", function()
eq(true, matches_any_import [[local x = require "plenary.other"]])
eq(true, matches_any_import [[local x = require("plenary.module").something]])
end)
it("must not require anything other than Window and Border from plenary", function()
local result = scandir.scan_dir("./lua/plenary/popup", { depth = 1 })
for _, file in ipairs(result) do
local popup_file = Path:new(file)
local lines = popup_file:readlines()
for _, line in ipairs(lines) do
local matches, msg = matches_any_import(line)
eq(false, matches, msg)
end
end
end)
end)

View file

@ -0,0 +1,162 @@
local popup = require "plenary.popup"
local eq = assert.are.same
describe("plenary.popup", function()
before_each(function()
vim.cmd [[highlight PopupColor1 ctermbg=lightblue guibg=lightblue]]
vim.cmd [[highlight PopupColor2 ctermbg=lightcyan guibg=lightcyan]]
end)
-- TODO: Probably want to clear all the popups between iterations
-- after_each(function() end)
it("can create a very simple window", function()
local win_id = popup.create("hello there", {
line = 1,
col = 1,
width = 20,
})
local win_config = vim.api.nvim_win_get_config(win_id)
eq(20, win_config.width)
end)
it("can create a very simple window after 'set nomodifiable'", function()
vim.o.modifiable = false
local win_id = popup.create("hello there", {
line = 1,
col = 1,
width = 20,
})
local win_config = vim.api.nvim_win_get_config(win_id)
eq(20, win_config.width)
vim.o.modifiable = true
end)
it("can apply a highlight", function()
local win_id = popup.create("hello there", {
highlight = "PopupColor1",
})
eq("Normal:PopupColor1,EndOfBuffer:PopupColor1", vim.api.nvim_win_get_option(win_id, "winhl"))
end)
it("can create a border", function()
local win_id, config = popup.create("hello border", {
line = 2,
col = 3,
border = {},
})
eq(true, vim.api.nvim_win_is_valid(win_id))
local border_id = config.border.win_id
assert(border_id, "Has a border win id")
eq(true, vim.api.nvim_win_is_valid(border_id))
end)
it("can apply a border highlight", function()
local _, opts = popup.create("hello there", {
border = true,
borderhighlight = "PopupColor2",
})
local border_win_id = opts.border.win_id
eq("Normal:PopupColor2", vim.api.nvim_win_get_option(border_win_id, "winhl"))
end)
it("can ignore border highlight with no border", function()
local _ = popup.create("hello there", {
border = false,
borderhighlight = "PopupColor3",
})
end)
it("can do basic padding", function()
local win_id = popup.create("12345", {
line = 1,
col = 1,
padding = {},
})
local bufnr = vim.api.nvim_win_get_buf(win_id)
eq({ "", " 12345 ", "" }, vim.api.nvim_buf_get_lines(bufnr, 0, -1, false))
end)
it("can do padding and border", function()
local win_id, config = popup.create("hello border", {
line = 2,
col = 2,
border = {},
padding = {},
})
local bufnr = vim.api.nvim_win_get_buf(win_id)
eq({ "", " hello border ", "" }, vim.api.nvim_buf_get_lines(bufnr, 0, -1, false))
local border_id = config.border.win_id
assert(border_id, "Has a border win id")
eq(true, vim.api.nvim_win_is_valid(border_id))
end)
describe("borderchars", function()
local test_border = function(name, borderchars, expected)
it(name, function()
local _, config = popup.create("all the plus signs", {
line = 8,
col = 55,
padding = { 0, 3, 0, 3 },
borderchars = borderchars,
})
local border_id = config.border.win_id
local border_bufnr = vim.api.nvim_win_get_buf(border_id)
eq(expected, vim.api.nvim_buf_get_lines(border_bufnr, 0, -1, false))
end)
end
test_border("can support multiple border patterns", { "+" }, {
"++++++++++++++++++++++++++",
"+ +",
"++++++++++++++++++++++++++",
})
test_border("can support multiple patterns inside the borderchars", { "-", "+" }, {
"+------------------------+",
"- -",
"+------------------------+",
})
end)
describe("what", function()
it("can be an existing bufnr", function()
local bufnr = vim.api.nvim_create_buf(false, false)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { "pass bufnr 1", "pass bufnr 2" })
local win_id = popup.create(bufnr, {
line = 8,
col = 55,
minwidth = 20,
})
eq(bufnr, vim.api.nvim_win_get_buf(win_id))
eq({ "pass bufnr 1", "pass bufnr 2" }, vim.api.nvim_buf_get_lines(bufnr, 0, -1, false))
end)
end)
describe("cursor", function()
pending("not yet tested", function()
popup.create({ "option 1", "option 2" }, {
line = "cursor+2",
col = "cursor+2",
border = { 1, 1, 1, 1 },
enter = true,
cursorline = true,
callback = function(win_id, sel)
print(sel)
end,
})
end)
end)
end)

View file

@ -0,0 +1,60 @@
local List = require "plenary.collections.py_list"
local Iter = require "plenary.iterators"
describe("List", function()
it("should detect whether a value is an instance of List", function()
local l = List { 1, 2 }
local n = 42
assert(List.is_list(l))
assert(not List.is_list(n))
end)
it("should be equal if all elements are equal", function()
local l1 = List { 1, 2, 3 }
local l2 = List { 1, 2, 3 }
local l3 = List { 4, 5, 6 }
assert.are.equal(l1, l2)
assert.are_not.equal(l1, l3)
end)
it("can be concatenated to other list-like tables", function()
local l1 = List { 1, 2, 3 } .. { 4 }
assert.are.equal(l1, List { 1, 2, 3, 4 })
end)
it("can create a copy of itself with equal elements", function()
local l1 = List { 1, 2, 3 }
local l2 = l1:copy()
assert.are.equal(l1, l2)
end)
it("can create a slice between two indices", function()
local l1 = List { 1, 2, 3, 4 }
local l2 = l1:slice(2, 4)
assert.are.equal(l2, List { 2, 3, 4 })
end)
it("can reverse itself in place", function()
local l = List { 1, 2, 3, 4 }
l:reverse()
assert.are.equal(l, List { 4, 3, 2, 1 })
end)
it("can push elements to itself", function()
local l = List { 1, 2, 3 }
l:push(4)
assert.are.equal(l, List { 1, 2, 3, 4 })
end)
it("can pop the n-th element from itself (last one by default)", function()
local l = List { 1, 2, 3, 4 }
local n = l:pop()
assert.are.equal(l, List { 1, 2, 3 })
assert.are.equal(n, 4)
end)
it("can create a list from an iterable", function()
local l = List.from_iter(Iter.range(5, 10))
assert.are.equal(l, List { 5, 6, 7, 8, 9, 10 })
end)
it("can be partitioned based on a predicate", function()
local l = List.from_iter(Iter.range(1, 10))
local evens, odds = l:iter():partition(function(e)
return e % 2 == 0
end)
assert.are.equal(evens, List { 2, 4, 6, 8, 10 })
assert.are.equal(odds, List { 1, 3, 5, 7, 9 })
end)
end)

View file

@ -0,0 +1,33 @@
local rotate = require("plenary.vararg").rotate
local eq = function(a, b)
assert.is["true"](vim.deep_equal(a, b), true)
end
describe("rotate", function()
it("should return as many values, as the first argument", function()
local args = {}
for _ = 0, 20 do
local n = select("#", unpack(args))
assert.is.equal(n, select("#", rotate(n, unpack(args))))
args[#args + 1] = n
end
end)
it("should rotate varargs", function()
eq({ rotate(3, 1, 2, 3) }, { 2, 3, 1 })
eq({ rotate(9, 1, 2, 3, 4, 5, 6, 7, 8, 9) }, { 2, 3, 4, 5, 6, 7, 8, 9, 1 })
end)
it("should rotate zero", function()
assert.is.equal(0, select("#", rotate(0)))
end)
it("should rotate none", function()
assert.is.equal(0, select("#", rotate()))
end)
it("should rotate one", function()
eq({ rotate(1, 1) }, { 1 })
end)
end)

View file

@ -0,0 +1,249 @@
local scan = require "plenary.scandir"
local mock = require "luassert.mock"
local stub = require "luassert.stub"
local eq = assert.are.same
local contains = function(tbl, str)
for _, v in ipairs(tbl) do
if v == str then
return true
end
end
return false
end
local contains_match = function(tbl, str)
for _, v in ipairs(tbl) do
if v:match(str) then
return true
end
end
return false
end
describe("scandir", function()
describe("can list all files recursive", function()
it("with cwd", function()
local dirs = scan.scan_dir "."
eq("table", type(dirs))
eq(true, contains(dirs, "./README.md"))
eq(true, contains(dirs, "./LICENSE"))
eq(true, contains(dirs, "./lua/plenary/job.lua"))
eq(false, contains(dirs, "./asdf/asdf/adsf.lua"))
end)
it("and callback gets called for each entry", function()
local count = 0
local dirs = scan.scan_dir(".", {
on_insert = function()
count = count + 1
end,
})
eq("table", type(dirs))
eq(true, contains(dirs, "./README.md"))
eq(true, contains(dirs, "./LICENSE"))
eq(true, contains(dirs, "./lua/plenary/job.lua"))
eq(false, contains(dirs, "./asdf/asdf/adsf.lua"))
eq(count, #dirs)
end)
it("with multiple paths", function()
local dirs = scan.scan_dir { "./lua", "./tests" }
eq("table", type(dirs))
eq(true, contains(dirs, "./lua/say.lua"))
eq(true, contains(dirs, "./lua/plenary/job.lua"))
eq(true, contains(dirs, "./tests/plenary/scandir_spec.lua"))
eq(false, contains(dirs, "./asdf/asdf/adsf.lua"))
end)
it("with hidden files", function()
local dirs = scan.scan_dir(".", { hidden = true })
eq("table", type(dirs))
eq(true, contains(dirs, "./README.md"))
eq(true, contains(dirs, "./lua/plenary/job.lua"))
eq(true, contains(dirs, "./.gitignore"))
eq(false, contains(dirs, "./asdf/asdf/adsf.lua"))
end)
it("with add directories", function()
local dirs = scan.scan_dir(".", { add_dirs = true })
eq("table", type(dirs))
eq(true, contains(dirs, "./README.md"))
eq(true, contains(dirs, "./lua/plenary/job.lua"))
eq(true, contains(dirs, "./lua"))
eq(true, contains(dirs, "./tests"))
eq(false, contains(dirs, "./asdf/asdf/adsf.lua"))
end)
it("with only directories", function()
local dirs = scan.scan_dir(".", { only_dirs = true })
eq("table", type(dirs))
eq(false, contains(dirs, "./README.md"))
eq(false, contains(dirs, "./lua/plenary/job.lua"))
eq(true, contains(dirs, "./lua"))
eq(true, contains(dirs, "./tests"))
eq(false, contains(dirs, "./asdf/asdf/adsf.lua"))
end)
it("until depth 1 is reached", function()
local dirs = scan.scan_dir(".", { depth = 1 })
eq("table", type(dirs))
eq(true, contains(dirs, "./README.md"))
eq(false, contains(dirs, "./lua"))
eq(false, contains(dirs, "./lua/say.lua"))
eq(false, contains(dirs, "./lua/plenary/job.lua"))
eq(false, contains(dirs, "./asdf/asdf/adsf.lua"))
end)
it("until depth 1 is reached and with directories", function()
local dirs = scan.scan_dir(".", { depth = 1, add_dirs = true })
eq("table", type(dirs))
eq(true, contains(dirs, "./README.md"))
eq(true, contains(dirs, "./lua"))
eq(false, contains(dirs, "./lua/say.lua"))
eq(false, contains(dirs, "./lua/plenary/job.lua"))
eq(false, contains(dirs, "./asdf/asdf/adsf.lua"))
end)
it("until depth 2 is reached", function()
local dirs = scan.scan_dir(".", { depth = 2 })
eq("table", type(dirs))
eq(true, contains(dirs, "./README.md"))
eq(true, contains(dirs, "./lua/say.lua"))
eq(false, contains(dirs, "./lua/plenary/job.lua"))
eq(false, contains(dirs, "./asdf/asdf/adsf.lua"))
end)
it("with respect_gitignore", function()
vim.cmd ":silent !touch lua/test.so"
local dirs = scan.scan_dir(".", { respect_gitignore = true })
vim.cmd ":silent !rm lua/test.so"
eq("table", type(dirs))
eq(true, contains(dirs, "./README.md"))
eq(true, contains(dirs, "./LICENSE"))
eq(true, contains(dirs, "./lua/plenary/job.lua"))
eq(false, contains(dirs, "./lua/test.so"))
eq(false, contains(dirs, "./asdf/asdf/adsf.lua"))
end)
it("with search pattern", function()
local dirs = scan.scan_dir(".", { search_pattern = "filetype" })
eq("table", type(dirs))
eq(true, contains(dirs, "./scripts/update_filetypes_from_github.lua"))
eq(true, contains(dirs, "./lua/plenary/filetype.lua"))
eq(true, contains(dirs, "./tests/plenary/filetype_spec.lua"))
eq(true, contains(dirs, "./data/plenary/filetypes/base.lua"))
eq(true, contains(dirs, "./data/plenary/filetypes/builtin.lua"))
eq(false, contains(dirs, "./README.md"))
end)
it("with callback search pattern", function()
local dirs = scan.scan_dir(".", {
search_pattern = function(entry)
return entry:match "filetype"
end,
})
eq("table", type(dirs))
eq(true, contains(dirs, "./scripts/update_filetypes_from_github.lua"))
eq(true, contains(dirs, "./lua/plenary/filetype.lua"))
eq(true, contains(dirs, "./tests/plenary/filetype_spec.lua"))
eq(true, contains(dirs, "./data/plenary/filetypes/base.lua"))
eq(true, contains(dirs, "./data/plenary/filetypes/builtin.lua"))
eq(false, contains(dirs, "./README.md"))
end)
end)
describe("gitignore", function()
local Path = require "plenary.path"
local mock_path, mock_gitignore
before_each(function()
mock_path = {
exists = stub.new().returns(true),
iter = function()
local i = 0
local n = table.getn(mock_gitignore)
return function()
i = i + 1
if i <= n then
return mock_gitignore[i]
end
end
end,
}
Path.new = stub.new().returns(mock_path)
end)
after_each(function()
Path.new:revert()
end)
describe("ignores path", function()
it("when path matches pattern exactly", function()
mock_gitignore = { "ignored.txt" }
local should_add = scan.__make_gitignore { "path" }
eq(false, should_add({ "path" }, "./path/ignored.txt"))
end)
it("when path matches * pattern", function()
mock_gitignore = { "*.txt" }
local should_add = scan.__make_gitignore { "path" }
eq(false, should_add({ "path" }, "./path/dir/ignored.txt"))
end)
it("when path matches leading ** pattern", function()
mock_gitignore = { "**/ignored.txt" }
local should_add = scan.__make_gitignore { "path" }
eq(false, should_add({ "path" }, "./path/dir/subdir/ignored.txt"))
end)
it("when path matches trailing ** pattern", function()
mock_gitignore = { "/dir/**" }
local should_add = scan.__make_gitignore { "path" }
eq(false, should_add({ "path" }, "./path/dir/subdir/ignored.txt"))
end)
it("when path matches ? pattern", function()
mock_gitignore = { "ignore?.txt" }
local should_add = scan.__make_gitignore { "path" }
eq(false, should_add({ "path" }, "./path/ignored.txt"))
end)
end)
describe("does not ignore path", function()
it("when path does not match", function()
mock_gitignore = { "ignored.txt" }
local should_add = scan.__make_gitignore { "path" }
eq(true, should_add({ "path" }, "./path/ok.txt"))
end)
it("when path is negated", function()
mock_gitignore = { "*.txt", "!ok.txt" }
local should_add = scan.__make_gitignore { "path" }
eq(true, should_add({ "path" }, "./path/ok.txt"))
end)
end)
end)
describe("ls", function()
it("works for cwd", function()
local dirs = scan.ls "."
eq("table", type(dirs))
eq(true, contains_match(dirs, "LICENSE"))
eq(true, contains_match(dirs, "README.md"))
eq(true, contains_match(dirs, "lua"))
eq(false, contains_match(dirs, "%.git$"))
end)
it("works for another directory", function()
local dirs = scan.ls "./lua"
eq("table", type(dirs))
eq(true, contains_match(dirs, "luassert"))
eq(true, contains_match(dirs, "plenary"))
eq(true, contains_match(dirs, "say.lua"))
eq(false, contains_match(dirs, "README.md"))
end)
it("works with opts.hidden for cwd", function()
local dirs = scan.ls(".", { hidden = true })
eq("table", type(dirs))
eq(true, contains_match(dirs, "README.md"))
eq(true, contains_match(dirs, "LICENSE"))
eq(true, contains_match(dirs, "lua"))
eq(true, contains_match(dirs, "%.git$"))
end)
end)
end)

View file

@ -0,0 +1,206 @@
local eq = assert.are.same
local tester_function = function()
error(7)
end
describe("busted specs", function()
describe("nested", function()
it("should work", function()
assert(true)
end)
end)
it("should not nest", function()
assert(true)
end)
it("should not fail unless we unpcall this", function()
pcall(tester_function)
end)
pending("other thing pending", function()
error()
end)
end)
describe("before each", function()
local a = 2
local b = 3
it("is not cleared", function()
eq(2, a)
eq(3, b)
a = a + 1
b = b + 1
end)
describe("nested", function()
before_each(function()
a = 0
end)
it("should clear a but not b", function()
eq(0, a)
eq(4, b)
a = a + 1
b = b + 1
end)
describe("nested nested", function()
before_each(function()
b = 0
end)
it("should clear b as well", function()
eq(0, a)
eq(0, b)
a = a + 1
b = b + 1
end)
end)
it("should only clear a", function()
eq(0, a)
eq(1, b)
a = a + 1
b = b + 1
end)
end)
it("should clear nothing", function()
eq(1, a)
eq(2, b)
end)
end)
describe("before_each ordering", function()
local order = ""
before_each(function()
order = order .. "1,"
end)
before_each(function()
order = order .. "2,"
end)
describe("nested 1 deep", function()
before_each(function()
order = order .. "3,"
end)
before_each(function()
order = order .. "4,"
end)
describe("nested 2 deep", function()
before_each(function()
order = order .. "5,"
end)
it("runs before_each`s in order", function()
eq("1,2,3,4,5,", order)
end)
end)
end)
describe("adjacent nested 1 deep", function()
before_each(function()
order = order .. "3a,"
end)
before_each(function()
order = order .. "4a,"
end)
describe("nested 2 deep", function()
before_each(function()
order = order .. "5a,"
end)
it("runs before_each`s in order", function()
eq("1,2,3,4,5,1,2,3a,4a,5a,", order)
end)
end)
end)
end)
describe("after each", function()
local a = 2
local b = 3
it("is not cleared", function()
eq(2, a)
eq(3, b)
a = a + 1
b = b + 1
end)
describe("nested", function()
after_each(function()
a = 0
end)
it("should not clear any at this point", function()
eq(3, a)
eq(4, b)
a = a + 1
b = b + 1
end)
describe("nested nested", function()
after_each(function()
b = 0
end)
it("should have cleared a", function()
eq(0, a)
eq(5, b)
a = a + 1
b = b + 1
end)
end)
it("should have cleared a and b", function()
eq(0, a)
eq(0, b)
a = a + 1
b = b + 1
end)
end)
it("should only have cleared a", function()
eq(0, a)
eq(1, b)
end)
end)
describe("after_each ordering", function()
local order = ""
describe("1st describe having after_each", function()
after_each(function()
order = order .. "1,"
end)
after_each(function()
order = order .. "2,"
end)
describe("nested 1 deep", function()
after_each(function()
order = order .. "3,"
end)
after_each(function()
order = order .. "4,"
end)
describe("nested 2 deep", function()
after_each(function()
order = order .. "5,"
end)
it("a test to trigger the after_each`s", function()
assert(true)
end)
end)
end)
describe("adjacent nested 1 deep", function()
after_each(function()
order = order .. "3a,"
end)
after_each(function()
order = order .. "4a,"
end)
describe("nested 2 deep", function()
after_each(function()
order = order .. "5a,"
end)
it("a test to trigger the adjacent after_each`s", function()
assert(true)
end)
end)
end)
end)
it("ran after_each`s in order", function()
eq("1,2,3,4,5,1,2,3a,4a,5a,", order)
end)
end)
describe("another top level describe test", function()
it("should work", function()
eq(1, 1)
end)
end)

View file

@ -0,0 +1,305 @@
local strings = require "plenary.strings"
local eq = assert.are.same
describe("strings", function()
describe("strdisplaywidth", function()
for _, case in ipairs {
{ str = "abcde", expected = { single = 5, double = 5 } },
-- This space below is a tab (U+0009)
{ str = "abc de", expected = { single = 10, double = 10 } },
{ str = "アイウエオ", expected = { single = 10, double = 10 } },
{ str = "├─┤", expected = { single = 3, double = 6 } },
{ str = 123, expected = { single = 3, double = 3 } },
} do
for _, ambiwidth in ipairs { "single", "double" } do
local item = type(case.str) == "string" and '"%s"' or "%s"
local msg = ("ambiwidth = %s, " .. item .. " -> %d"):format(ambiwidth, case.str, case.expected[ambiwidth])
local original = vim.o.ambiwidth
vim.o.ambiwidth = ambiwidth
it("lua: " .. msg, function()
eq(case.expected[ambiwidth], strings.strdisplaywidth(case.str))
end)
it("vim: " .. msg, function()
eq(case.expected[ambiwidth], vim.fn.strdisplaywidth(case.str))
end)
vim.o.ambiwidth = original
end
end
end)
describe("strcharpart", function()
for _, case in ipairs {
{ args = { "abcde", 2 }, expected = "cde" },
{ args = { "abcde", 2, 2 }, expected = "cd" },
{ args = { "アイウエオ", 2, 2 }, expected = "ウエ" },
{ args = { "├───┤", 2, 2 }, expected = "──" },
} do
local msg = ('("%s", %d, %s) -> "%s"'):format(case.args[1], case.args[2], tostring(case.args[3]), case.expected)
it("lua: " .. msg, function()
eq(case.expected, strings.strcharpart(unpack(case.args)))
end)
it("vim: " .. msg, function()
eq(case.expected, vim.fn.strcharpart(unpack(case.args)))
end)
end
end)
describe("truncate", function()
for _, case in ipairs {
-- truncations from the right
{ args = { "abcde", 6, nil, 1 }, expected = { single = "abcde", double = "abcde" } },
{ args = { "abcde", 5, nil, 1 }, expected = { single = "abcde", double = "abcde" } },
{ args = { "abcde", 4, nil, 1 }, expected = { single = "abc…", double = "ab…" } },
{
args = { "アイウエオ", 11, nil, 1 },
expected = { single = "アイウエオ", double = "アイウエオ" },
},
{
args = { "アイウエオ", 10, nil, 1 },
expected = { single = "アイウエオ", double = "アイウエオ" },
},
{
args = { "アイウエオ", 9, nil, 1 },
expected = { single = "アイウエ…", double = "アイウ…" },
},
{ args = { "アイウエオ", 8, nil, 1 }, expected = { single = "アイウ…", double = "アイウ…" } },
{ args = { "├─┤", 7, nil, 1 }, expected = { single = "├─┤", double = "├─┤" } },
{ args = { "├─┤", 6, nil, 1 }, expected = { single = "├─┤", double = "├─┤" } },
{ args = { "├─┤", 5, nil, 1 }, expected = { single = "├─┤", double = "├…" } },
{ args = { "├─┤", 4, nil, 1 }, expected = { single = "├─┤", double = "├…" } },
{ args = { "├─┤", 3, nil, 1 }, expected = { single = "├─┤", double = "" } },
{ args = { "├─┤", 2, nil, 1 }, expected = { single = "├…", double = "" } },
-- truncations from the left
{ args = { "abcde", 6, nil, -1 }, expected = { single = "abcde", double = "abcde" } },
{ args = { "abcde", 5, nil, -1 }, expected = { single = "abcde", double = "abcde" } },
{ args = { "abcde", 4, nil, -1 }, expected = { single = "…cde", double = "…de" } },
{
args = { "アイウエオ", 11, nil, -1 },
expected = { single = "アイウエオ", double = "アイウエオ" },
},
{
args = { "アイウエオ", 10, nil, -1 },
expected = { single = "アイウエオ", double = "アイウエオ" },
},
{
args = { "アイウエオ", 9, nil, -1 },
expected = { single = "…イウエオ", double = "…ウエオ" },
},
{ args = { "アイウエオ", 8, nil, -1 }, expected = { single = "…ウエオ", double = "…ウエオ" } },
{ args = { "├─┤", 7, nil, -1 }, expected = { single = "├─┤", double = "├─┤" } },
{ args = { "├─┤", 6, nil, -1 }, expected = { single = "├─┤", double = "├─┤" } },
{ args = { "├─┤", 5, nil, -1 }, expected = { single = "├─┤", double = "…┤" } },
{ args = { "├─┤", 4, nil, -1 }, expected = { single = "├─┤", double = "…┤" } },
{ args = { "├─┤", 3, nil, -1 }, expected = { single = "├─┤", double = "" } },
{ args = { "├─┤", 2, nil, -1 }, expected = { single = "…┤", double = "" } },
-- truncations from the middle
{ args = { "abcde", 6, nil, 0 }, expected = { single = "abcde", double = "abcde" } },
{ args = { "abcde", 5, nil, 0 }, expected = { single = "abcde", double = "abcde" } },
{ args = { "abcde", 4, nil, 0 }, expected = { single = "a…de", double = "a…e" } },
{
args = { "アイウエオ", 11, nil, 0 },
expected = { single = "アイウエオ", double = "アイウエオ" },
},
{
args = { "アイウエオ", 10, nil, 0 },
expected = { single = "アイウエオ", double = "アイウエオ" },
},
{
args = { "アイウエオ", 9, nil, 0 },
expected = { single = "アイ…エオ", double = "ア…エオ" },
},
{ args = { "アイウエオ", 8, nil, 0 }, expected = { single = "ア…エオ", double = "ア…エオ" } },
{ args = { "├─┤", 7, nil, 0 }, expected = { single = "├─┤", double = "├─┤" } },
{ args = { "├─┤", 6, nil, 0 }, expected = { single = "├─┤", double = "├─┤" } },
{ args = { "├─┤", 5, nil, 0 }, expected = { single = "├─┤", double = "…┤" } },
{ args = { "├─┤", 4, nil, 0 }, expected = { single = "├─┤", double = "…┤" } },
{ args = { "├─┤", 3, nil, 0 }, expected = { single = "├─┤", double = "" } },
{ args = { "├─┤", 2, nil, 0 }, expected = { single = "…┤", double = "" } },
} do
for _, ambiwidth in ipairs { "single", "double" } do
local msg = ("ambiwidth = %s, direction = %s, [%s, %d] -> %s"):format(
ambiwidth,
(case.args[4] > 0) and "right" or (case.args[4] < 0) and "left" or "middle",
case.args[1],
case.args[2],
case.expected[ambiwidth]
)
it(msg, function()
local original = vim.o.ambiwidth
vim.o.ambiwidth = ambiwidth
eq(case.expected[ambiwidth], strings.truncate(unpack(case.args)))
vim.o.ambiwidth = original
end)
end
end
end)
describe("align_str", function()
for _, case in ipairs {
{ args = { "abcde", 8 }, expected = { single = "abcde ", double = "abcde " } },
{ args = { "アイウ", 8 }, expected = { single = "アイウ ", double = "アイウ " } },
{ args = { "├─┤", 8 }, expected = { single = "├─┤ ", double = "├─┤ " } },
{ args = { "abcde", 8, true }, expected = { single = " abcde", double = " abcde" } },
{ args = { "アイウ", 8, true }, expected = { single = " アイウ", double = " アイウ" } },
{ args = { "├─┤", 8, true }, expected = { single = " ├─┤", double = " ├─┤" } },
} do
for _, ambiwidth in ipairs { "single", "double" } do
local msg = ('ambiwidth = %s, [%s, %d, %s] -> "%s"'):format(
ambiwidth,
case.args[1],
case.args[2],
tostring(case.args[3]),
case.expected[ambiwidth]
)
it(msg, function()
local original = vim.o.ambiwidth
vim.o.ambiwidth = ambiwidth
eq(case.expected[ambiwidth], strings.align_str(unpack(case.args)))
vim.o.ambiwidth = original
end)
end
end
end)
describe("dedent", function()
local function lines(t)
return table.concat(t, "\n")
end
for _, case in ipairs {
{
msg = "empty string",
tabstop = 8,
args = { "" },
expected = "",
},
{
msg = "in case tabs are longer than spaces",
tabstop = 8,
args = {
lines {
" <Tab><Tab> -> 13 spaces",
" 5 spaces -> 0 space",
},
},
expected = lines {
" <Tab><Tab> -> 13 spaces",
"5 spaces -> 0 space",
},
},
{
msg = "in case tabs are shorter than spaces",
tabstop = 2,
args = {
lines {
" <Tab><Tab> -> 0 space",
" 5spaces -> 1 space",
},
},
expected = lines {
"<Tab><Tab> -> 0 space",
" 5spaces -> 1 space",
},
},
{
msg = "ignores empty lines",
tabstop = 2,
args = {
lines {
"",
"",
"",
" 8 spaces -> 3 spaces",
"",
"",
" 5 spaces -> 0 space",
"",
"",
"",
},
},
expected = lines {
"",
"",
"",
" 8 spaces -> 3 spaces",
"",
"",
"5 spaces -> 0 space",
"",
"",
"",
},
},
{
msg = "no indent",
tabstop = 2,
args = {
lines {
" <Tab> -> 2 spaces",
"Here is no indent.",
" 4 spaces will remain",
},
},
expected = lines {
" <Tab> -> 2 spaces",
"Here is no indent.",
" 4 spaces will remain",
},
},
{
msg = "leave_indent = 4",
tabstop = 2,
args = {
lines {
" <Tab> -> 6 spaces",
"0 indent -> 4 spaces",
" 4 spaces -> 8 spaces",
},
4,
},
expected = lines {
" <Tab> -> 6 spaces",
" 0 indent -> 4 spaces",
" 4 spaces -> 8 spaces",
},
},
{
msg = "typical usecase: <Tab> to 5 spaces",
tabstop = 4,
args = {
lines {
"",
" Chapter 1",
"",
" Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed",
" do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
"",
" Ut enim ad minim veniam, quis nostrud exercitation ullamco",
" laboris nisi ut aliquip ex ea commodo consequat.",
"",
},
5,
},
expected = lines {
"",
" Chapter 1",
"",
" Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed",
" do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
"",
" Ut enim ad minim veniam, quis nostrud exercitation ullamco",
" laboris nisi ut aliquip ex ea commodo consequat.",
"",
},
},
} do
local msg = ("tabstop = %d, %s"):format(case.tabstop, case.msg)
it(msg, function()
local original = vim.bo.tabstop
vim.bo.tabstop = case.tabstop
eq(case.expected, strings.dedent(unpack(case.args)))
vim.bo.tabstop = original
end)
end
end)
end)

View file

@ -0,0 +1,27 @@
local tbl = require "plenary.tbl"
local function should_fail(fun)
local stat = pcall(fun)
assert(not stat, "Function should have errored")
end
describe("tbl utilities", function()
it("should be able to freeze a table", function()
local t = { 1, 2, 3 }
local frozen = tbl.freeze(t)
assert(t[1] == frozen[1])
assert(t[2] == frozen[2])
assert(t[3] == frozen[3])
should_fail(function()
frozen[4] = "thisthis"
end)
should_fail(function()
frozen.hello = "asdfasdf"
end)
assert(not frozen[5])
assert(not frozen.hello)
end)
end)

View file

@ -0,0 +1,5 @@
describe("simple nvim test", function()
it("should work", function()
vim.cmd "let g:val = v:true"
end)
end)