Editing Module:Template test case

Warning: You are not logged in. Your IP address will be publicly visible if you make any edits. Read the Privacy Policy to learn what information we collect about you and how we use it.

If you log in or create an account, your edits will be attributed to your username, along with other benefits.

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.
  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
-- Load required modules
Line 54: Line 33:
getFullPage = true,
getFullPage = true,
getName = true,
getName = true,
makeHeader = true,
makeHeading = true,
getOutput = true
getOutput = true
}
}
Line 94: Line 73:


function Template:getFullPage()
function Template:getFullPage()
if not self.template then
if 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)
local strippedTemplate, hasColon = self.template:gsub('^:', '', 1)
hasColon = hasColon > 0
hasColon = hasColon > 0
Line 110: Line 85:
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 112:
end
end


function Template:makeHeader()
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())
template = self:getName(),
requireMagicWord = self.requireMagicWord,
}
if format == 'code' then
if format == 'code' then
invocation = '<code>' .. mw.text.nowiki(invocation) .. '</code>'
invocation = '<code>' .. mw.text.nowiki(invocation) .. '</code>'
elseif format == 'kbd' then
invocation = '<kbd>' .. mw.text.nowiki(invocation) .. '</kbd>'
elseif format == 'plain' then
elseif format == 'plain' then
invocation = mw.text.nowiki(invocation)
invocation = mw.text.nowiki(invocation)
Line 160: Line 132:


function Template:getOutput()
function Template:getOutput()
local protect = require('Module:Protect')
return self._invocation:getOutput(self:getName())
-- calling self._invocation:getOutput{...}
return protect(self._invocation.getOutput)(self._invocation, {
template = self:getName(),
requireMagicWord = self.requireMagicWord,
})
end
end


Line 181: Line 148:
columns = 'renderColumns',
columns = 'renderColumns',
rows = 'renderRows',
rows = 'renderRows',
tablerows = 'renderRows',
inline = 'renderInline',
cells = 'renderCells',
default = 'renderDefault'
default = 'renderDefault'
}
}
Line 191: Line 155:
obj.cfg = cfg
obj.cfg = cfg


-- Separate general options from template options. Template options are
-- Separate numbered and named options.
-- numbered, whereas general options are not.
local generalOptions, templateOptions = {}, {}
local generalOptions, templateOptions = {}, {}
for k, v in pairs(options) do
do
local prefix, num
-- Separate numbered and named options
if type(k) == 'string' then
local optionNum = {} -- a unique key for option numbers inside templateOptions
prefix, num = k:match('^(.-)([1-9][0-9]*)$')
local rawTemplateOptions = {}
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)
rawTemplateOptions[num] = rawTemplateOptions[num] or {}
rawTemplateOptions[num][prefix] = v
rawTemplateOptions[num][optionNum] = num -- record for use in error messages
else
generalOptions[k] = v
end
end
end
if prefix then
-- Remove gaps in the numbered options
num = tonumber(num)
local nums = {}
templateOptions[num] = templateOptions[num] or {}
for num in pairs(rawTemplateOptions) do
templateOptions[num][prefix] = v
nums[#nums + 1] = num
else
end
generalOptions[k] = v
table.sort(nums)
for i, num in ipairs(nums) do
templateOptions[i] = rawTemplateOptions[num]
end
-- Check that there are no missing template options.
for i = 3, #templateOptions do -- Defaults are added later for 1 and 2
local t = templateOptions[i]
if not t.template then
local num = t[optionNum]
error(obj:message(
'missing-template-option-error',
num, num
), 2)
end
end
end
end
end
Line 210: Line 199:
-- Set general options
-- Set general options
generalOptions.showcode = yesno(generalOptions.showcode)
generalOptions.showcode = yesno(generalOptions.showcode)
generalOptions.showheader = yesno(generalOptions.showheader) ~= false
generalOptions.showcaption = yesno(generalOptions.showcaption) ~= false
generalOptions.collapsible = yesno(generalOptions.collapsible)
generalOptions.collapsible = yesno(generalOptions.collapsible)
generalOptions.notcollapsed = yesno(generalOptions.notcollapsed)
generalOptions.wantdiff = yesno(generalOptions.wantdiff)
obj.options = generalOptions
obj.options = generalOptions


-- Preprocess template args
-- Add default template options
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[1] = templateOptions[1] or {}
templateOptions[2] = templateOptions[2] 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
if templateOptions[1].template and not templateOptions[2].template then
templateOptions[2].template = templateOptions[1].template ..
templateOptions[2].template = templateOptions[1].template ..
Line 249: Line 217:
)
)
end
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
-- Make the template objects
obj.templates = {}
obj.templates = {}
for i, options in ipairs(templateOptions) do
for i, t in ipairs(templateOptions) do
table.insert(obj.templates, Template.new(invocationObj, options))
table.insert(obj.templates, Template.new(invocationObj, t))
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
end


Line 327: Line 244:
local out = obj:getOutput()
local out = obj:getOutput()
-- Remove the random parts from strip markers.
-- Remove the random parts from strip markers.
out = out:gsub('(\127\'"`UNIQ.-)%-%x+%-(QINU`"\'\127)', '%1%2')
out = out:gsub('(%cUNIQ).-(QINU%c)', '%1%2')
return out
return out
end
end
Line 341: Line 258:


