dev

This module contains testcases for its parent module, Inspect.

See also


-- <nowiki>
local inspect = require('Module:Inspect')
local suite = require('Module:ScribuntoUnit'):new()

local unindent = require('Module:Unindent')

function suite:testNumbers()
    self:assertEquals('1', inspect(1))
    self:assertEquals('1.5', inspect(1.5))
    self:assertEquals('-3.14', inspect(-3.14))
end

function suite:testStrings()
    -- puts quotes around regular strings
    self:assertEquals('"hello"', inspect('hello'))
    -- puts apostrophes around strings with quotes
    self:assertEquals('\'I have "quotes"\'', inspect('I have "quotes"'))
    -- uses regular quotes if the string has both quotes and apostrophes
    self:assertEquals('"I have \\"quotes\\" and \'apostrophes\'"', inspect('I have "quotes" and \'apostrophes\''))
    -- escapes newlines properly
    self:assertEquals('"I have \\n new \\n lines"', inspect('I have \n new \n lines'))
    -- escapes tabs properly
    self:assertEquals('"I have \\t a tab character"', inspect('I have \t a tab character'))
    -- escapes backspaces properly
    self:assertEquals('"I have \\b a back space"', inspect('I have \b a back space'))
    -- escapes unnamed control characters with 1 or 2 digits
    self:assertEquals(
        '"Here are some control characters: \\0 \\1 \\6 \\17 \\27 \\31"',
        inspect('Here are some control characters: \0 \1 \6 \17 \27 \31')
    )
    -- escapes unnamed control characters with 3 digits when they are followed by numbers
    self:assertEquals(
        '"Control chars followed by digits \\0001 \\0011 \\0061 \\0171 \\0271 \\0311"',
        inspect('Control chars followed by digits \0001 \0011 \0061 \0171 \0271 \0311')
    )
    -- backslashes its backslashes
    self:assertEquals('"I have \\\\ a backslash"', inspect('I have \\ a backslash'))
    self:assertEquals('"I have \\\\\\\\ two backslashes"', inspect('I have \\\\ two backslashes'))
    self:assertEquals(
        '"I have \\\\\\n a backslash followed by a newline"',
        inspect('I have \\\n a backslash followed by a newline')
    )
end

function suite:testNil()
    self:assertEquals('nil', inspect(nil))
end

function suite:testFunctions()
    self:assertEquals('{ <function 1>, <function 2>, <function 1> }', inspect{ mw.log, type, mw.log })
end

function suite:testBooleans()
    self:assertEquals('true', inspect(true))
    self:assertEquals('false', inspect(false))
end

function suite:testTables()
    -- works with simple array-like tables
    self:assertEquals('{ 1, 2, 3 }', inspect{1, 2, 3})
    -- works with nested arrays
    self:assertEquals('{ "a", "b", "c", { "d", "e" }, "f" }', inspect{'a', 'b', 'c', {'d', 'e'}, 'f'})
    -- works with simple dictionary tables
    self:assertEquals('{\n  a = 1,\n  b = 2\n}', inspect{a = 1, b = 2})
    -- identifies tables with no number 1 as struct-like
    self:assertEquals(unindent[[
        {
          [2] = 1,
          [25] = 1,
          id = 1
        }
    ]], inspect{[2] = 1, [25] = 1, id = 1})
    -- identifies numeric non-array keys as dictionary keys
    self:assertEquals('{ 1, 2,\n  [-1] = true\n}', inspect{1, 2, [-1] = true})
    self:assertEquals('{ 1, 2,\n  [1.5] = true\n}', inspect{1, 2, [1.5] = true})
    -- sorts keys in dictionary tables
    local t = { 1, 2, 3,
        [mw.log] = 1, ['buy more'] = 1, a = 1,
        [14] = 1, [{c=2}] = 1, [true]= 1
    }
    self:assertEquals(unindent[[
        { 1, 2, 3,
          [14] = 1,
          [true] = 1,
          a = 1,
          ["buy more"] = 1,
          [{
            c = 2
          }] = 1,
          [<function 1>] = 1
        }
    ]], inspect(t))
    -- works with nested dictionary tables
    self:assertEquals(unindent[[
        {
          a = 1,
          b = {
            c = 2
          },
          d = 3
        }
    ]], inspect{d = 3, b = {c = 2}, a = 1})
    -- works with hybrid tables
    self:assertEquals(unindent[[
        { "a", {
            b = 1
          }, 2,
          ["ahoy you"] = 4,
          c = 3
        }
    ]], inspect{ 'a', {b = 1}, 2, c = 3, ['ahoy you'] = 4 })
    -- displays <table x> instead of repeating an already existing table
    local a = { 1, 2, 3 }
    local b = { 'a', 'b', 'c', a }
    a[4] = b
    a[5] = a
    a[6] = b
    self:assertEquals('<1>{ 1, 2, 3, <2>{ "a", "b", "c", <table 1> }, <table 1>, <table 2> }', inspect(a))
