Module:Template test case: Difference between revisions

Nothing to hide, but nothing to show you either.
Jump to navigation Jump to search
Content added Content deleted
(if the first template is specified but the second is not, make the second the sandbox of the first rather than the sandbox of the base page; simplify the title-getting code in the process)
(add TestCase:renderColumns and some more supporting methods)
Line 20: Line 20:
-- Set input
-- Set input
for k, v in pairs(options or {}) do
for k, v in pairs(options or {}) do
obj[k] = v
if not Template[k] then
obj[k] = v
end
end
end
obj.invocation = invocationObj
obj.invocation = invocationObj
Line 69: Line 71:
local link = self:makeLink(display)
local link = self:makeLink(display)
return mw.text.nowiki('{{') .. link .. mw.text.nowiki('}}')
return mw.text.nowiki('{{') .. link .. mw.text.nowiki('}}')
end

function Template:makeHeading()
return self.heading or self:makeBraceLink()
end
end


Line 144: Line 150:


return obj
return obj
end

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

function TestCase:renderColumns()
local root = mw.html.create('table')
root
:addClass(self.options.class)
:cssText(self.options.style)
:tag('caption')
:wikitext(self.options.caption or 'Side by side comparison')

-- Headings
local headingRow = root:tag('tr')
if self.options.rowheader then
headingRow:tag('th'):wikitext(self.options.heading0)
end
local width = tostring(math.floor(100 / #self.templates)) .. '%'
for i, obj in ipairs(self.templates) do
headingRow
:tag('th')
:css('width', width)
:wikitext(obj:makeHeading())
end

-- Row header
local dataRow = root: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
dataRow:tag('td')
:newline()
:wikitext(self:getTemplateOutput(obj))
:wikitext(self.options.after)
end
return tostring(root)
end
end


Line 152: Line 206:
ret[#ret + 1] = '<div style="clear: both;"></div>'
ret[#ret + 1] = '<div style="clear: both;"></div>'
ret[#ret + 1] = obj:makeBraceLink()
ret[#ret + 1] = obj:makeBraceLink()
ret[#ret + 1] = obj:getOutput()
ret[#ret + 1] = self:getTemplateOutput(obj)
end
end
return table.concat(ret, '\n\n')
return table.concat(ret, '\n\n')

Revision as of 07:06, 25 November 2014

Documentation for this module may be created at Module:Template test case/doc

-- This module provides several methods to generate test cases.

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

local TEMPLATE_NAME_MAGIC_WORD = '<TEMPLATE_NAME>'
local TEMPLATE_NAME_MAGIC_WORD_ESCAPED = TEMPLATE_NAME_MAGIC_WORD:gsub('%p', '%%%0')

-------------------------------------------------------------------------------
-- Template class
-------------------------------------------------------------------------------

local Template = {}
Template.__index = Template

function Template.new(invocationObj, options)
	local obj = setmetatable({}, Template)

	-- Set input
	for k, v in pairs(options or {}) do
		if not Template[k] then
			obj[k] = v
		end
	end
	obj.invocation = invocationObj

	-- Validate input
	if not obj.template and not obj.title then
		error('no template or title specified', 2)
	end

	return obj
end

function Template:getFullPage()
	if self.template then
		local strippedTemplate, hasColon = self.template:gsub('^:', '', 1)
		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
	else
		return self.title.prefixedText
	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:makeHeading()
	return self.heading or self:makeBraceLink()
end

function Template:getInvocation(format)
	local invocation = self.invocation:getInvocation(self:getName())
	invocation = mw.text.nowiki(invocation)
	if format == 'code' then
		invocation = '<code>' .. invocation .. '</code>'
	elseif format == 'pre' then
		invocation = '<pre style="white-space: pre-wrap;">' .. invocation .. '</pre>'
		invocation = mw.getCurrentFrame():preprocess(invocation)
	end
	return invocation
end

function Template:getOutput()
	return self.invocation:getOutput(self:getName())
end

-------------------------------------------------------------------------------
-- TestCase class
-------------------------------------------------------------------------------

local TestCase = {}
TestCase.__index = TestCase

function TestCase.new(invocationObj, options)
	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
		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

	-- Separate general options from options for specific templates
	local templateOptions = mTableTools.numData(options, true)
	obj.options = templateOptions.other or {}

	-- Add default template options
	templateOptions[1] = templateOptions[1] or {}
	templateOptions[2] = templateOptions[2] or {}
	if templateOptions[1].template and not templateOptions[2].template then
		templateOptions[2].template = templateOptions[1].template .. '/sandbox'
	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('sandbox')
	end

	-- Make the template objects
	obj.templates = {}
	for i, t in ipairs(templateOptions) do
		table.insert(obj.templates, Template.new(invocationObj, t))
	end

	return obj
end

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

function TestCase:renderColumns()
	local root = mw.html.create('table')
	root
		:addClass(self.options.class)
		:cssText(self.options.style)
		:tag('caption')
			:wikitext(self.options.caption or 'Side by side comparison')

	-- Headings
	local headingRow = root:tag('tr')
	if self.options.rowheader then
		headingRow:tag('th'):wikitext(self.options.heading0)
	end
	local width = tostring(math.floor(100 / #self.templates)) .. '%'
	for i, obj in ipairs(self.templates) do
		headingRow
			:tag('th')
				:css('width', width)
				:wikitext(obj:makeHeading())
	end

	-- Row header
	local dataRow = root: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
		dataRow:tag('td')
			:newline()
			:wikitext(self:getTemplateOutput(obj))
			:wikitext(self.options.after)
	end
	
	return tostring(root)
end

function TestCase:renderDefault()
	local ret = {}
	ret[#ret + 1] = self.templates[1]:getInvocation('code')
	for i, obj in ipairs(self.templates) do
		ret[#ret + 1] = '<div style="clear: both;"></div>'
		ret[#ret + 1] = obj:makeBraceLink()
		ret[#ret + 1] = self:getTemplateOutput(obj)
	end
	return table.concat(ret, '\n\n')
end

function TestCase:__tostring()
	local methods = {
		columns = 'renderColumns',
		rows = 'renderRows'
	}
	local format = self.options.format
	local method = format and methods[format] or 'renderDefault'
	return self[method](self)
end

-------------------------------------------------------------------------------
-- Nowiki invocation class
-------------------------------------------------------------------------------

local NowikiInvocation = {}
NowikiInvocation.__index = NowikiInvocation

function NowikiInvocation.new(invocation)
	local obj = setmetatable({}, NowikiInvocation)
	obj.invocation = mw.text.unstrip(invocation)
	return obj
end

function NowikiInvocation:getInvocation(template)
	template = template:gsub('%%', '%%%%') -- Escape "%" with "%%"
	local invocation, count = self.invocation:gsub(
		TEMPLATE_NAME_MAGIC_WORD_ESCAPED,
		template
	)
	if count < 1 then
		error(string.format(
			"the template invocation must include '%s' in place " ..
			"of the template name",
			TEMPLATE_NAME_MAGIC_WORD
		))
	end
	return invocation
end

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

-------------------------------------------------------------------------------
-- Table invocation class
-------------------------------------------------------------------------------

local TableInvocation = {}
TableInvocation.__index = TableInvocation

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

function TableInvocation:getInvocation(template)
	return require('Module:Template invocation').invocation(
		template,
		self.invokeArgs
	)
end

function TableInvocation:getOutput(template)
	return mw.getCurrentFrame():expandTemplate{
		title = template,
		args = self.invokeArgs
	}
end

-------------------------------------------------------------------------------
-- Exports
-------------------------------------------------------------------------------

-- Table-based exports

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 = {}, {}
	for k, v in pairs(args) do
		local optionKey = type(k) == 'string' and k:match('^_(.*)$')
		if optionKey then
			if type(v) == 'string' then
				v = v:match('^%s*(.-)%s*$') -- trim whitespace
			end
			if v ~= '' then
				options[optionKey] = v
			end
		else
			invokeArgs[k] = v
		end
	end
	local invocationObj = TableInvocation.new(invokeArgs)
	local testCaseObj = TestCase.new(invocationObj, options)
	return tostring(testCaseObj)
end

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

function p.rows(frame)
	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.invocation)
	args.invocation = nil
	local options = args
	local testCaseObj = TestCase.new(invocationObj, options)
	return tostring(testCaseObj)
end

function p.nowiki(frame)
	local args = require('Module:Arguments').getArgs(frame, {
		wrappers = 'Template:Test case from invocation'
	})
	return p._nowiki(args)
end

-- Exports for testing

function p._exportClasses()
	return {
		TestCase = TestCase,
		Invocation = Invocation,
		NowikiInvocation = NowikiInvocation,
		TableInvocation = TableInvocation
	}
end

return p