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. | ||
local yesno = require('Module:Yesno') | local yesno = require('Module:Yesno') | ||
local mTableTools = require('Module:TableTools') | |||
local TEMPLATE_NAME_MAGIC_WORD = '__TEMPLATENAME__' | |||
local | local TEMPLATE_NAME_MAGIC_WORD_ESCAPED = TEMPLATE_NAME_MAGIC_WORD:gsub('%p', '%%%0') | ||
------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ||
Line 54: | Line 18: | ||
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 83: | ||
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 95: | ||
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 122: | ||
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 142: | ||
function Template:getOutput() | function Template:getOutput() | ||
return self._invocation:getOutput(self:getName()) | |||
end | end | ||
Line 174: | Line 151: | ||
local TestCase = {} | local TestCase = {} | ||
TestCase.__index = TestCase | TestCase.__index = TestCase | ||
function TestCase.new(invocationObj, options | function TestCase.new(invocationObj, options) | ||
local obj = setmetatable({}, TestCase) | local obj = setmetatable({}, TestCase) | ||
-- | -- 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 196: | ||
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 214: | ||
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('collapsible') | |||
:addClass(isEqual and 'collapsed' or nil) | |||
:addClass(' | |||
:css('background-color', 'transparent') | :css('background-color', 'transparent') | ||
:css('width', '100%') | :css('width', '100%') | ||
Line 387: | Line 228: | ||
: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 246: | ||
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 282: | ||
-- 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 311: | ||
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 336: | ||
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 343: | ||
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 363: | ||
local NowikiInvocation = {} | local NowikiInvocation = {} | ||
NowikiInvocation.__index = NowikiInvocation | NowikiInvocation.__index = NowikiInvocation | ||
function NowikiInvocation.new(invocation | function NowikiInvocation.new(invocation) | ||
local obj = setmetatable({}, NowikiInvocation) | local obj = setmetatable({}, NowikiInvocation) | ||
invocation = mw.text.unstrip(invocation) | invocation = mw.text.unstrip(invocation) | ||
-- Decode HTML entities for <, >, and ". This means that HTML entities in | -- Decode HTML entities for <, >, and ". This means that HTML entities in | ||
Line 696: | Line 378: | ||
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( | ||
TEMPLATE_NAME_MAGIC_WORD_ESCAPED, | |||
template | template | ||
) | ) | ||
if | if count < 1 then | ||
error( | error(string.format( | ||
' | "the template invocation must include '%s' in place " .. | ||
"of the template name", | |||
TEMPLATE_NAME_MAGIC_WORD | |||
)) | )) | ||
end | end | ||
Line 711: | Line 394: | ||
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 405: | ||
local TableInvocation = {} | local TableInvocation = {} | ||
TableInvocation.__index = TableInvocation | TableInvocation.__index = TableInvocation | ||
function TableInvocation.new(invokeArgs, nowikiCode | function TableInvocation.new(invokeArgs, nowikiCode) | ||
local obj = setmetatable({}, TableInvocation) | local obj = setmetatable({}, TableInvocation) | ||
obj.invokeArgs = invokeArgs | obj.invokeArgs = invokeArgs | ||
obj.code = nowikiCode | obj.code = nowikiCode | ||
Line 732: | Line 413: | ||
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 425: | ||
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 433: | ||
------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ||
-- | -- Exports | ||
------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ||
-- Table-based exports | |||
function | local function getTableArgs(frame, wrappers) | ||
return require('Module:Arguments').getArgs(frame, { | |||
wrappers = wrappers, | |||
trim = false, | |||
removeBlanks = false | |||
}) | |||
end | |||
local p = {} | |||
function p._table(args) | |||
local options, invokeArgs = {}, {} | local options, invokeArgs = {}, {} | ||
for k, v in pairs(args) do | for k, v in pairs(args) do | ||
Line 790: | Line 470: | ||
options.code = nil | options.code = nil | ||
local invocationObj = TableInvocation.new(invokeArgs, nowikiCode | local invocationObj = TableInvocation.new(invokeArgs, nowikiCode) | ||
local testCaseObj = TestCase.new(invocationObj, options | local testCaseObj = TestCase.new(invocationObj, options) | ||
return tostring(testCaseObj) | return tostring(testCaseObj) | ||
end | end | ||
function | function p.table(frame) | ||
return p._table(getTableArgs(frame, 'Template:Test case from arguments')) | |||
end | |||
function p.columns(frame) | |||
local args = getTableArgs(frame, 'Template:Testcase table') | |||
args._format = 'columns' | |||
return p._table(args) | |||
end | |||
local | function p.rows(frame) | ||
local invocationObj = NowikiInvocation.new(code | local args = getTableArgs(frame, 'Template:Testcase rows') | ||
args._format = 'rows' | |||
return p._table(args) | |||
end | |||
-- Nowiki-based exports | |||
function p._nowiki(args) | |||
local invocationObj = NowikiInvocation.new(args.code) | |||
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 | ||
local testCaseObj = TestCase.new(invocationObj, args | local testCaseObj = TestCase.new(invocationObj, args) | ||
return tostring(testCaseObj) | return tostring(testCaseObj) | ||
end | end | ||
function p.nowiki(frame) | |||
local args = require('Module:Arguments').getArgs(frame, { | |||
wrappers = 'Template:Test case from invocation' | |||
function p. | |||
local | |||
}) | }) | ||
return p._nowiki(args) | |||
end | |||
-- Exports for testing | |||
function p._exportClasses() | function p._exportClasses() | ||
return { | return { | ||
Template = Template, | Template = Template, | ||
TestCase = TestCase, | TestCase = TestCase, | ||
Invocation = Invocation, | |||
NowikiInvocation = NowikiInvocation, | NowikiInvocation = NowikiInvocation, | ||
TableInvocation = TableInvocation | TableInvocation = TableInvocation |