Module:Template test case: Difference between revisions

    From Nonbinary Wiki
    (move the logic for finding the options into the exports - it seemed a bit unintuitive to have invocation objects handling argument code as well)
    (make a template class and add code to set template objects in the test case class)
    Line 9: Line 9:


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


    --[[
    local Template = {}
    local function validateTemplateOptions()
    Template.__index = Template
    -- Add the template names for the first two templates if they weren't
     
    -- specified.
    function Template.new(invocationObj, options, titleCallback)
    do
    local obj = setmetatable({}, Template)
    local title = mw.title.getCurrentTitle().basePageTitle
     
    local template
    -- Set input
    if title.namespace == 10 then
    for k, v in pairs(options or {}) do
    template = title.text
    obj[k] = v
    elseif title.namespace == 0 then
    end
    template = ':' .. title.prefixedText
    obj.invocation = invocationObj
     
    -- Validate template
    if not obj.template then
    if titleCallback then
    obj.title = titleCallback()
    else
    else
    template = title.prefixedText
    error('no template or title callback specified', 2)
    end
    end
    local templatePage = title.prefixedText
    templateOptions[1] = templateOptions[1] or {}
    templateOptions[1].templatePage = templateOptions[1].template or templatePage
    templateOptions[1].template = templateOptions[1].template or template
    templateOptions[2] = templateOptions[2] or {}
    templateOptions[2].templatePage = templateOptions[2].template or templatePage .. '/sandbox'
    templateOptions[2].template = templateOptions[2].template or template .. '/sandbox'
    end
    end


    -- Validate options for three or more templates.
    return obj
    if #templateOptions >= 3 then
    end
    for i = 3, #templateOptions do
     
    local template = templateOptions[i].template
    function Template:getFullPage()
    if not template then
    if self.template then
    error('arguments for a third or subsequent ' ..
    local strippedTemplate, hasColon = self.template:gsub('^:', '', 1)
    'template were found, but no template name ' ..
    local ns = strippedTemplate:match('^(.-):')
    'was specified', 3)
    ns = ns and mw.site.namespaces[ns]
    end
    if ns then
    return strippedTemplate
    elseif hasColon then
    return strippedTemplate -- Main namespace
    else
    return mw.site.namespaces[10].name .. ':' .. strippedTemplate
    end
    end
    else
    return self.title.prefixedText
    end
    end
    end
    end
    --]]


    local function parseTemplateOptions(origOptions)
    function Template:getName()
    -- Given a table of raw options, returns a table of general options
    if self.template then
    -- and template options.
    return self.template
    local templateOptions = mTableTools.numData(origOptions, true)
    else
    local options = templateOptions.other
    return require('Module:Template invocation').name(self.title)
    templateOptions.other = nil
    end
    return options, templateOptions
    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: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
    end


    Line 65: Line 98:
    TestCase.__index = TestCase
    TestCase.__index = TestCase


    function TestCase.new(invocationObj)
    function TestCase.new(invocationObj, options)
    checkType('TestCase.new', 'invocationObj', invocationObj, 'table')
    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 > 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)
    options = templateOptions.other
    templateOptions.other = nil
    -- Make the template objects
    obj.templates = {}
    local function templateTitleCallback()
    return mw.title.getCurrentTitle().basePageTitle
    end
    obj.templates[1] = Template.new(
    invocationObj,
    templateOptions[1],
    templateTitleCallback
    )
    obj.templates[2] = Template.new(
    invocationObj,
    options,
    function ()
    return templateTitleCallback():subPageTitle('sandbox')
    end
    )
    for i = 3, #templateOptions do
    table.insert(obj.templates, Template.new(
    invocationObj,
    templateOptions[i]
    ))
    end
    return obj
    return obj
    end
    end
    --------------------------------------------------------------------------------
    -- Test case display functions
    --
    -- Test case display functions produce the wikitext to display the template
    -- output for one test case. For example, one function might produce templates
    -- aligned horizontally, and another function might produce templates aligned
    -- one below the other.
    --
    -- They are named functions that accept the following parameters:
    -- * 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.
    --------------------------------------------------------------------------------


    -------------------------------------------------------------------------------
    -------------------------------------------------------------------------------

    Revision as of 14:09, 24 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, titleCallback)
    	local obj = setmetatable({}, Template)
    
    	-- Set input
    	for k, v in pairs(options or {}) do
    		obj[k] = v
    	end
    	obj.invocation = invocationObj
    
    	-- Validate template
    	if not obj.template then
    		if titleCallback then
    			obj.title = titleCallback()
    		else
    			error('no template or title callback specified', 2)
    		end
    	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: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 > 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)
    	options = templateOptions.other
    	templateOptions.other = nil
    
    	-- Make the template objects
    	obj.templates = {}
    	local function templateTitleCallback()
    		return mw.title.getCurrentTitle().basePageTitle
    	end
    	obj.templates[1] = Template.new(
    		invocationObj,
    		templateOptions[1],
    		templateTitleCallback
    	)
    	obj.templates[2] = Template.new(
    		invocationObj,
    		options,
    		function ()
    			return templateTitleCallback():subPageTitle('sandbox')
    		end
    	)
    	for i = 3, #templateOptions do
    		table.insert(obj.templates, Template.new(
    			invocationObj,
    			templateOptions[i]
    		))
    	end
    
    	return obj
    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 a % 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 invocation = TableInvocation.new(invokeArgs)
    	return invocation
    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 invocation = NowikiInvocation.new(args.invocation)
    	args.invocation = nil
    	local options = args
    	return invocation
    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