Repository: nvim-lua/plenary.nvim
Pull request #335 (2022-03-26 14:13:35)

Fix typo
explected -> expected



Patch:
diff --git a/lua/plenary/async/init.lua b/lua/plenary/async/init.lua
index f8222fc..71ccd84 100644
--- a/lua/plenary/async/init.lua
+++ b/lua/plenary/async/init.lua
@@ -36,6 +36,8 @@ exports.tests.add_globals = function()
   a.describe = exports.tests.describe
   -- must prefix with a or stack overflow
   a.it = exports.tests.it
+  a.before_each = exports.tests.before_each
+  a.after_each = exports.tests.after_each
 end
 
 exports.tests.add_to_env = function()
@@ -50,6 +52,8 @@ exports.tests.add_to_env = function()
   env.a.describe = exports.tests.describe
   -- must prefix with a or stack overflow
   env.a.it = exports.tests.it
+  a.before_each = exports.tests.before_each
+  a.after_each = exports.tests.after_each
 
   setfenv(2, env)
 end
diff --git a/lua/plenary/filetype.lua b/lua/plenary/filetype.lua
index ef45102..6a047c5 100644
--- a/lua/plenary/filetype.lua
+++ b/lua/plenary/filetype.lua
@@ -130,19 +130,24 @@ filetype.detect_from_name = function(filepath)
 end
 
 filetype.detect_from_modeline = function(filepath)
-  local tail = Path:new(filepath):tail(1)
+  local tail = Path:new(filepath):readbyterange(-256, 256)
   if not tail then
     return ""
   end
