Module:Template test case: Difference between revisions

m
87 revisions imported from wikipedia:Module:Template_test_case
(add nowiki exports and finish the NowikiInvocation class)
m (87 revisions imported from wikipedia:Module:Template_test_case)
 
(81 intermediate revisions by 11 users not shown)
Line 1: Line 1:
-- This module provides several methods to generate test cases.
--[[
  A module for generating test case templates.
 
  This module incorporates code from the English Wikipedia's "Testcase table"
  module,[1] written by Frietjes [2] with contributions by Mr. Stradivarius [3]
  and Jackmcbarn,[4] and the English Wikipedia's "Testcase rows" module,[5]
  written by Mr. Stradivarius.
 
  The "Testcase table" and "Testcase rows" modules are released under the
  CC BY-SA 3.0 License [6] and the GFDL.[7]
 
  License: CC BY-SA 3.0 and the GFDL
  Author: Mr. Stradivarius
 
  [1] https://en.wikipedia.org/wiki/Module:Testcase_table
  [2] https://en.wikipedia.org/wiki/User:Frietjes
  [3] https://en.wikipedia.org/wiki/User:Mr._Stradivarius
  [4] https://en.wikipedia.org/wiki/User:Jackmcbarn
  [5] https://en.wikipedia.org/wiki/Module:Testcase_rows
  [6] https://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License
  [7] https://en.wikipedia.org/wiki/Wikipedia:Text_of_the_GNU_Free_Documentation_License
]]
 
-- Load required modules
local yesno = require('Module:Yesno')
 
-- Set constants
local DATA_MODULE = 'Module:Template test case/data'


local mTableTools = require('Module:TableTools')
-------------------------------------------------------------------------------
local libraryUtil = require('libraryUtil')
-- Shared methods
local checkType = libraryUtil.checkType
-------------------------------------------------------------------------------


local TEMPLATE_NAME_MAGIC_WORD = '<TEMPLATE_NAME>'
local function message(self, key, ...)
local TEMPLATE_NAME_MAGIC_WORD_ESCAPED = TEMPLATE_NAME_MAGIC_WORD:gsub('%p', '%%%0')
-- This method is added to classes that need to deal with messages from the
-- config module.
local msg = self.cfg.msg[key]
if select(1, ...) then
return mw.message.newRawMessage(msg, ...):plain()
else
return msg
end
end


-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- Helper functions
-- Template class
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------