end

function suite:testMetatables()
    -- includes the metatable as an extra hash attribute
    local foo = { foo = 1, __mode = 'v' }
    local bar = setmetatable({a = 1}, foo)
    self:assertEquals(unindent[[
        {
          a = 1,
          <metatable> = {
            __mode = "v",
            foo = 1
          }
        }
    ]], inspect(bar))
    -- includes metatables with table in __metatable field
    local spam = setmetatable({}, {__metatable=nil})
    local eggs = setmetatable({}, {__metatable={}})
    local function process(item)
        return item
    end
    local function inspector(data)
        return inspect(data, {process=process})
    end
    self:assertEquals(unindent([[
      {
        <metatable> = {}
      }
    ]]), inspector(spam))
    self:assertEquals(unindent([[
      {
        <metatable> = {}
      }
    ]]), inspector(eggs))
end

function suite:testSelfMetatables()
    -- accepts a table that is its own metatable without stack overflowing
    local x = {}
    setmetatable(x,x)
    self:assertEquals(unindent([[
      <1>{
        <metatable> = <table 1>
      }
    ]]), inspect(x))
    -- can invoke the __tostring method without stack overflowing
    local t = {}
    t.__index = t
    setmetatable(t,t)
    self:assertEquals(unindent([[
      <1>{
        __index = <table 1>,
        <metatable> = <table 1>
      }
    ]]), inspect(t))
end

function suite:testTostringOverride()
    local stringify = _G.tostring
    _G.tostring = inspect
    local s = tostring({1, 2, 3})
    _G.tostring = stringify
    self:assertEquals('{ 1, 2, 3 }', s)
end

function suite:testOptionDepth()
    local level5 = { 1, 2, 3, a = { b = { c = { d = { e = 5 } } } } }
    local keys = { [level5] = true }
    -- has infinite depth by default
    self:assertEquals(unindent[[
        { 1, 2, 3,
          a = {
            b = {
              c = {
                d = {
                  e = 5
                }
              }
            }
          }
        }
    ]], inspect(level5))
    -- is modifiable by the user
    self:assertEquals(unindent[[
        { 1, 2, 3,
          a = {...}
        }
    ]], inspect(level5, {depth = 1}))
    self:assertEquals(unindent[[
        { 1, 2, 3,
          a = {
            b = {...}
          }
        }
    ]], inspect(level5, {depth = 2}))
    self:assertEquals(unindent[[
        { 1, 2, 3,
          a = {
            b = {
              c = {
                d = {...}
              }
            }
          }
        }
    ]], inspect(level5, {depth = 4}))
    self:assertEquals('{...}', inspect(level5, {depth = 0}))
    -- respects depth on keys
    self:assertEquals(unindent[[
        {
          [{ 1, 2, 3,
            a = {
              b = {
                c = {...}
              }
            }
          }] = true
        }
    ]], inspect(keys, {depth = 4}))
end

