<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="rue">
	<id>https://wiki.kocky.cc/w/index.php?action=history&amp;feed=atom&amp;title=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C%3AScribuntoUnit</id>
	<title>Модуль:ScribuntoUnit - Історія едітовань</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.kocky.cc/w/index.php?action=history&amp;feed=atom&amp;title=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C%3AScribuntoUnit"/>
	<link rel="alternate" type="text/html" href="https://wiki.kocky.cc/w/index.php?title=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C:ScribuntoUnit&amp;action=history"/>
	<updated>2026-04-04T21:13:26Z</updated>
	<subtitle>Історія едітовань той сторінкы на вікі</subtitle>
	<generator>MediaWiki 1.40.1</generator>
	<entry>
		<id>https://wiki.kocky.cc/w/index.php?title=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C:ScribuntoUnit&amp;diff=112&amp;oldid=prev</id>
		<title>Бетярь: 1 ревізія: Module:Common from StarCitizenTools</title>
		<link rel="alternate" type="text/html" href="https://wiki.kocky.cc/w/index.php?title=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C:ScribuntoUnit&amp;diff=112&amp;oldid=prev"/>
		<updated>2023-10-18T00:53:16Z</updated>

		<summary type="html">&lt;p&gt;1 ревізія: Module:Common from StarCitizenTools&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;rue&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Старша верзія&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Ревізія 02:53, 18 октовбра 2023&lt;/td&gt;
				&lt;/tr&gt;
&lt;!-- diff cache key wiki:diff::1.12:old-111:rev-112 --&gt;
&lt;/table&gt;</summary>
		<author><name>Бетярь</name></author>
	</entry>
	<entry>
		<id>https://wiki.kocky.cc/w/index.php?title=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C:ScribuntoUnit&amp;diff=111&amp;oldid=prev</id>
		<title>starcitizen&gt;Alistair3149 в 04:34, 6 юнія 2023</title>
		<link rel="alternate" type="text/html" href="https://wiki.kocky.cc/w/index.php?title=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D1%8C:ScribuntoUnit&amp;diff=111&amp;oldid=prev"/>
		<updated>2023-06-06T04:34:01Z</updated>

		<summary type="html">&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Нова сторінка&lt;/b&gt;&lt;/p&gt;&lt;div&gt;-------------------------------------------------------------------------------&lt;br /&gt;
-- Unit tests for Scribunto.&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
require(&amp;#039;strict&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
local DebugHelper = {}&lt;br /&gt;
local ScribuntoUnit = {}&lt;br /&gt;
&lt;br /&gt;
-- The cfg table contains all localisable strings and configuration, to make it&lt;br /&gt;
-- easier to port this module to another wiki.&lt;br /&gt;
local cfg = mw.loadData(&amp;#039;Module:ScribuntoUnit/config&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Concatenates keys and values, ideal for displaying a template or parser function argument table.&lt;br /&gt;
-- @param keySeparator glue between key and value (defaults to &amp;quot; = &amp;quot;)&lt;br /&gt;
-- @param separator glue between different key-value pairs (defaults to &amp;quot;, &amp;quot;)&lt;br /&gt;
-- @example concatWithKeys({a = 1, b = 2, c = 3}, &amp;#039; =&amp;gt; &amp;#039;, &amp;#039;, &amp;#039;) =&amp;gt; &amp;quot;a =&amp;gt; 1, b =&amp;gt; 2, c =&amp;gt; 3&amp;quot;&lt;br /&gt;
-- &lt;br /&gt;
function DebugHelper.concatWithKeys(table, keySeparator, separator)&lt;br /&gt;
    keySeparator = keySeparator or &amp;#039; = &amp;#039;&lt;br /&gt;
    separator = separator or &amp;#039;, &amp;#039;&lt;br /&gt;
    local concatted = &amp;#039;&amp;#039;&lt;br /&gt;
    local i = 1&lt;br /&gt;
    local first = true&lt;br /&gt;
    local unnamedArguments = true&lt;br /&gt;
    for k, v in pairs(table) do&lt;br /&gt;
        if first then&lt;br /&gt;
            first = false&lt;br /&gt;
        else&lt;br /&gt;
            concatted = concatted .. separator&lt;br /&gt;
        end&lt;br /&gt;
        if k == i and unnamedArguments then&lt;br /&gt;
            i = i + 1&lt;br /&gt;
            concatted = concatted .. tostring(v)&lt;br /&gt;
        else&lt;br /&gt;
            unnamedArguments = false&lt;br /&gt;
            concatted = concatted .. tostring(k) .. keySeparator .. tostring(v)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    return concatted&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Compares two tables recursively (non-table values are handled correctly as well).&lt;br /&gt;
-- @param ignoreMetatable if false, t1.__eq is used for the comparison&lt;br /&gt;
-- &lt;br /&gt;
function DebugHelper.deepCompare(t1, t2, ignoreMetatable)&lt;br /&gt;
    local type1 = type(t1)&lt;br /&gt;
    local type2 = type(t2)&lt;br /&gt;
&lt;br /&gt;
    if type1 ~= type2 then &lt;br /&gt;
        return false &lt;br /&gt;
    end&lt;br /&gt;
    if type1 ~= &amp;#039;table&amp;#039; then &lt;br /&gt;
        return t1 == t2 &lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local metatable = getmetatable(t1)&lt;br /&gt;
    if not ignoreMetatable and metatable and metatable.__eq then &lt;br /&gt;
        return t1 == t2 &lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    for k1, v1 in pairs(t1) do&lt;br /&gt;
        local v2 = t2[k1]&lt;br /&gt;
        if v2 == nil or not DebugHelper.deepCompare(v1, v2) then &lt;br /&gt;
            return false &lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    for k2, v2 in pairs(t2) do&lt;br /&gt;
        if t1[k2] == nil then &lt;br /&gt;
            return false &lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return true&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Raises an error with stack information&lt;br /&gt;
-- @param details a table with error details&lt;br /&gt;
--        - should have a &amp;#039;text&amp;#039; key which is the error message to display&lt;br /&gt;
--        - a &amp;#039;trace&amp;#039; key will be added with the stack data&lt;br /&gt;
--        - and a &amp;#039;source&amp;#039; key with file/line number&lt;br /&gt;
--        - a metatable will be added for error handling&lt;br /&gt;
-- &lt;br /&gt;
function DebugHelper.raise(details, level)&lt;br /&gt;
    level = (level or 1) + 1&lt;br /&gt;
    details.trace = debug.traceback(&amp;#039;&amp;#039;, level)&lt;br /&gt;
    details.source = string.match(details.trace, &amp;#039;^%s*stack traceback:%s*(%S*: )&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
--    setmetatable(details, {&lt;br /&gt;
--        __tostring: function() return details.text end&lt;br /&gt;
--    })&lt;br /&gt;
&lt;br /&gt;
    error(details, level)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- when used in a test, that test gets ignored, and the skipped count increases by one.&lt;br /&gt;
-- &lt;br /&gt;
function ScribuntoUnit:markTestSkipped()&lt;br /&gt;
    DebugHelper.raise({ScribuntoUnit = true, skipped = true}, 3)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Checks that the input is true&lt;br /&gt;
-- @param message optional description of the test&lt;br /&gt;
-- &lt;br /&gt;
function ScribuntoUnit:assertTrue(actual, message)&lt;br /&gt;
    if not actual then&lt;br /&gt;
        DebugHelper.raise({ScribuntoUnit = true, text = string.format(&amp;quot;Failed to assert that %s is true&amp;quot;, tostring(actual)), message = message}, 2)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Checks that the input is false&lt;br /&gt;
-- @param message optional description of the test&lt;br /&gt;
-- &lt;br /&gt;
function ScribuntoUnit:assertFalse(actual, message)&lt;br /&gt;
    if actual then&lt;br /&gt;
        DebugHelper.raise({ScribuntoUnit = true, text = string.format(&amp;quot;Failed to assert that %s is false&amp;quot;, tostring(actual)), message = message}, 2)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Checks an input string contains the expected string&lt;br /&gt;
-- @param message optional description of the test&lt;br /&gt;
-- @param plain search is made with a plain string instead of a ustring pattern&lt;br /&gt;
-- &lt;br /&gt;
function ScribuntoUnit:assertStringContains(pattern, s, plain, message)&lt;br /&gt;
	if type(pattern) ~= &amp;#039;string&amp;#039; then&lt;br /&gt;
		DebugHelper.raise({&lt;br /&gt;
			ScribuntoUnit = true,&lt;br /&gt;
			text = mw.ustring.format(&amp;quot;Pattern type error (expected string, got %s)&amp;quot;, type(pattern)),&lt;br /&gt;
			message = message&lt;br /&gt;
		}, 2)&lt;br /&gt;
	end&lt;br /&gt;
	if type(s) ~= &amp;#039;string&amp;#039; then&lt;br /&gt;
		DebugHelper.raise({&lt;br /&gt;
			ScribuntoUnit = true,&lt;br /&gt;
			text = mw.ustring.format(&amp;quot;String type error (expected string, got %s)&amp;quot;, type(s)),&lt;br /&gt;
			message = message&lt;br /&gt;
		}, 2)&lt;br /&gt;
	end&lt;br /&gt;
	if not mw.ustring.find(s, pattern, nil, plain) then&lt;br /&gt;
		DebugHelper.raise({&lt;br /&gt;
			ScribuntoUnit = true,&lt;br /&gt;
			text = mw.ustring.format(&amp;#039;Failed to find %s &amp;quot;%s&amp;quot; in string &amp;quot;%s&amp;quot;&amp;#039;, plain and &amp;quot;plain string&amp;quot; or &amp;quot;pattern&amp;quot;, pattern, s),&lt;br /&gt;
			message = message&lt;br /&gt;
		}, 2)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Checks an input string doesn&amp;#039;t contain the expected string&lt;br /&gt;
-- @param message optional description of the test&lt;br /&gt;
-- @param plain search is made with a plain string instead of a ustring pattern&lt;br /&gt;
-- &lt;br /&gt;
function ScribuntoUnit:assertNotStringContains(pattern, s, plain, message)&lt;br /&gt;
	if type(pattern) ~= &amp;#039;string&amp;#039; then&lt;br /&gt;
		DebugHelper.raise({&lt;br /&gt;
			ScribuntoUnit = true,&lt;br /&gt;
			text = mw.ustring.format(&amp;quot;Pattern type error (expected string, got %s)&amp;quot;, type(pattern)),&lt;br /&gt;
			message = message&lt;br /&gt;
		}, 2)&lt;br /&gt;
	end&lt;br /&gt;
	if type(s) ~= &amp;#039;string&amp;#039; then&lt;br /&gt;
		DebugHelper.raise({&lt;br /&gt;
			ScribuntoUnit = true,&lt;br /&gt;
			text = mw.ustring.format(&amp;quot;String type error (expected string, got %s)&amp;quot;, type(s)),&lt;br /&gt;
			message = message&lt;br /&gt;
		}, 2)&lt;br /&gt;
	end&lt;br /&gt;
	local i, j = mw.ustring.find(s, pattern, nil, plain)&lt;br /&gt;
	if i then&lt;br /&gt;
		local match = mw.ustring.sub(s, i, j)&lt;br /&gt;
		DebugHelper.raise({&lt;br /&gt;
			ScribuntoUnit = true,&lt;br /&gt;
			text = mw.ustring.format(&amp;#039;Found match &amp;quot;%s&amp;quot; for %s &amp;quot;%s&amp;quot;&amp;#039;, match, plain and &amp;quot;plain string&amp;quot; or &amp;quot;pattern&amp;quot;, pattern),&lt;br /&gt;
			message = message&lt;br /&gt;
		}, 2)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Checks that an input has the expected value.&lt;br /&gt;
-- @param message optional description of the test&lt;br /&gt;
-- @example assertEquals(4, add(2,2), &amp;quot;2+2 should be 4&amp;quot;)&lt;br /&gt;
-- &lt;br /&gt;
function ScribuntoUnit:assertEquals(expected, actual, message)&lt;br /&gt;
&lt;br /&gt;
	if type(expected) == &amp;#039;number&amp;#039; and type(actual) == &amp;#039;number&amp;#039; then&lt;br /&gt;
        self:assertWithinDelta(expected, actual, 1e-8, message)&lt;br /&gt;
&lt;br /&gt;
	elseif expected ~= actual then&lt;br /&gt;
        DebugHelper.raise({&lt;br /&gt;
            ScribuntoUnit = true, &lt;br /&gt;
            text = string.format(&amp;quot;Failed to assert that %s equals expected %s&amp;quot;, tostring(actual), tostring(expected)), &lt;br /&gt;
            actual = actual,&lt;br /&gt;
            expected = expected,&lt;br /&gt;
            message = message,&lt;br /&gt;
        }, 2)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Checks that &amp;#039;actual&amp;#039; is within &amp;#039;delta&amp;#039; of &amp;#039;expected&amp;#039;.&lt;br /&gt;
-- @param message optional description of the test&lt;br /&gt;
-- @example assertEquals(1/3, 9/3, &amp;quot;9/3 should be 1/3&amp;quot;, 0.000001)&lt;br /&gt;
function ScribuntoUnit:assertWithinDelta(expected, actual, delta, message)&lt;br /&gt;
    if type(expected) ~= &amp;quot;number&amp;quot; then&lt;br /&gt;
        DebugHelper.raise({&lt;br /&gt;
            ScribuntoUnit = true,&lt;br /&gt;
            text = string.format(&amp;quot;Expected value %s is not a number&amp;quot;, tostring(expected)),&lt;br /&gt;
            actual = actual,&lt;br /&gt;
            expected = expected,&lt;br /&gt;
            message = message,&lt;br /&gt;
        }, 2)&lt;br /&gt;
    end&lt;br /&gt;
    if type(actual) ~= &amp;quot;number&amp;quot; then&lt;br /&gt;
        DebugHelper.raise({&lt;br /&gt;
            ScribuntoUnit = true,&lt;br /&gt;
            text = string.format(&amp;quot;Actual value %s is not a number&amp;quot;, tostring(actual)),&lt;br /&gt;
            actual = actual,&lt;br /&gt;
            expected = expected,&lt;br /&gt;
            message = message,&lt;br /&gt;
        }, 2)&lt;br /&gt;
    end&lt;br /&gt;
    local diff = expected - actual&lt;br /&gt;
    if diff &amp;lt; 0 then diff = - diff end  -- instead of importing math.abs&lt;br /&gt;
    if diff &amp;gt; delta then&lt;br /&gt;
        DebugHelper.raise({&lt;br /&gt;
            ScribuntoUnit = true, &lt;br /&gt;
            text = string.format(&amp;quot;Failed to assert that %f is within %f of expected %f&amp;quot;, actual, delta, expected), &lt;br /&gt;
            actual = actual,&lt;br /&gt;
            expected = expected,&lt;br /&gt;
            message = message,&lt;br /&gt;
        }, 2)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Checks that a table has the expected value (including sub-tables).&lt;br /&gt;
-- @param message optional description of the test&lt;br /&gt;
-- @example assertDeepEquals({{1,3}, {2,4}}, partition(odd, {1,2,3,4}))&lt;br /&gt;
function ScribuntoUnit:assertDeepEquals(expected, actual, message)&lt;br /&gt;
    if not DebugHelper.deepCompare(expected, actual) then&lt;br /&gt;
        if type(expected) == &amp;#039;table&amp;#039; then&lt;br /&gt;
            expected = mw.dumpObject(expected)&lt;br /&gt;
        end&lt;br /&gt;
        if type(actual) == &amp;#039;table&amp;#039; then&lt;br /&gt;
            actual = mw.dumpObject(actual)&lt;br /&gt;
        end&lt;br /&gt;
        DebugHelper.raise({&lt;br /&gt;
            ScribuntoUnit = true, &lt;br /&gt;
            text = string.format(&amp;quot;Failed to assert that %s equals expected %s&amp;quot;, tostring(actual), tostring(expected)), &lt;br /&gt;
            actual = actual,&lt;br /&gt;
            expected = expected,&lt;br /&gt;
            message = message,&lt;br /&gt;
        }, 2)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Checks that a wikitext gives the expected result after processing.&lt;br /&gt;
-- @param message optional description of the test&lt;br /&gt;
-- @example assertResultEquals(&amp;quot;Hello world&amp;quot;, &amp;quot;{{concat|Hello|world}}&amp;quot;)&lt;br /&gt;
function ScribuntoUnit:assertResultEquals(expected, text, message)&lt;br /&gt;
    local frame = self.frame&lt;br /&gt;
    local actual = frame:preprocess(text)&lt;br /&gt;
    if expected ~= actual then&lt;br /&gt;
        DebugHelper.raise({&lt;br /&gt;
            ScribuntoUnit = true, &lt;br /&gt;
            text = string.format(&amp;quot;Failed to assert that %s equals expected %s after preprocessing&amp;quot;, text, tostring(expected)), &lt;br /&gt;
            actual = actual,&lt;br /&gt;
            actualRaw = text,&lt;br /&gt;
            expected = expected,&lt;br /&gt;
            message = message,&lt;br /&gt;
        }, 2)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Checks that two wikitexts give the same result after processing.&lt;br /&gt;
-- @param message optional description of the test&lt;br /&gt;
-- @example assertSameResult(&amp;quot;{{concat|Hello|world}}&amp;quot;, &amp;quot;{{deleteLastChar|Hello world!}}&amp;quot;)&lt;br /&gt;
function ScribuntoUnit:assertSameResult(text1, text2, message)&lt;br /&gt;
    local frame = self.frame&lt;br /&gt;
    local processed1 = frame:preprocess(text1)&lt;br /&gt;
    local processed2 = frame:preprocess(text2)&lt;br /&gt;
    if processed1 ~= processed2 then&lt;br /&gt;
        DebugHelper.raise({&lt;br /&gt;
            ScribuntoUnit = true, &lt;br /&gt;
            text = string.format(&amp;quot;Failed to assert that %s equals expected %s after preprocessing&amp;quot;, processed1, processed2), &lt;br /&gt;
            actual = processed1,&lt;br /&gt;
            actualRaw = text1,&lt;br /&gt;
            expected = processed2,&lt;br /&gt;
            expectedRaw = text2,&lt;br /&gt;
            message = message,&lt;br /&gt;
        }, 2)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Checks that a parser function gives the expected output.&lt;br /&gt;
-- @param message optional description of the test&lt;br /&gt;
-- @example assertParserFunctionEquals(&amp;quot;Hello world&amp;quot;, &amp;quot;msg:concat&amp;quot;, {&amp;quot;Hello&amp;quot;, &amp;quot; world&amp;quot;})&lt;br /&gt;
function ScribuntoUnit:assertParserFunctionEquals(expected, pfname, args, message)&lt;br /&gt;
    local frame = self.frame&lt;br /&gt;
    local actual = frame:callParserFunction{ name = pfname, args = args}&lt;br /&gt;
    if expected ~= actual then&lt;br /&gt;
        DebugHelper.raise({&lt;br /&gt;
            ScribuntoUnit = true, &lt;br /&gt;
            text = string.format(&amp;quot;Failed to assert that %s with args %s equals expected %s after preprocessing&amp;quot;, &lt;br /&gt;
                                 DebugHelper.concatWithKeys(args), pfname, expected),&lt;br /&gt;
            actual = actual,&lt;br /&gt;
            actualRaw = pfname,&lt;br /&gt;
            expected = expected,&lt;br /&gt;
            message = message,&lt;br /&gt;
        }, 2)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Checks that a template gives the expected output.&lt;br /&gt;
-- @param message optional description of the test&lt;br /&gt;
-- @example assertTemplateEquals(&amp;quot;Hello world&amp;quot;, &amp;quot;concat&amp;quot;, {&amp;quot;Hello&amp;quot;, &amp;quot; world&amp;quot;})&lt;br /&gt;
function ScribuntoUnit:assertTemplateEquals(expected, template, args, message)&lt;br /&gt;
    local frame = self.frame&lt;br /&gt;
    local actual = frame:expandTemplate{ title = template, args = args}&lt;br /&gt;
    if expected ~= actual then&lt;br /&gt;
        DebugHelper.raise({&lt;br /&gt;
            ScribuntoUnit = true, &lt;br /&gt;
            text = string.format(&amp;quot;Failed to assert that %s with args %s equals expected %s after preprocessing&amp;quot;, &lt;br /&gt;
                                 DebugHelper.concatWithKeys(args), template, expected),&lt;br /&gt;
            actual = actual,&lt;br /&gt;
            actualRaw = template,&lt;br /&gt;
            expected = expected,&lt;br /&gt;
            message = message,&lt;br /&gt;
        }, 2)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Checks whether a function throws an error&lt;br /&gt;
-- @param fn the function to test&lt;br /&gt;
-- @param expectedMessage optional the expected error message&lt;br /&gt;
-- @param message optional description of the test&lt;br /&gt;
function ScribuntoUnit:assertThrows(fn, expectedMessage, message)&lt;br /&gt;
    local succeeded, actualMessage = pcall(fn)&lt;br /&gt;
    if succeeded then&lt;br /&gt;
        DebugHelper.raise({&lt;br /&gt;
            ScribuntoUnit = true,&lt;br /&gt;
            text = &amp;#039;Expected exception but none was thrown&amp;#039;,&lt;br /&gt;
            message = message,&lt;br /&gt;
        }, 2)&lt;br /&gt;
    end&lt;br /&gt;
	-- For strings, strip the line number added to the error message&lt;br /&gt;
    actualMessage = type(actualMessage) == &amp;#039;string&amp;#039; &lt;br /&gt;
    	and string.match(actualMessage, &amp;#039;Module:[^:]*:[0-9]*: (.*)&amp;#039;)&lt;br /&gt;
    	or actualMessage&lt;br /&gt;
    local messagesMatch = DebugHelper.deepCompare(expectedMessage, actualMessage)&lt;br /&gt;
    if expectedMessage and not messagesMatch then&lt;br /&gt;
        DebugHelper.raise({&lt;br /&gt;
            ScribuntoUnit = true,&lt;br /&gt;
            expected = expectedMessage,&lt;br /&gt;
            actual = actualMessage,&lt;br /&gt;
            text = string.format(&amp;#039;Expected exception with message %s, but got message %s&amp;#039;, &lt;br /&gt;
                tostring(expectedMessage), tostring(actualMessage)&lt;br /&gt;
            ),&lt;br /&gt;
            message = message&lt;br /&gt;
        }, 2)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Checks whether a function doesn&amp;#039;t throw an error&lt;br /&gt;
-- @param fn the function to test&lt;br /&gt;
-- @param message optional description of the test&lt;br /&gt;
function ScribuntoUnit:assertDoesNotThrow(fn, message)&lt;br /&gt;
	local succeeded, actualMessage = pcall(fn)&lt;br /&gt;
	if succeeded then&lt;br /&gt;
	    return&lt;br /&gt;
	end&lt;br /&gt;
	-- For strings, strip the line number added to the error message&lt;br /&gt;
	actualMessage = type(actualMessage) == &amp;#039;string&amp;#039; &lt;br /&gt;
		and string.match(actualMessage, &amp;#039;Module:[^:]*:[0-9]*: (.*)&amp;#039;)&lt;br /&gt;
		or actualMessage&lt;br /&gt;
	DebugHelper.raise({&lt;br /&gt;
		ScribuntoUnit = true,&lt;br /&gt;
		actual = actualMessage,&lt;br /&gt;
		text = string.format(&amp;#039;Expected no exception, but got exception with message %s&amp;#039;,&lt;br /&gt;
			tostring(actualMessage)&lt;br /&gt;
		),&lt;br /&gt;
		message = message&lt;br /&gt;
	}, 2)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Creates a new test suite.&lt;br /&gt;
-- @param o a table with test functions (alternatively, the functions can be added later to the returned suite)&lt;br /&gt;
-- &lt;br /&gt;
function ScribuntoUnit:new(o)&lt;br /&gt;
    o = o or {}&lt;br /&gt;
    setmetatable(o, {__index = self})&lt;br /&gt;
    o.run = function(frame) return self:run(o, frame) end&lt;br /&gt;
    return o&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Resets global counters&lt;br /&gt;
-- &lt;br /&gt;
function ScribuntoUnit:init(frame)&lt;br /&gt;
    self.frame = frame or mw.getCurrentFrame()&lt;br /&gt;
    self.successCount = 0&lt;br /&gt;
    self.failureCount = 0&lt;br /&gt;
    self.skipCount = 0&lt;br /&gt;
    self.results = {}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Runs a single testcase&lt;br /&gt;
-- @param name test nume&lt;br /&gt;
-- @param test function containing assertions&lt;br /&gt;
-- &lt;br /&gt;
function ScribuntoUnit:runTest(suite, name, test)&lt;br /&gt;
    local success, details = pcall(test, suite)&lt;br /&gt;
    &lt;br /&gt;
    if success then&lt;br /&gt;
        self.successCount = self.successCount + 1&lt;br /&gt;
        table.insert(self.results, {name = name, success = true})&lt;br /&gt;
    elseif type(details) ~= &amp;#039;table&amp;#039; or not details.ScribuntoUnit then -- a real error, not a failed assertion&lt;br /&gt;
        self.failureCount = self.failureCount + 1&lt;br /&gt;
        table.insert(self.results, {name = name, error = true, message = &amp;#039;Lua error -- &amp;#039; .. tostring(details)})&lt;br /&gt;
    elseif details.skipped then&lt;br /&gt;
        self.skipCount = self.skipCount + 1&lt;br /&gt;
        table.insert(self.results, {name = name, skipped = true})&lt;br /&gt;
    else&lt;br /&gt;
        self.failureCount = self.failureCount + 1&lt;br /&gt;
        local message = details.source&lt;br /&gt;
        if details.message then&lt;br /&gt;
            message = message .. details.message .. &amp;quot;\n&amp;quot;&lt;br /&gt;
        end&lt;br /&gt;
        message = message .. details.text&lt;br /&gt;
        table.insert(self.results, {name = name, error = true, message = message, expected = details.expected, actual = details.actual, testname = details.message})&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Runs all tests and displays the results.&lt;br /&gt;
-- &lt;br /&gt;
function ScribuntoUnit:runSuite(suite, frame)&lt;br /&gt;
    self:init(frame)&lt;br /&gt;
	local names = {}&lt;br /&gt;
    for name in pairs(suite) do&lt;br /&gt;
        if name:find(&amp;#039;^test&amp;#039;) then&lt;br /&gt;
			table.insert(names, name)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
	table.sort(names) -- Put tests in alphabetical order.&lt;br /&gt;
	for i, name in ipairs(names) do&lt;br /&gt;
		local func = suite[name]&lt;br /&gt;
		self:runTest(suite, name, func)&lt;br /&gt;
	end&lt;br /&gt;
    return {&lt;br /&gt;
        successCount = self.successCount,&lt;br /&gt;
        failureCount = self.failureCount,&lt;br /&gt;
        skipCount = self.skipCount,&lt;br /&gt;
        results = self.results,&lt;br /&gt;
    }&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- #invoke entry point for running the tests.&lt;br /&gt;
-- Can be called without a frame, in which case it will use mw.log for output&lt;br /&gt;
-- @param displayMode see displayResults()&lt;br /&gt;
-- &lt;br /&gt;
function ScribuntoUnit:run(suite, frame)&lt;br /&gt;
    local testData = self:runSuite(suite, frame)&lt;br /&gt;
    if frame and frame.args then&lt;br /&gt;
        return self:displayResults(testData, frame.args.displayMode or &amp;#039;table&amp;#039;)&lt;br /&gt;
    else&lt;br /&gt;
        return self:displayResults(testData, &amp;#039;log&amp;#039;)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-------------------------------------------------------------------------------&lt;br /&gt;
-- Displays test results &lt;br /&gt;
-- @param displayMode: &amp;#039;table&amp;#039;, &amp;#039;log&amp;#039; or &amp;#039;short&amp;#039;&lt;br /&gt;
-- &lt;br /&gt;
function ScribuntoUnit:displayResults(testData, displayMode)&lt;br /&gt;
    if displayMode == &amp;#039;table&amp;#039; then&lt;br /&gt;
        return self:displayResultsAsTable(testData)&lt;br /&gt;
    elseif displayMode == &amp;#039;log&amp;#039; then&lt;br /&gt;
        return self:displayResultsAsLog(testData)&lt;br /&gt;
    elseif displayMode == &amp;#039;short&amp;#039; then&lt;br /&gt;
        return self:displayResultsAsShort(testData)&lt;br /&gt;
    else&lt;br /&gt;
        error(&amp;#039;unknown display mode&amp;#039;)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function ScribuntoUnit:displayResultsAsLog(testData)&lt;br /&gt;
    if testData.failureCount &amp;gt; 0 then&lt;br /&gt;
        mw.log(&amp;#039;FAILURES!!!&amp;#039;)&lt;br /&gt;
    elseif testData.skipCount &amp;gt; 0 then&lt;br /&gt;
        mw.log(&amp;#039;Some tests could not be executed without a frame and have been skipped. Invoke this test suite as a template to run all tests.&amp;#039;)&lt;br /&gt;
    end&lt;br /&gt;
    mw.log(string.format(&amp;#039;Assertions: success: %d, error: %d, skipped: %d&amp;#039;, testData.successCount, testData.failureCount, testData.skipCount))&lt;br /&gt;
    mw.log(&amp;#039;-------------------------------------------------------------------------------&amp;#039;)&lt;br /&gt;
    for _, result in ipairs(testData.results) do&lt;br /&gt;
        if result.error then&lt;br /&gt;
            mw.log(string.format(&amp;#039;%s: %s&amp;#039;, result.name, result.message))&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function ScribuntoUnit:displayResultsAsShort(testData)&lt;br /&gt;
    local text = string.format(cfg.shortResultsFormat, testData.successCount, testData.failureCount, testData.skipCount)&lt;br /&gt;
    if testData.failureCount &amp;gt; 0 then&lt;br /&gt;
        text = &amp;#039;&amp;lt;span class=&amp;quot;error&amp;quot;&amp;gt;&amp;#039; .. text .. &amp;#039;&amp;lt;/span&amp;gt;&amp;#039;&lt;br /&gt;
    end&lt;br /&gt;
    return text&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function ScribuntoUnit:displayResultsAsTable(testData)&lt;br /&gt;
    local successIcon, failIcon = self.frame:preprocess(cfg.successIndicator), self.frame:preprocess(cfg.failureIndicator)&lt;br /&gt;
    local text = &amp;#039;&amp;#039;&lt;br /&gt;
	if testData.failureCount &amp;gt; 0 then&lt;br /&gt;
		local msg = mw.message.newRawMessage(cfg.failureSummary, testData.failureCount):plain()&lt;br /&gt;
		msg = self.frame:preprocess(msg)&lt;br /&gt;
		if cfg.failureCategory then&lt;br /&gt;
			msg = cfg.failureCategory .. msg&lt;br /&gt;
		end&lt;br /&gt;
		text = text .. failIcon .. &amp;#039; &amp;#039; .. msg .. &amp;#039;\n&amp;#039;&lt;br /&gt;
	else&lt;br /&gt;
		text = text .. successIcon .. &amp;#039; &amp;#039; .. cfg.successSummary .. &amp;#039;\n&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
    text = text .. &amp;#039;{| class=&amp;quot;wikitable mw-collapsible mw-collapsed scribunto-test-table&amp;quot;\n&amp;#039;&lt;br /&gt;
    text = text .. &amp;#039;|-\n! colspan=&amp;quot;4&amp;quot; | Unit tests\n&amp;#039;&lt;br /&gt;
    text = text .. &amp;#039;|-\n!\n! &amp;#039; .. cfg.nameString .. &amp;#039;\n! &amp;#039; .. cfg.expectedString .. &amp;#039;\n! &amp;#039; .. cfg.actualString .. &amp;#039;\n&amp;#039;&lt;br /&gt;
    for _, result in ipairs(testData.results) do&lt;br /&gt;
        text = text .. &amp;#039;|-\n&amp;#039;&lt;br /&gt;
        if result.error then&lt;br /&gt;
            text = text .. &amp;#039;| &amp;#039; .. failIcon .. &amp;#039;\n| &amp;#039;&lt;br /&gt;
            if (result.expected and result.actual) then&lt;br /&gt;
            	local name = result.name&lt;br /&gt;
            	if result.testname then&lt;br /&gt;
            		name = name .. &amp;#039; / &amp;#039; .. result.testname&lt;br /&gt;
            	end&lt;br /&gt;
                text = text .. name .. &amp;#039;\n| &amp;#039; .. mw.text.nowiki(tostring(result.expected)) .. &amp;#039;\n| &amp;#039; .. mw.text.nowiki(tostring(result.actual)) .. &amp;#039;\n&amp;#039;&lt;br /&gt;
            else&lt;br /&gt;
                text = text .. result.name .. &amp;#039;\n| &amp;#039; .. &amp;#039; colspan=&amp;quot;2&amp;quot; | &amp;#039; .. mw.text.nowiki(result.message) .. &amp;#039;\n&amp;#039;&lt;br /&gt;
            end&lt;br /&gt;
        else&lt;br /&gt;
            text = text .. &amp;#039;| &amp;#039; .. successIcon .. &amp;#039;\n| &amp;#039; .. result.name .. &amp;#039;\n|\n|\n&amp;#039;&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
    text = text .. &amp;#039;|}\n&amp;#039;&lt;br /&gt;
    return text&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return ScribuntoUnit&lt;/div&gt;</summary>
		<author><name>starcitizen&gt;Alistair3149</name></author>
	</entry>
</feed>