Editing Module:Template test case
The edit can be undone. Please check the comparison below to verify that this is what you want to do, and then publish the changes below to finish undoing the edit.
Latest revision | Your text | ||
Line 1: | Line 1: | ||
-- | -- This module provides several methods to generate test cases. | ||
-- Load required modules | -- Load required modules | ||
local yesno = require('Module:Yesno') | local yesno = require('Module:Yesno') | ||
local mTableTools = require('Module:TableTools') | |||
-- Set constants | -- Set constants | ||
local DATA_MODULE = 'Module:Template test case/data' | local DATA_MODULE = 'Module:Template test case/data' | ||
------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ||
Line 54: | Line 19: | ||
getFullPage = true, | getFullPage = true, | ||
getName = true, | getName = true, | ||
makeHeading = true, | |||
getOutput = true | getOutput = true | ||
} | } | ||
function Template.outputEquals(...) | |||
-- This function accepts 2 or more template objects and compares their | |||
-- output. If all the outputs are equal, it returns true, and if any of them | |||
-- are different, it returns false. | |||
local n = select('#', ...) | |||
if n < 2 then | |||
error('Template.outputEquals requires at least two arguments', 2) | |||
end | |||
local function normaliseOutput(obj) | |||
local out = obj:getOutput() | |||
-- Remove the random parts from strip markers (see [[Help:Strip markers]]) | |||
out = out:gsub('(%cUNIQ).-(QINU%c)', '%1%2') | |||
return out | |||
end | |||
local prevOutput = normaliseOutput(select(1, ...)) | |||
for i = 2, n do | |||
local output = normaliseOutput(select(i, ...)) | |||
if output ~= prevOutput then | |||
return false | |||
end | |||
prevOutput = output | |||
end | |||
return true | |||
end | |||
function Template.new(invocationObj, options) | function Template.new(invocationObj, options) | ||
Line 94: | Line 84: | ||
function Template:getFullPage() | function Template:getFullPage() | ||
if | if self.template then | ||
local strippedTemplate, hasColon = self.template:gsub('^:', '', 1) | local strippedTemplate, hasColon = self.template:gsub('^:', '', 1) | ||
hasColon = hasColon > 0 | hasColon = hasColon > 0 | ||
Line 110: | Line 96: | ||
return mw.site.namespaces[10].name .. ':' .. strippedTemplate | return mw.site.namespaces[10].name .. ':' .. strippedTemplate | ||
end | end | ||
else | |||
return self.title.prefixedText | |||
end | end | ||
end | end | ||
Line 135: | Line 123: | ||
end | end | ||
function Template: | function Template:makeHeading() | ||
return self.heading or self:makeBraceLink() | return self.heading or self:makeBraceLink() | ||
end | end | ||
function Template:getInvocation(format) | function Template:getInvocation(format) | ||
local invocation = self._invocation:getInvocation | local invocation = self._invocation:getInvocation(self:getName()) | ||
if format == 'code' then | if format == 'code' then | ||
invocation = '<code>' .. mw.text.nowiki(invocation) .. '</code>' | invocation = '<code>' .. mw.text.nowiki(invocation) .. '</code>' | ||
elseif format == 'plain' then | elseif format == 'plain' then | ||
invocation = mw.text.nowiki(invocation) | invocation = mw.text.nowiki(invocation) | ||
Line 160: | Line 143: | ||
function Template:getOutput() | function Template:getOutput() | ||
return self._invocation:getOutput(self:getName()) | |||
end | end | ||
Line 174: | Line 152: | ||
local TestCase = {} | local TestCase = {} | ||
TestCase.__index = TestCase | TestCase.__index = TestCase | ||
function TestCase.new(invocationObj, options, cfg) | function TestCase.new(invocationObj, options, cfg) | ||
Line 191: | Line 157: | ||
obj.cfg = cfg | obj.cfg = cfg | ||
-- | -- Validate options | ||
do | |||
local highestNum = 0 | |||
for k in pairs(options) do | |||
if type(k) == 'string' then | |||
local num = k:match('([1-9][0-9]*)$') | |||
num = tonumber(num) | |||
if num and num > highestNum then | |||
highestNum = num | |||
end | |||
end | |||
end | end | ||
if | for i = 3, highestNum do | ||
if not options['template' .. i] then | |||
error(string.format( | |||
"one or more options ending in '%d' were " .. | |||
"detected, but no 'template%d' option was found", | |||
i, i | |||
), 2) | |||
end | |||
end | end | ||
end | end | ||
-- | -- Separate general options from options for specific templates | ||
local templateOptions = mTableTools.numData(options, true) | |||
obj.options = templateOptions.other or {} | |||
obj.options = | |||
-- | -- Normalize boolean options | ||
obj.options.showcode = yesno(obj.options.showcode) | |||
obj.options.collapsible = yesno(obj.options.collapsible) | |||
-- | -- Add default template options | ||
templateOptions[1] = templateOptions[1] or {} | templateOptions[1] = templateOptions[1] or {} | ||
templateOptions[2] = templateOptions[2] or {} | templateOptions[2] = templateOptions[2] or {} | ||
if templateOptions[1].template and not templateOptions[2].template then | if templateOptions[1].template and not templateOptions[2].template then | ||
templateOptions[2].template = templateOptions[1].template .. | templateOptions[2].template = templateOptions[1].template .. '/sandbox' | ||
end | end | ||
if not templateOptions[1].template then | if not templateOptions[1].template then | ||
Line 245: | Line 198: | ||
end | end | ||
if not templateOptions[2].template then | if not templateOptions[2].template then | ||
templateOptions[2].title = templateOptions[1].title:subPageTitle( | templateOptions[2].title = templateOptions[1].title:subPageTitle('sandbox') | ||
end | end | ||
-- Make the template objects | -- Make the template objects | ||
obj.templates = {} | obj.templates = {} | ||
for i, | for i, t in ipairs(templateOptions) do | ||
table.insert(obj.templates, Template.new(invocationObj, | table.insert(obj.templates, Template.new(invocationObj, t)) | ||
end | end | ||
Line 316: | Line 216: | ||
end | end | ||
return output | return output | ||
end | end | ||
function TestCase:makeCollapsible(s) | function TestCase:makeCollapsible(s) | ||
local | local isEqual = Template.outputEquals(unpack(self.templates)) | ||
local root = mw.html.create('table') | local root = mw.html.create('table') | ||
root | root | ||
:addClass(' | :addClass('collapsible') | ||
:addClass(isEqual and 'collapsed' or nil) | |||
:css('background-color', 'transparent') | :css('background-color', 'transparent') | ||
:css('width', '100%') | :css('width', '100%') | ||
Line 387: | Line 230: | ||
:tag('th') | :tag('th') | ||
:css('background-color', isEqual and 'lightgreen' or 'yellow') | :css('background-color', isEqual and 'lightgreen' or 'yellow') | ||
:wikitext(title) | :wikitext(self.options.title or self.templates[1]:makeHeading()) | ||
:done() | :done() | ||
:done() | :done() | ||
:tag('tr') | :tag('tr') | ||
:tag('td') | :tag('td') | ||
:wikitext(s) | :wikitext(s) | ||
return tostring(root) | return tostring(root) | ||
end | end | ||
Line 408: | Line 248: | ||
local tableroot = root:tag('table') | local tableroot = root:tag('table') | ||
tableroot | |||
:addClass(self.options.class) | |||
:cssText(self.options.style) | |||
:tag('caption') | |||
:wikitext(self.options.caption or 'Side by side comparison') | |||
-- Headings | |||
local headingRow = 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. | |||
headingRow: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 | |||
headingRow | |||
:tag('th') | |||
:css('width', width) | |||
:wikitext(obj:makeHeading()) | |||
end | end | ||
Line 450: | Line 284: | ||
-- Template output | -- Template output | ||
for i, obj in ipairs(self.templates) do | for i, obj in ipairs(self.templates) do | ||
dataRow:tag('td') | |||
:newline() | |||
:wikitext(self:getTemplateOutput(obj)) | |||
:wikitext(self.options.after) | |||
end | end | ||
Line 499: | Line 313: | ||
for _, obj in ipairs(self.templates) do | for _, obj in ipairs(self.templates) do | ||
-- Build the row HTML | |||
tableroot | |||
-- | :tag('tr') | ||
:tag('td') | |||
:css('text-align', 'center') | :css('text-align', 'center') | ||
:css('font-weight', 'bold') | :css('font-weight', 'bold') | ||
:wikitext(obj: | :wikitext(obj:makeHeading()) | ||
:done() | |||
:done() | |||
:tag('tr') | |||
:tag('td') | |||
:newline() | |||
:wikitext(self:getTemplateOutput(obj)) | |||
: | |||
end | end | ||
return tostring(root) | return tostring(root) | ||
Line 646: | Line 338: | ||
for i, obj in ipairs(self.templates) do | for i, obj in ipairs(self.templates) do | ||
ret[#ret + 1] = '<div style="clear: both;"></div>' | ret[#ret + 1] = '<div style="clear: both;"></div>' | ||
ret[#ret + 1] = obj:makeBraceLink() | |||
ret[#ret + 1] = self:getTemplateOutput(obj) | |||
end | end | ||
return table.concat(ret, '\n\n') | return table.concat(ret, '\n\n') | ||
Line 661: | Line 345: | ||
function TestCase:__tostring() | function TestCase:__tostring() | ||
local methods = { | |||
collapsed = 'renderCollapsed', | |||
columns = 'renderColumns', | |||
rows = 'renderRows' | |||
} | |||
local format = self.options.format | local format = self.options.format | ||
local method = format and | local method = format and methods[format] or 'renderDefault' | ||
local ret = self[method](self) | local ret = self[method](self) | ||
if self.options.collapsible then | if self.options.collapsible then | ||
ret = self:makeCollapsible(ret) | ret = self:makeCollapsible(ret) | ||
end | end | ||
return ret | return ret | ||
Line 679: | Line 365: | ||
local NowikiInvocation = {} | local NowikiInvocation = {} | ||
NowikiInvocation.__index = NowikiInvocation | NowikiInvocation.__index = NowikiInvocation | ||
function NowikiInvocation.new(invocation, cfg) | function NowikiInvocation.new(invocation, cfg) | ||
Line 696: | Line 381: | ||
end | end | ||
function NowikiInvocation:getInvocation( | function NowikiInvocation:getInvocation(template) | ||
template = template:gsub('%%', '%%%%') -- Escape "%" with "%%" | |||
local invocation, count = self.invocation:gsub( | local invocation, count = self.invocation:gsub( | ||
self.cfg.templateNameMagicWordPattern, | self.cfg.templateNameMagicWordPattern, | ||
template | template | ||
) | ) | ||
if | if count < 1 then | ||
error( | error(string.format( | ||
' | "the template invocation must include '%s' in place " .. | ||
"of the template name", | |||
self.cfg.templateNameMagicWord | self.cfg.templateNameMagicWord | ||
)) | )) | ||
Line 711: | Line 397: | ||
end | end | ||
function NowikiInvocation:getOutput( | function NowikiInvocation:getOutput(template) | ||
local invocation = self:getInvocation( | local invocation = self:getInvocation(template) | ||
return mw.getCurrentFrame():preprocess(invocation) | return mw.getCurrentFrame():preprocess(invocation) | ||
end | end | ||
Line 722: | Line 408: | ||
local TableInvocation = {} | local TableInvocation = {} | ||
TableInvocation.__index = TableInvocation | TableInvocation.__index = TableInvocation | ||
function TableInvocation.new(invokeArgs, nowikiCode, cfg) | function TableInvocation.new(invokeArgs, nowikiCode, cfg) | ||
Line 732: | Line 417: | ||
end | end | ||
function TableInvocation:getInvocation( | function TableInvocation:getInvocation(template) | ||
if self.code then | if self.code then | ||
local nowikiObj = NowikiInvocation | local nowikiObj = NowikiInvocation(self.code) | ||
return nowikiObj:getInvocation( | return nowikiObj:getInvocation(template) | ||
else | else | ||
return require('Module:Template invocation').invocation( | return require('Module:Template invocation').invocation( | ||
template, | |||
self.invokeArgs | self.invokeArgs | ||
) | ) | ||
Line 744: | Line 429: | ||
end | end | ||
function TableInvocation:getOutput( | function TableInvocation:getOutput(template) | ||
return mw.getCurrentFrame():expandTemplate{ | return mw.getCurrentFrame():expandTemplate{ | ||
title = | title = template, | ||
args = self.invokeArgs | args = self.invokeArgs | ||
} | } | ||
Line 758: | Line 437: | ||
------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ||
-- | -- Exports | ||
------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ||
local | local p = {} | ||
function | function p.table(args, cfg) | ||
cfg = cfg or mw.loadData(DATA_MODULE) | cfg = cfg or mw.loadData(DATA_MODULE) | ||
Line 795: | Line 471: | ||
end | end | ||
function | function p.nowiki(args, cfg) | ||
cfg = cfg or mw.loadData(DATA_MODULE) | cfg = cfg or mw.loadData(DATA_MODULE) | ||
local invocationObj = NowikiInvocation.new(args.code, cfg) | |||
local invocationObj = NowikiInvocation.new(code, cfg) | |||
args.code = nil | args.code = nil | ||
-- Assume we want to see the code as we already passed it in. | -- Assume we want to see the code as we already passed it in. | ||
args.showcode = args.showcode or true | args.showcode = args.showcode or true | ||
Line 807: | Line 481: | ||
return tostring(testCaseObj) | return tostring(testCaseObj) | ||
end | end | ||
function p.main(frame, cfg) | function p.main(frame, cfg) | ||
Line 829: | Line 497: | ||
-- by the user. | -- by the user. | ||
local func = wrapperConfig and wrapperConfig.func or 'table' | local func = wrapperConfig and wrapperConfig.func or 'table' | ||
local isTableFunc = func == 'table' | |||
local userArgs = require('Module:Arguments').getArgs(frame, { | local userArgs = require('Module:Arguments').getArgs(frame, { | ||
parentOnly = wrapperConfig, | parentOnly = wrapperConfig, | ||
frameOnly = not wrapperConfig, | frameOnly = not wrapperConfig, | ||
trim = | trim = not isTableFunc, | ||
removeBlanks = | removeBlanks = not isTableFunc | ||
}) | }) | ||
Line 847: | Line 516: | ||
end | end | ||
return | return p[func](args, cfg) | ||
end | end | ||