function suite:testOptionNewline()
    -- changes the substring used for newlines
    local t = {a = {b = 1}}
    self:assertEquals('{@  a = {@    b = 1@  }@}', inspect(t, {newline = '@'}))
end

function suite:testOptionIndent()
    -- changes the substring used for indenting
    local t = {a = {b = 1}}
    self:assertEquals('{\n>>>a = {\n>>>>>>b = 1\n>>>}\n}', inspect(t, {indent = '>>>'}))
end

function suite:testOptionProcess()
    -- removes one element
    local names = {'Andrew', 'Peter', 'Ann' }
    local function removeAnn(item)
        return item ~= 'Ann' and item or  nil
    end
    self:assertEquals('{ "Andrew", "Peter" }', inspect(names, {process = removeAnn}))
    -- uses the path
    local function removeThird(item, path)
        return path[1] ~= 3 and item or nil
    end
    self:assertEquals('{ "Andrew", "Peter" }', inspect(names, {process = removeThird}))
    -- replaces items
    local function filterAnn(item)
        return item == 'Ann' and '<filtered>' or  item
    end
    self:assertEquals('{ "Andrew", "Peter", "<filtered>" }', inspect(names, {process = filterAnn}))
    -- nullifies metatables
    local mt = {'world'}
    local t1  = setmetatable({'hello'}, mt)
    local function removeMt(item)
        return item ~= mt and item or nil
    end
    self:assertEquals('{ "hello" }', inspect(t1, {process = removeMt}))
    -- nullifies metatables using their paths
    local function removeMtByPath(item, path)
        return path[#path] ~= inspect.METATABLE and item or nil
    end
    self:assertEquals('{ "hello" }', inspect(t1, {process = removeMt}))
    -- nullifies the root object
    local function removeNames(item)
        return item ~= names and item or nil
    end
    self:assertEquals('nil', inspect(names, {process = removeNames}))
    -- changes keys
    local dict1 = {a = 1}
    local function changeKey(item)
        return item == 'a' and 'x' or item
    end
    self:assertEquals('{\n  x = 1\n}', inspect(dict1, {process = changeKey}))
    -- nullifies keys
    local dict2 = {a = 1, b = 2}
    local removeA = function(item)
        return item ~= 'a' and item or nil
    end
    self:assertEquals('{\n  b = 2\n}', inspect(dict2, {process = removeA}))
    -- prints inspect.KEY & inspect.METATABLE
    local t2 = {inspect.KEY, inspect.METATABLE}
    self:assertEquals('{ inspect.KEY, inspect.METATABLE }', inspect(t2))
    -- marks key paths with inspect.KEY and metatables with inspect.METATABLE
    local t3 = { [{a=1}] = setmetatable({b=2}, {c=3}) }
    local items = {}
    local function addItem(item, path)
        items[#items + 1] = {item = item, path = path}
        return item
    end
    inspect(t3, {process = addItem})
    self:assertDeepEquals({
        {item = t3,                          path = {}},
        {item = {a=1},                       path = {{a=1}, inspect.KEY}},
        {item = 'a',                         path = {{a=1}, inspect.KEY, 'a', inspect.KEY}},
        {item = 1,                           path = {{a=1}, inspect.KEY, 'a'}},
        {item = setmetatable({b=2}, {c=3}),  path = {{a=1}}},
        {item = 'b',                         path = {{a=1}, 'b', inspect.KEY}},
        {item = 2,                           path = {{a=1}, 'b'}},
        {item = {c=3},                       path = {{a=1}, inspect.METATABLE}},
        {item = 'c',                         path = {{a=1}, inspect.METATABLE, 'c', inspect.KEY}},
        {item = 3,                           path = {{a=1}, inspect.METATABLE, 'c'}}
    }, items)
    -- handles recursive tables correctly
    local t4 = {1, 2, 3}
    t4.loop = t4
    local function recursionProcess(x)
        return x
    end
    self:assertTrue(inspect(t4, {process = recursionProcess}))
end

return suite