--[[
local Template = {}
local function validateTemplateOptions()
 
-- Add the template names for the first two templates if they weren't
Template.memoizedMethods = {
-- specified.
-- Names of methods to be memoized in each object. This table should only
do
-- hold methods with no parameters.
local title = mw.title.getCurrentTitle().basePageTitle
getFullPage = true,
local template
getName = true,
if title.namespace == 10 then
makeHeader = true,
template = title.text
getOutput = true
elseif title.namespace == 0 then
}
template = ':' .. title.prefixedText
 
else
function Template.new(invocationObj, options)
template = title.prefixedText
local obj = {}
 
-- Set input
for k, v in pairs(options or {}) do
if not Template[k] then
obj[k] = v
end
end
local templatePage = title.prefixedText
end
templateOptions[1] = templateOptions[1] or {}
obj._invocation = invocationObj
templateOptions[1].templatePage = templateOptions[1].template or templatePage
 
templateOptions[1].template = templateOptions[1].template or template
-- Validate input
templateOptions[2] = templateOptions[2] or {}
if not obj.template and not obj.title then
templateOptions[2].templatePage = templateOptions[2].template or templatePage .. '/sandbox'
error('no template or title specified', 2)
templateOptions[2].template = templateOptions[2].template or template .. '/sandbox'
end
end


-- Validate options for three or more templates.
-- Memoize expensive method calls
if #templateOptions >= 3 then
local memoFuncs = {}
for i = 3, #templateOptions do
return setmetatable(obj, {
local template = templateOptions[i].template
__index = function (t, key)
if not template then
if Template.memoizedMethods[key] then
error('arguments for a third or subsequent ' ..
local func = memoFuncs[key]
'template were found, but no template name ' ..
if not func then
'was specified', 3)
local val = Template[key](t)
func = function () return val end
memoFuncs[key] = func
end
return func
else
return Template[key]
end
end
end
end
})
end
function Template:getFullPage()
if not self.template then
return self.title.prefixedText
elseif self.template:sub(1, 7) == '#invoke' then
return 'Module' .. self.template:sub(8):gsub('|.*', '')
else
local strippedTemplate, hasColon = self.template:gsub('^:', '', 1)
hasColon = hasColon > 0
local ns = strippedTemplate:match('^(.-):')
ns = ns and mw.site.namespaces[ns]
if ns then
return strippedTemplate
elseif hasColon then
return strippedTemplate -- Main namespace
else
return mw.site.namespaces[10].name .. ':' .. strippedTemplate
end
end
end
function Template:getName()
if self.template then
return self.template
else
return require('Module:Template invocation').name(self.title)
end
end
function Template:makeLink(display)
if display then
return string.format('[[:%s|%s]]', self:getFullPage(), display)
else
return string.format('[[:%s]]', self:getFullPage())
end
end
function Template:makeBraceLink(display)
display = display or self:getName()
local link = self:makeLink(display)
return mw.text.nowiki('{{') .. link .. mw.text.nowiki('}}')
end
function Template:makeHeader()
return self.heading or self:makeBraceLink()
end
function Template:getInvocation(format)
local invocation = self._invocation:getInvocation{
template = self:getName(),
requireMagicWord = self.requireMagicWord,
}
if format == 'code' then
invocation = '<code>' .. mw.text.nowiki(invocation) .. '</code>'
elseif format == 'kbd' then
invocation = '<kbd>' .. mw.text.nowiki(invocation) .. '</kbd>'
elseif format == 'plain' then
invocation = mw.text.nowiki(invocation)
else
-- Default is pre tags
invocation = mw.text.encode(invocation, '&')
invocation = '<pre style="white-space: pre-wrap;">' .. invocation .. '</pre>'
invocation = mw.getCurrentFrame():preprocess(invocation)
end
end
return invocation
end
end
--]]


local function parseTemplateOptions(origOptions)
function Template:getOutput()
-- Given a table of raw options, returns a table of general options
local protect = require('Module:Protect')
-- and template options.
-- calling self._invocation:getOutput{...}
local templateOptions = mTableTools.numData(origOptions, true)
return protect(self._invocation.getOutput)(self._invocation, {
local options = templateOptions.other
template = self:getName(),
templateOptions.other = nil
requireMagicWord = self.requireMagicWord,
return options, templateOptions
})
end
end


-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- Invocation class
-- TestCase class
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------


local Invocation = {}
local TestCase = {}
Invocation.__index = Invocation
TestCase.__index = TestCase
TestCase.message = message -- add the message method
 
TestCase.renderMethods = {
-- Keys in this table are values of the "format" option, values are the
-- method for rendering that format.
columns = 'renderColumns',
rows = 'renderRows',
tablerows = 'renderRows',
inline = 'renderInline',
cells = 'renderCells',
default = 'renderDefault'
}
 
function TestCase.new(invocationObj, options, cfg)
local obj = setmetatable({}, TestCase)
obj.cfg = cfg
 
-- Separate general options from template options. Template options are
-- numbered, whereas general options are not.
local generalOptions, templateOptions = {}, {}
for k, v in pairs(options) do
local prefix, num
if type(k) == 'string' then
prefix, num = k:match('^(.-)([1-9][0-9]*)$')
end
if prefix then
num = tonumber(num)
templateOptions[num] = templateOptions[num] or {}
templateOptions[num][prefix] = v
else
generalOptions[k] = v
end
end
 