-  return filetype._parse_modeline(tail)
+  local lines = vim.split(tail, "\n")
+  local idx = lines[#lines] ~= "" and #lines or #lines - 1
+  if idx >= 1 then
+    return filetype._parse_modeline(lines[idx])
+  end
 end
 
 filetype.detect_from_shebang = function(filepath)
-  local head = Path:new(filepath):head(1)
+  local head = Path:new(filepath):readbyterange(0, 256)
   if not head then
     return ""
   end
-  return filetype._parse_shebang(head)
+  local lines = vim.split(head, "\n")
+  return filetype._parse_shebang(lines[1])
 end
 
 --- Detect a filetype from a path.
diff --git a/lua/plenary/path.lua b/lua/plenary/path.lua
index 7da6912..4a1fd23 100644
--- a/lua/plenary/path.lua
+++ b/lua/plenary/path.lua
@@ -88,6 +88,12 @@ local function _normalize_path(filename, cwd)
     return filename
   end
 
+  -- handles redundant `./` in the middle
+  local redundant = path.sep .. "%." .. path.sep
+  if filename:match(redundant) then
+    filename = filename:gsub(redundant, path.sep)
+  end
+
   local out_file = filename
 
   local has = string.find(filename, path.sep .. "..", 1, true) or string.find(filename, ".." .. path.sep, 1, true)
@@ -341,9 +347,14 @@ function Path:normalize(cwd)
   self:make_relative(cwd)
 
   -- Substitute home directory w/ "~"
-  self.filename = self.filename:gsub("^" .. path.home, "~" .. path.sep, 1)
+  -- string.gsub is not useful here because usernames with dashes at the end
+  -- will be seen as a regexp pattern rather than a raw string
+  local start, finish = string.find(self.filename, path.home, 1, true)
+  if start == 1 then
+    self.filename = "~" .. path.sep .. string.sub(self.filename, (finish + 1), -1)
+  end
 
-  return _normalize_path(self.filename, self._cwd)
+  return _normalize_path(clean(self.filename), self._cwd)
 end
 
 local function shorten_len(filename, len, exclude)
@@ -835,7 +846,7 @@ end
 function Path:iter()
   local data = self:readlines()
   local i = 0
-  local n = table.getn(data)
+  local n = #data
   return function()
     i = i + 1
     if i <= n then
@@ -844,4 +855,41 @@ function Path:iter()
   end
 end
 
+function Path:readbyterange(offset, length)
+  self = check_self(self)
+
+  local fd = uv.fs_open(self:expand(), "r", 438)
+  if not fd then
+    return
+  end
+  local stat = assert(uv.fs_fstat(fd))
+  if stat.type ~= "file" then
+    uv.fs_close(fd)
+    return nil
+  end
+
+  if offset < 0 then
+    offset = stat.size + offset
+    -- Windows fails if offset is < 0 even though offset is defined as signed
+    -- http://docs.libuv.org/en/v1.x/fs.html#c.uv_fs_read
+    if offset < 0 then
+      offset = 0
+    end
+  end
+
+  local data = ""
+  while #data < length do
+    local read_chunk = assert(uv.fs_read(fd, length - #data, offset))
+    if #read_chunk == 0 then
+      break
+    end
+    data = data .. read_chunk
+    offset = offset + #read_chunk
+  end
+
+  assert(uv.fs_close(fd))
+
+  return data
+end
+
 return Path


Test patch:
diff --git a/TESTS_README.md b/TESTS_README.md
index 1c9b900..e5db67a 100644
--- a/TESTS_README.md
+++ b/TESTS_README.md
@@ -65,7 +65,7 @@ describe("example", function()
   -- mocked instance of api to interact with
 
   describe("realistic_func", function()
-    it("Should make explected calls to api, fully mocked", function()
+    it("Should make expected calls to api, fully mocked", function()
       -- mock the vim.api
       local api = mock(vim.api, true)
 
diff --git a/lua/plenary/async/tests.lua b/lua/plenary/async/tests.lua
index 367b83d..4895773 100644
--- a/lua/plenary/async/tests.lua
+++ b/lua/plenary/async/tests.lua
@@ -10,4 +10,12 @@ M.it = function(s, async_func)
   it(s, util.will_block(async_func))
 end
 
+M.before_each = function(async_func)
+  before_each(util.will_block(async_func))
+end
+
+M.after_each = function(async_func)
+  after_each(util.will_block(async_func))
+end
+
 return M
diff --git a/tests/plenary/async/test_spec.lua b/tests/plenary/async/test_spec.lua
new file mode 100644
index 0000000..6a37309
--- /dev/null
+++ b/tests/plenary/async/test_spec.lua
@@ -0,0 +1,52 @@
+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)
diff --git a/tests/plenary/path_spec.lua b/tests/plenary/path_spec.lua
index be9cae1..2713532 100644
--- a/tests/plenary/path_spec.lua
+++ b/tests/plenary/path_spec.lua
@@ -168,6 +168,12 @@ describe("Path", function()
   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()
@@ -206,12 +212,12 @@ describe("Path", function()
       assert.are.same("/tmp/lua/plenary/path.lua", p:normalize())
     end)
 
-    it("can normalize ~ when file is within home directory (traling slash)", function()
+    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())
+      assert.are.same("~/test_file", p:normalize())
     end)
 
     it("can normalize ~ when file is within home directory (no trailing slash)", function()
@@ -219,7 +225,15 @@ describe("Path", function()
       local p = Path:new { home, "./test_file" }
       p.path.home = home
       p._cwd = "/tmp/lua"
-      assert.are.same("~//./test_file", p:normalize())
+      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)
   end)
 
@@ -684,6 +698,22 @@ 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)
 end)
 
 -- function TestPath:testIsDir()
diff --git a/tests/plenary/strings_spec.lua b/tests/plenary/strings_spec.lua
index fcf990e..d9abd6d 100644
--- a/tests/plenary/strings_spec.lua
+++ b/tests/plenary/strings_spec.lua
@@ -3,16 +3,14 @@ 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 _, 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])
@@ -30,14 +28,12 @@ describe("strings", function()
   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
+    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)))
@@ -49,79 +45,77 @@ describe("strings", function()
   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 _, 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,
@@ -141,16 +135,14 @@ describe("strings", function()
   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 _, 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,
@@ -173,136 +165,134 @@ describe("strings", 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",
+    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",
           },
         },
-        {
-          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",
-          },
+        expected = lines {
+          "           <Tab><Tab> -> 13 spaces",
+          "5 spaces -> 0 space",
         },
-        {
-          msg = "ignores empty lines",
-          tabstop = 2,
-          args = {
-            lines {
-              "",
-              "",
-              "",
-              "        8 spaces -> 3 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 {
+        },
+        expected = lines {
+          "<Tab><Tab> -> 0 space",
+          " 5spaces -> 1 space",
+        },
+      },
+      {
+        msg = "ignores empty lines",
+        tabstop = 2,
+        args = {
+          lines {
             "",
             "",
             "",
-            "   8 spaces -> 3 spaces",
+            "        8 spaces -> 3 spaces",
             "",
             "",
-            "5 spaces -> 0 space",
+            "     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",
+        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",
           },
         },
-        {
-          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",
-          },
+        expected = lines {
+          "  <Tab> -> 2 spaces",
+          "Here is no indent.",
+          "    4 spaces will remain",
         },
-        {
-          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,
+      },
+      {
+        msg = "leave_indent = 4",
+        tabstop = 2,
+        args = {
+          lines {
+            "	<Tab> -> 6 spaces",
+            "0 indent -> 4 spaces",
+            "    4 spaces -> 8 spaces",
           },
-          expected = lines {
+          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",
+            "		Chapter 1",
             "",
-            "       Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed",
-            "     do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
+            "	  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.",
+            "	  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
+      },
+    } do
       local msg = ("tabstop = %d, %s"):format(case.tabstop, case.msg)
       it(msg, function()
         local original = vim.bo.tabstop