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