-- Set general options
generalOptions.showcode = yesno(generalOptions.showcode)
generalOptions.showheader = yesno(generalOptions.showheader) ~= false
generalOptions.showcaption = yesno(generalOptions.showcaption) ~= false
generalOptions.collapsible = yesno(generalOptions.collapsible)
generalOptions.notcollapsed = yesno(generalOptions.notcollapsed)
generalOptions.wantdiff = yesno(generalOptions.wantdiff)
obj.options = generalOptions
 
-- Preprocess template args
for num, t in pairs(templateOptions) do
if t.showtemplate ~= nil then
t.showtemplate = yesno(t.showtemplate)
end
end
 
-- Set up first two template options tables, so that if only the
-- "template3" is specified it isn't made the first template when the
-- the table options array is compressed.
templateOptions[1] = templateOptions[1] or {}
templateOptions[2] = templateOptions[2] or {}
 
-- Allow the "template" option to override the "template1" option for
-- backwards compatibility with [[Module:Testcase table]].
if generalOptions.template then
templateOptions[1].template = generalOptions.template
end
 
-- Add default template options
if templateOptions[1].template and not templateOptions[2].template then
templateOptions[2].template = templateOptions[1].template ..
'/' .. obj.cfg.sandboxSubpage
end
if not templateOptions[1].template then
templateOptions[1].title = mw.title.getCurrentTitle().basePageTitle
end
if not templateOptions[2].template then
templateOptions[2].title = templateOptions[1].title:subPageTitle(
obj.cfg.sandboxSubpage
)
end
 
-- Remove template options for any templates where the showtemplate
-- argument is false. This prevents any output for that template.
for num, t in pairs(templateOptions) do
if t.showtemplate == false then
templateOptions[num] = nil
end
end
 
-- Check for missing template names.
for num, t in pairs(templateOptions) do
if not t.template and not t.title then
error(obj:message(
'missing-template-option-error',
num, num
), 2)
end
end
 
-- Compress templateOptions table so we can iterate over it with ipairs.
templateOptions = (function (t)
local nums = {}
for num in pairs(t) do
nums[#nums + 1] = num
end
table.sort(nums)
local ret = {}
for i, num in ipairs(nums) do
ret[i] = t[num]
end
return ret
end)(templateOptions)
 
-- Don't require the __TEMPLATENAME__ magic word for nowiki invocations if
-- there is only one template being output.
if #templateOptions <= 1 then
templateOptions[1].requireMagicWord = false
end
 
mw.logObject(templateOptions)
 
-- Make the template objects
obj.templates = {}
for i, options in ipairs(templateOptions) do
table.insert(obj.templates, Template.new(invocationObj, options))
end
 
-- Add tracking categories. At the moment we are only tracking templates
-- that use any "heading" parameters or an "output" parameter.
obj.categories = {}
for k, v in pairs(options) do
if type(k) == 'string' and k:find('heading') then
obj.categories['Test cases using heading parameters'] = true
elseif k == 'output' then
obj.categories['Test cases using output parameter'] = true
end
end


function Invocation.new()
local obj = setmetatable({}, TableInvocation)
return obj
return obj
end
end


function Invocation:setOptions(t)
function TestCase:getTemplateOutput(templateObj)
self._options = t
local output = templateObj:getOutput()
if self.options.resetRefs then
mw.getCurrentFrame():extensionTag('references')
end
return output
end
end


function Invocation:getOptions()
function TestCase:templateOutputIsEqual()
return self._options
-- Returns a boolean showing whether all of the template outputs are equal.
-- The random parts of strip markers (see [[Help:Strip markers]]) are
-- removed before comparison. This means a strip marker can contain anything
-- and still be treated as equal, but it solves the problem of otherwise
-- identical wikitext not returning as exactly equal.
local function normaliseOutput(obj)
local out = obj:getOutput()
-- Remove the random parts from strip markers.
out = out:gsub('(\127\'"`UNIQ.-)%-%x+%-(QINU`"\'\127)', '%1%2')
return out
end
local firstOutput = normaliseOutput(self.templates[1])
for i = 2, #self.templates do
local output = normaliseOutput(self.templates[i])
if output ~= firstOutput then
return false
end
end
return true
end
 
function TestCase:makeCollapsible(s)
local title = self.options.title or self.templates[1]:makeHeader()
if self.options.titlecode then
title = self.templates[1]:getInvocation('kbd')
end
local isEqual = self:templateOutputIsEqual()
local root = mw.html.create('table')
if self.options.wantdiff then
root
:addClass('mw-collapsible')
if self.options.notcollapsed == false then
root
:addClass('mw-collapsed')
end
root
:css('background-color', 'transparent')
:css('width', '100%')
:css('border', 'solid silver 1px')
:tag('tr')
:tag('th')
:css('background-color', isEqual and 'yellow' or '#90a8ee')
:wikitext(title)
:done()
:done()
:tag('tr')
:tag('td')
:newline()
:wikitext(s)
:newline()
else
root
:addClass('mw-collapsible')
if self.options.notcollapsed == false then
root
:addClass('mw-collapsed')
end
if self.options.notcollapsed ~= true or false then
root
:addClass(isEqual and 'mw-collapsed' or nil)
end
root
:css('background-color', 'transparent')
:css('width', '100%')
:css('border', 'solid silver 1px')
:tag('tr')
:tag('th')
:css('background-color', isEqual and 'lightgreen' or 'yellow')
:wikitext(title)
:done()
:done()
:tag('tr')
:tag('td')
:newline()
:wikitext(s)
:newline()
end
return tostring(root)
end
 
function TestCase:renderColumns()
local root = mw.html.create()
if self.options.showcode then
root
:wikitext(self.templates[1]:getInvocation())
:newline()
end
 
local tableroot = root:tag('table')
 
if self.options.showheader then
-- Caption
if self.options.showcaption then
tableroot
:addClass(self.options.class)
:cssText(self.options.style)
:tag('caption')
:wikitext(self.options.caption or self:message('columns-header'))
end
 
-- Headers
local headerRow = tableroot:tag('tr')
if self.options.rowheader then
-- rowheader is correct here. We need to add another th cell if
-- rowheader is set further down, even if heading0 is missing.
headerRow:tag('th'):wikitext(self.options.heading0)
end
local width
if #self.templates > 0 then
width = tostring(math.floor(100 / #self.templates)) .. '%'
else
width = '100%'
end
for i, obj in ipairs(self.templates) do
headerRow
:tag('th')
:css('width', width)
:wikitext(obj:makeHeader())
end
end
 
-- Row header
local dataRow = tableroot:tag('tr'):css('vertical-align', 'top')
if self.options.rowheader then
dataRow:tag('th')
:attr('scope', 'row')
:wikitext(self.options.rowheader)
end
-- Template output
for i, obj in ipairs(self.templates) do
if self.options.output == 'nowiki+' then
dataRow:tag('td')
:newline()
:wikitext(self.options.before)
:wikitext(self:getTemplateOutput(obj))
:wikitext(self.options.after)
:wikitext('<pre style="white-space: pre-wrap;">')
:wikitext(mw.text.nowiki(self.options.before or ""))
:wikitext(mw.text.nowiki(self:getTemplateOutput(obj)))
:wikitext(mw.text.nowiki(self.options.after or ""))
:wikitext('</pre>')
elseif self.options.output == 'nowiki' then
dataRow:tag('td')
:newline()
:wikitext(mw.text.nowiki(self.options.before or ""))
:wikitext(mw.text.nowiki(self:getTemplateOutput(obj)))
:wikitext(mw.text.nowiki(self.options.after or ""))
else
dataRow:tag('td')
:newline()
:wikitext(self.options.before)
:wikitext(self:getTemplateOutput(obj))
:wikitext(self.options.after)
end
end
return tostring(root)
end
 
function TestCase:renderRows()
local root = mw.html.create()
if self.options.showcode then
root
:wikitext(self.templates[1]:getInvocation())
:newline()
end
 
local tableroot = root:tag('table')
tableroot
:addClass(self.options.class)
:cssText(self.options.style)
 
if self.options.caption then
tableroot
:tag('caption')
:wikitext(self.options.caption)
end
 
for _, obj in ipairs(self.templates) do
local dataRow = tableroot:tag('tr')
-- Header
if self.options.showheader then
if self.options.format == 'tablerows' then
dataRow:tag('th')
:attr('scope', 'row')
:css('vertical-align', 'top')
:css('text-align', 'left')
:wikitext(obj:makeHeader())
dataRow:tag('td')
:css('vertical-align', 'top')
:css('padding', '0 1em')
:wikitext('→')
else
dataRow:tag('td')
:css('text-align', 'center')
:css('font-weight', 'bold')
:wikitext(obj:makeHeader())
dataRow = tableroot:tag('tr')
end
end
-- Template output
if self.options.output == 'nowiki+' then
dataRow:tag('td')
:newline()
:wikitext(self:getTemplateOutput(obj))
:wikitext('<pre style="white-space: pre-wrap;">')
:wikitext(mw.text.nowiki(self:getTemplateOutput(obj)))
:wikitext('</pre>')
elseif self.options.output == 'nowiki' then
dataRow:tag('td')
:newline()
:wikitext(mw.text.nowiki(self:getTemplateOutput(obj)))
else
dataRow:tag('td')
:newline()
:wikitext(self:getTemplateOutput(obj))
end
end
 
return tostring(root)
end
 
function TestCase:renderInline()
local arrow = mw.language.getContentLanguage():getArrow('forwards')
local ret = {}
for i, obj in ipairs(self.templates) do
local line = {}
line[#line + 1] = self.options.prefix or '* '
if self.options.showcode then
line[#line + 1] = obj:getInvocation('code')
line[#line + 1] = ' '
line[#line + 1] = arrow
line[#line + 1] = ' '
end
if self.options.output == 'nowiki+' then
line[#line + 1] = self:getTemplateOutput(obj)
line[#line + 1] = '<pre style="white-space: pre-wrap;">'
line[#line + 1] = mw.text.nowiki(self:getTemplateOutput(obj))
line[#line + 1] = '</pre>'
elseif self.options.output == 'nowiki' then
line[#line + 1] = mw.text.nowiki(self:getTemplateOutput(obj))
else
line[#line + 1] = self:getTemplateOutput(obj)
end
ret[#ret + 1] = table.concat(line)
end
if self.options.addline then
local line = {}
line[#line + 1] = self.options.prefix or '* '
line[#line + 1] = self.options.addline
ret[#ret + 1] = table.concat(line)
end
return table.concat(ret, '\n')
end
 
function TestCase:renderCells()
local root = mw.html.create()
local dataRow = root:tag('tr')
dataRow
:css('vertical-align', 'top')
:addClass(self.options.class)
:cssText(self.options.style)
 
-- Row header
if self.options.rowheader then
dataRow:tag('th')
:attr('scope', 'row')
:newline()
:wikitext(self.options.rowheader or self:message('row-header'))
end
-- Caption
if self.options.showcaption then
dataRow:tag('th')
:attr('scope', 'row')
:newline()
:wikitext(self.options.caption or self:message('columns-header'))
end
 
-- Show code
if self.options.showcode then
dataRow:tag('td')
:newline()
:wikitext(self:getInvocation('code'))
end
 
-- Template output
for i, obj in ipairs(self.templates) do
if self.options.output == 'nowiki+' then
dataRow:tag('td')
:newline()
:wikitext(self.options.before)
:wikitext(self:getTemplateOutput(obj))
:wikitext(self.options.after)
:wikitext('<pre style="white-space: pre-wrap;">')
:wikitext(mw.text.nowiki(self.options.before or ""))
:wikitext(mw.text.nowiki(self:getTemplateOutput(obj)))
:wikitext(mw.text.nowiki(self.options.after or ""))
:wikitext('</pre>')
elseif self.options.output == 'nowiki' then
dataRow:tag('td')
:newline()
:wikitext(mw.text.nowiki(self.options.before or ""))
:wikitext(mw.text.nowiki(self:getTemplateOutput(obj)))
:wikitext(mw.text.nowiki(self.options.after or ""))
else
dataRow:tag('td')
:newline()
:wikitext(self.options.before)
:wikitext(self:getTemplateOutput(obj))
:wikitext(self.options.after)
end
end
 
 
return tostring(root)
end
 
function TestCase:renderDefault()
local ret = {}
if self.options.showcode then
ret[#ret + 1] = self.templates[1]:getInvocation()
end
for i, obj in ipairs(self.templates) do
ret[#ret + 1] = '<div style="clear: both;"></div>'
if self.options.showheader then
ret[#ret + 1] = obj:makeHeader()
end
if self.options.output == 'nowiki+' then
ret[#ret + 1] = self:getTemplateOutput(obj) .. '<pre style="white-space: pre-wrap;">' .. mw.text.nowiki(self:getTemplateOutput(obj)) .. '</pre>'
elseif self.options.output == 'nowiki' then
ret[#ret + 1] = mw.text.nowiki(self:getTemplateOutput(obj))
else
ret[#ret + 1] = self:getTemplateOutput(obj)
end
end
return table.concat(ret, '\n\n')
end
 
function TestCase:__tostring()
local format = self.options.format
local method = format and TestCase.renderMethods[format] or 'renderDefault'
local ret = self[method](self)
if self.options.collapsible then
ret = self:makeCollapsible(ret)
end
for cat in pairs(self.categories) do
ret = ret .. string.format('[[Category:%s]]', cat)
end
return ret
end
end


Line 82: Line 677:
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------


local NowikiInvocation = setmetatable({}, Invocation)
local NowikiInvocation = {}
NowikiInvocation.__index = NowikiInvocation
NowikiInvocation.__index = NowikiInvocation
NowikiInvocation.message = message -- Add the message method


function NowikiInvocation.new(args)
function NowikiInvocation.new(invocation, cfg)
checkType('NowikiInvocation.new', 'args', args, 'table')
local obj = setmetatable({}, NowikiInvocation)
local obj = Invocation.new()
obj.cfg = cfg
setmetatable(obj, NowikiInvocation)
invocation = mw.text.unstrip(invocation)
 
-- Decode HTML entities for <, >, and ". This means that HTML entities in
obj.invocation = mw.text.unstrip(args.invocation)
-- the original code must be escaped as e.g. &amp;lt;, which is unfortunate,
args.invocation = nil
-- but it is the best we can do as the distinction between <, >, " and &lt;,
local options = {}
-- &gt;, &quot; is lost during the original nowiki operation.
for k, v in pairs(args) do
invocation = invocation:gsub('&lt;', '<')
options[k] = v
invocation = invocation:gsub('&gt;', '>')
end
invocation = invocation:gsub('&quot;', '"')
obj.options = options
obj.invocation = invocation
 
return obj
return obj
end
end


function NowikiInvocation:getInvocation(template)
function NowikiInvocation:getInvocation(options)
template = template:gsub('%%', '%%%%') -- Escape a % with %%
local template = options.template:gsub('%%', '%%%%') -- Escape "%" with "%%"
local invocation, count = self.invocation:gsub(
local invocation, count = self.invocation:gsub(
TEMPLATE_NAME_MAGIC_WORD_ESCAPED,
self.cfg.templateNameMagicWordPattern,
template
template
)
)
if count < 1 then
if options.requireMagicWord ~= false and count < 1 then
error(string.format(
error(self:message(
"the template invocation must include '%s' in place " ..
'nowiki-magic-word-error',
"of the template name",
self.cfg.templateNameMagicWord
TEMPLATE_NAME_MAGIC_WORD
))
))
end
end
Line 117: Line 711:
end
end


function NowikiInvocation:getOutput(template)
function NowikiInvocation:getOutput(options)
local invocation = self:getInvocation(template)
local invocation = self:getInvocation(options)
return mw.getCurrentFrame():preprocess(invocation)
return mw.getCurrentFrame():preprocess(invocation)
end
end
Line 128: Line 722:
local TableInvocation = {}
local TableInvocation = {}
TableInvocation.__index = TableInvocation
TableInvocation.__index = TableInvocation
TableInvocation.message = message -- Add the message method


function TableInvocation.new(args)
function TableInvocation.new(invokeArgs, nowikiCode, cfg)
checkType('TableInvocation.new', 'args', args, 'table')
local obj = setmetatable({}, TableInvocation)
local obj = Invocation.new()
obj.cfg = cfg
setmetatable(obj, TableInvocation)
 
local rawOptions, invokeArgs = {}, {}
for k, v in pairs(args) do
local optionKey = type(k) == 'string' and k:match('^_(.*)$')
if optionKey then
rawOptions[optionKey] = v
else
invokeArgs[k] = v
end
end
obj.invokeArgs = invokeArgs
obj.invokeArgs = invokeArgs
local options, templateOptions = parseTemplateOptions(rawOptions)
obj.code = nowikiCode
obj:setOptions(options)
obj:setTemplateOptions(templateOptions)
 
return obj
return obj
end
end


function TableInvocation:getInvocation(template)
function TableInvocation:getInvocation(options)
return require('Module:Template invocation').invocation(
if self.code then
template,
local nowikiObj = NowikiInvocation.new(self.code, self.cfg)
self.invokeArgs
return nowikiObj:getInvocation(options)
)
else
return require('Module:Template invocation').invocation(
options.template,
self.invokeArgs
)
end
end
end


function TableInvocation:getOutput(template)
function TableInvocation:getOutput(options)
if (options.template:sub(1, 7) == '#invoke') then
local moduleCall = mw.text.split(options.template, '|', true)
local args = mw.clone(self.invokeArgs)
table.insert(args, 1, moduleCall[2])
return mw.getCurrentFrame():callParserFunction(moduleCall[1], args)
end
return mw.getCurrentFrame():expandTemplate{
return mw.getCurrentFrame():expandTemplate{
title = template,
title = options.template,
args = self.invokeArgs
args = self.invokeArgs
}
}
Line 166: Line 758:


-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- TestCase class
-- Bridge functions
--
-- These functions translate template arguments into forms that can be accepted
-- by the different classes, and return the results.
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------


local TestCase = {}
local bridge = {}
TestCase.__index = TestCase


function TestCase.new(invocationObj)
function bridge.table(args, cfg)
checkType('TestCase.new', 'invocationObj', invocationObj, 'table')
cfg = cfg or mw.loadData(DATA_MODULE)
local obj = setmetatable({}, TestCase)
return obj
end


--------------------------------------------------------------------------------
local options, invokeArgs = {}, {}
-- Test case display functions
for k, v in pairs(args) do
--
local optionKey = type(k) == 'string' and k:match('^_(.*)$')
-- Test case display functions produce the wikitext to display the template
if optionKey then
-- output for one test case. For example, one function might produce templates
if type(v) == 'string' then
-- aligned horizontally, and another function might produce templates aligned
v = v:match('^%s*(.-)%s*$') -- trim whitespace
-- one below the other.
end
--
if v ~= '' then
-- They are named functions that accept the following parameters:
options[optionKey] = v
-- * templates - an array of subtables containing data about each template to be
--    displayed. These subtables can contain the following values:
--    * result - the expanded wikitext from the template.
--    * invocation - the original unexpanded wikitext that the output was
--        generated from. This may be nil if the invocation is not available.
--    * name - the name of the template.
--    * link - a normal wikilink to the template page (displays as
--        "Template:Foo").
--    * braceLink - a wikilink to the template page formatted like the {{tl}}
--        template, i.e. it displays as "{{Foo}}".
--    * heading - a heading to display above the template output.
--------------------------------------------------------------------------------
 
-------------------------------------------------------------------------------
-- Exports
-------------------------------------------------------------------------------
 
-- Table-based exports
 
local function getTableArgs(frame, wrappers)
return require('Module:Arguments').getArgs(frame, {
wrappers = wrappers,
valueFunc = function (k, v)
if type(k) == 'string' and k:find('^_') then
v = mw.text.trim(v)
if v ~= '' then
return v
end
else
return v
end
end
else
invokeArgs[k] = v
end
end
})
end
end


local p = {}
-- Allow passing a nowiki invocation as an option. While this means users
-- have to pass in the code twice, whitespace is preserved and &lt; etc.
-- will work as intended.
local nowikiCode = options.code
options.code = nil


function p._table(args)
local invocationObj = TableInvocation.new(invokeArgs, nowikiCode, cfg)
local invocation = TableInvocation.new(args)
local testCaseObj = TestCase.new(invocationObj, options, cfg)
return invocation
return tostring(testCaseObj)
end
end


function p.table(frame)
function bridge.nowiki(args, cfg)
return p._table(getTableArgs(frame, 'Template:Test case'))
cfg = cfg or mw.loadData(DATA_MODULE)
 
local code = args.code or args[1]
local invocationObj = NowikiInvocation.new(code, cfg)
args.code = nil
args[1] = nil
-- Assume we want to see the code as we already passed it in.
args.showcode = args.showcode or true
local testCaseObj = TestCase.new(invocationObj, args, cfg)
return tostring(testCaseObj)
end
end


function p.columns(frame)
-------------------------------------------------------------------------------
local args = getTableArgs(frame, 'Template:Testcase table')
-- Exports
args._format = 'columns'
-------------------------------------------------------------------------------
return p._table(args)
end


function p.rows(frame)
local p = {}
local args = getTableArgs(frame, 'Template:Testcase rows')
args._format = 'rows'
return p._table(args)
end


-- Nowiki-based exports
function p.main(frame, cfg)
cfg = cfg or mw.loadData(DATA_MODULE)


function p._nowiki(args)
-- Load the wrapper config, if any.
local invocation = NowikiInvocation.new(args)
local wrapperConfig
return invocation
if frame.getParent then
end
local title = frame:getParent():getTitle()
local template = title:gsub(cfg.sandboxSubpagePattern, '')
wrapperConfig = cfg.wrappers[template]
end


function p.nowiki(frame)
-- Work out the function we will call, use it to generate the config for
local args = require('Module:Arguments').getArgs(frame, {
-- Module:Arguments, and use Module:Arguments to find the arguments passed
wrappers = 'Template:Test case'
-- by the user.
local func = wrapperConfig and wrapperConfig.func or 'table'
local userArgs = require('Module:Arguments').getArgs(frame, {
parentOnly = wrapperConfig,
frameOnly = not wrapperConfig,
trim = func ~= 'table',
removeBlanks = func ~= 'table'
})
})
return p._nowiki(args)
 
-- Get default args and build the args table. User-specified args overwrite
-- default args.
local defaultArgs = wrapperConfig and wrapperConfig.args or {}
local args = {}
for k, v in pairs(defaultArgs) do
args[k] = v
end
for k, v in pairs(userArgs) do
args[k] = v
end
 
return bridge[func](args, cfg)
end
end


-- Exports for testing
function p._exportClasses() -- For testing
 
function p._exportClasses()
return {
return {
Template = Template,
TestCase = TestCase,
TestCase = TestCase,
Invocation = Invocation,
NowikiInvocation = NowikiInvocation,
NowikiInvocation = NowikiInvocation,
TableInvocation = TableInvocation
TableInvocation = TableInvocation