function TestCase:makeCollapsible(s)
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 isEqual = self:templateOutputIsEqual()
local root = mw.html.create('table')
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
root
:css('background-color', 'transparent')
:addClass('collapsible')
:css('width', '100%')
:addClass(isEqual and 'collapsed' or nil)
: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('background-color', 'transparent')
:css('width', '100%')
:css('width', '100%')
Line 387: Line 269:
: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')
:newline()
:wikitext(s)
:wikitext(s)
:newline()
end
return tostring(root)
return tostring(root)
end
end
Line 408: Line 287:


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 self:message('columns-header'))


if self.options.showheader then
-- Headings
-- Caption
local headingRow = tableroot:tag('tr')
if self.options.showcaption then
if self.options.rowheader then
tableroot
-- rowheader is correct here. We need to add another th cell if
:addClass(self.options.class)
-- rowheader is set further down, even if heading0 is missing.
:cssText(self.options.style)
headingRow:tag('th'):wikitext(self.options.heading0)
:tag('caption')
end
:wikitext(self.options.caption or self:message('columns-header'))
local width
end
if #self.templates > 0 then
 
width = tostring(math.floor(100 / #self.templates)) .. '%'
-- Headers
else
local headerRow = tableroot:tag('tr')
width = '100%'
if self.options.rowheader then
end
-- rowheader is correct here. We need to add another th cell if
for i, obj in ipairs(self.templates) do
-- rowheader is set further down, even if heading0 is missing.
headingRow
headerRow:tag('th'):wikitext(self.options.heading0)
:tag('th')
end
:css('width', width)
local width
:wikitext(obj:makeHeading())
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
end


Line 450: Line 323:
-- Template output
-- Template output
for i, obj in ipairs(self.templates) do
for i, obj in ipairs(self.templates) do
if self.options.output == 'nowiki+' then
dataRow:tag('td')
dataRow:tag('td')
:newline()
:newline()
:wikitext(self:getTemplateOutput(obj))
:wikitext(self.options.before)
:wikitext(self.options.after)
: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
end
Line 499: Line 352:


for _, obj in ipairs(self.templates) do
for _, obj in ipairs(self.templates) do
local dataRow = tableroot:tag('tr')
-- Build the row HTML
tableroot
-- Header
:tag('tr')
if self.options.showheader then
:tag('td')
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('text-align', 'center')
:css('font-weight', 'bold')
:css('font-weight', 'bold')
:wikitext(obj:makeHeader())
:wikitext(obj:makeHeading())
dataRow = tableroot:tag('tr')
:done()
end
:done()
end
:tag('tr')
:tag('td')
-- Template output
:newline()
if self.options.output == 'nowiki+' then
:wikitext(self:getTemplateOutput(obj))
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
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)
return tostring(root)
Line 646: Line 377:
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>'
if self.options.showheader then
ret[#ret + 1] = obj:makeBraceLink()
ret[#ret + 1] = obj:makeHeader()
ret[#ret + 1] = self:getTemplateOutput(obj)
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
end
return table.concat(ret, '\n\n')
return table.concat(ret, '\n\n')
Line 666: Line 389:
if self.options.collapsible then
if self.options.collapsible then
ret = self:makeCollapsible(ret)
ret = self:makeCollapsible(ret)
end
for cat in pairs(self.categories) do
ret = ret .. string.format('[[Category:%s]]', cat)
end
end
return ret
return ret
Line 696: Line 416:
end
end


function NowikiInvocation:getInvocation(options)
function NowikiInvocation:getInvocation(template)
local template = options.template:gsub('%%', '%%%%') -- Escape "%" with "%%"
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 options.requireMagicWord ~= false and count < 1 then
if count < 1 then
error(self:message(
error(self:message(
'nowiki-magic-word-error',
'nowiki-magic-word-error',
Line 711: Line 431:
end
end


function NowikiInvocation:getOutput(options)
function NowikiInvocation:getOutput(template)
local invocation = self:getInvocation(options)
local invocation = self:getInvocation(template)
return mw.getCurrentFrame():preprocess(invocation)
return mw.getCurrentFrame():preprocess(invocation)
end
end
Line 732: Line 452:
end
end


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


function TableInvocation:getOutput(options)
function TableInvocation:getOutput(template)
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 = options.template,
title = template,
args = self.invokeArgs
args = self.invokeArgs
}
}
Line 758: Line 472:


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


local bridge = {}
local p = {}


function bridge.table(args, cfg)
function p.table(args, cfg)
cfg = cfg or mw.loadData(DATA_MODULE)
cfg = cfg or mw.loadData(DATA_MODULE)


Line 795: Line 506:
end
end


function bridge.nowiki(args, cfg)
function p.nowiki(args, cfg)
cfg = cfg or mw.loadData(DATA_MODULE)
cfg = cfg or mw.loadData(DATA_MODULE)


local code = args.code or args[1]
local invocationObj = NowikiInvocation.new(args.code, cfg)
local invocationObj = NowikiInvocation.new(code, cfg)
args.code = nil
args.code = nil
args[1] = 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 516:
return tostring(testCaseObj)
return tostring(testCaseObj)
end
end
-------------------------------------------------------------------------------
-- Exports
-------------------------------------------------------------------------------
local p = {}


function p.main(frame, cfg)
function p.main(frame, cfg)
Line 847: Line 550:
end
end


return bridge[func](args, cfg)
return p[func](args, cfg)
end
end


Please note that all contributions to Nonbinary Wiki are considered to be released under the Creative Commons Attribution-ShareAlike (see Nonbinary Wiki:Copyrights for details). If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource. Do not submit copyrighted work without permission!
Cancel Editing help (opens in new window)

Template used on this page: