Modul:Arguments/testcases

local getArgs = require('Module:Arguments/sandbox').getArgs
local ScribuntoUnit = require('Module:ScribuntoUnit')
local suite = ScribuntoUnit:new()

--------------------------------------------------------------------------
-- Default values
--------------------------------------------------------------------------

local d = {} 
d.frameTitle = 'Frame title'
d.parentTitle = 'Parent title'

-- Precedence-testing values
d.firstFrameArg = 'first frame argument'
d.firstParentArg = 'first parent argument'
d.secondParentArg = 'second parent argument'
d.uniqueFrameArg = 'unique frame argument'
d.uniqueFrameArgKey = 'uniqueFrameArgKey'
d.uniqueParentArg = 'unique parent argument'
d.uniqueParentArgKey = 'uniqueParentArgKey'

-- Trimming and whitespace values.
-- Whitespace gets trimmed from named parameters, so keys for these need
-- to be numbers to make this a proper test.
d.blankArg = ''
d.blankArgKey = 100 
d.spacesArg = '\n   '
d.spacesArgKey = 101
d.untrimmedArg = '\n   foo bar   '
d.untrimmedArgKey = 102
d.trimmedArg = 'foo bar'
d.valueFuncValue = 'valueFuncValue'
d.defaultValueFunc = function() return d.valueFuncValue end

--------------------------------------------------------------------------
-- Helper functions
--------------------------------------------------------------------------

function suite.getFrames(frameTitle, frameArgs, parentTitle, parentArgs)
	frameTitle = frameTitle or d.frameTitle
	frameArgs = frameArgs or {
		d.firstFrameArg,
		[d.uniqueFrameArgKey] = d.uniqueFrameArg,
		[d.blankArgKey] = d.blankArg,
		[d.spacesArgKey] = d.spacesArg,
		[d.untrimmedArgKey] = d.untrimmedArg
	}
	parentTitle = parentTitle or d.parentTitle
	parentArgs = parentArgs or {
		d.firstParentArg,
		d.secondParentArg,
		[d.uniqueParentArgKey] = d.uniqueParentArg
	}
	local currentFrame = mw.getCurrentFrame()
	local parent = currentFrame:newChild{title = parentTitle, args = parentArgs}
	local frame = parent:newChild{title = frameTitle, args = frameArgs}
	return frame, parent
end

function suite.getDefaultArgs(options, frameTitle, frameArgs, parentTitle, parentArgs)
	local frame, parent = suite.getFrames(frameTitle, frameArgs, parentTitle, parentArgs)
	local args = getArgs(frame, options)
	return args
end

function suite:assertError(func, ...)
	-- Asserts that executing the function func results in an error.
	-- Parameters after func are func's arguments.
	local success, msg = pcall(func, ...)
	self:assertFalse(success)
end

function suite:assertNumberOfIterations(expected, iterator, t)
	local noIterations = 0
	for k, v in iterator(t) do
		noIterations = noIterations + 1
	end
	self:assertEquals(expected, noIterations)
end

--------------------------------------------------------------------------
-- Test precedence
--------------------------------------------------------------------------

function suite:assertDefaultPrecedence(args)
	self:assertEquals(d.firstFrameArg, args[1])
	self:assertEquals(d.secondParentArg, args[2])
	self:assertEquals(d.uniqueFrameArg, args[d.uniqueFrameArgKey])
	self:assertEquals(d.uniqueParentArg, args[d.uniqueParentArgKey])
end

function suite:testDefaultPrecedence()
	self:assertDefaultPrecedence(suite.getDefaultArgs())
end

function suite:testDefaultPrecedenceThroughWrapper()
	self:assertDefaultPrecedence(suite.getDefaultArgs{wrappers = {d.parentTitle}, parentOnly = false})
end

function suite:testDefaultPrecedenceThroughNonWrapper()
	self:assertDefaultPrecedence(suite.getDefaultArgs({wrappers = d.parentTitle, frameOnly = false}, nil, nil, 'Not the parent title'))
end

function suite:assertParentFirst(args)
	self:assertEquals(d.firstParentArg, args[1])
	self:assertEquals(d.secondParentArg, args[2])
	self:assertEquals(d.uniqueFrameArg, args[d.uniqueFrameArgKey])
	self:assertEquals(d.uniqueParentArg, args[d.uniqueParentArgKey])
end

function suite:testParentFirst()
	self:assertParentFirst(suite.getDefaultArgs{parentFirst = true})
end

function suite:testParentFirstThroughWrapper()
	self:assertParentFirst(suite.getDefaultArgs{wrappers = {d.parentTitle}, parentOnly = false, parentFirst = true})
end

function suite:testParentFirstThroughNonWrapper()
	self:assertParentFirst(suite.getDefaultArgs({wrappers = d.parentTitle, frameOnly = false, parentFirst = true}, nil, nil, 'Not the parent title'))
end

function suite:assertParentOnly(args)
	self:assertEquals(d.firstParentArg, args[1])
	self:assertEquals(d.secondParentArg, args[2])
	self:assertEquals(nil, args[d.uniqueFrameArgKey])
	self:assertEquals(d.uniqueParentArg, args[d.uniqueParentArgKey])
end

function suite:testParentOnly()
	self:assertParentOnly(suite.getDefaultArgs{parentOnly = true})
end

function suite:testParentOnlyThroughWrapper()
	self:assertParentOnly(suite.getDefaultArgs{wrappers = {d.parentTitle}})
end

function suite:testParentOnlyThroughSandboxWrapper()
	self:assertParentOnly(suite.getDefaultArgs({wrappers = d.parentTitle}, nil, nil, d.parentTitle .. '/sandbox'))
end

function suite:assertFrameOnly(args)
	self:assertEquals(d.firstFrameArg, args[1])
	self:assertEquals(nil, args[2])
	self:assertEquals(d.uniqueFrameArg, args[d.uniqueFrameArgKey])
	self:assertEquals(nil, args[d.uniqueParentArgKey])
end

function suite:testFrameOnly()
	self:assertFrameOnly(suite.getDefaultArgs{frameOnly = true})
end

function suite:testFrameOnlyThroughNonWrapper()
	self:assertFrameOnly(suite.getDefaultArgs({wrappers = d.parentTitle}, nil, nil, 'Not the parent title'))
end

function suite:testDefaultPrecedenceWithWhitespace()
	local frame, parent = suite.getFrames(
		d.frameTitle,
		{'  '},
		d.parentTitle,
		{d.firstParentArg}
	)
	local args = getArgs(frame)
	self:assertEquals(d.firstParentArg, args[1])
end

--------------------------------------------------------------------------
-- Test trimming and blank removal
--------------------------------------------------------------------------

function suite:testDefaultTrimmingAndBlankRemoval()
	local args = suite.getDefaultArgs()
	self:assertEquals(nil, args[d.blankArgKey])
	self:assertEquals(nil, args[d.spacesArgKey])
	self:assertEquals(d.trimmedArg, args[d.untrimmedArgKey])
end

function suite:testRemoveBlanksButNoTrimming()
	local args = suite.getDefaultArgs{trim = false}
	self:assertEquals(nil, args[d.blankArgKey])
	self:assertEquals(nil, args[d.spacesArgKey])
	self:assertEquals(d.untrimmedArg, args[d.untrimmedArgKey])
end

function suite:testTrimButNoBlankRemoval()
	local args = suite.getDefaultArgs{removeBlanks = false}
	self:assertEquals(d.blankArg, args[d.blankArgKey])
	self:assertEquals('', args[d.spacesArgKey])
	self:assertEquals(d.trimmedArg, args[d.untrimmedArgKey])
end

function suite:testNoTrimOrBlankRemoval()
	local args = suite.getDefaultArgs{trim = false, removeBlanks = false}
	self:assertEquals(d.blankArg, args[d.blankArgKey])
	self:assertEquals(d.spacesArg, args[d.spacesArgKey])
	self:assertEquals(d.untrimmedArg, args[d.untrimmedArgKey])
end

--------------------------------------------------------------------------
-- Test valueFunc
--------------------------------------------------------------------------

function suite:testValueFunc()
	local args = suite.getDefaultArgs{valueFunc = d.defaultValueFunc}
	self:assertEquals(d.valueFuncValue, args['some random key: sdfaliwyda'])
end

function suite:testValueFuncPrecedence()
	local args = suite.getDefaultArgs{
		trim = false,
		removeBlanks = false,
		valueFunc = d.defaultValueFunc
	}
	self:assertEquals(d.valueFuncValue, args[1])
	self:assertEquals(d.valueFuncValue, args['some random key: gekjabawyvy'])
end

function suite:testValueFuncKey()
	local args = suite.getDefaultArgs{valueFunc = function(key, value)
		return 'valueFunc key: '.. key
	end}
	self:assertEquals('valueFunc key: foo', args.foo)
end

function suite:testValueFuncValue()
	local args = suite.getDefaultArgs{valueFunc = function(key, value)
		return 'valueFunc value: '.. value
	end}
	self:assertEquals(
		'valueFunc value: ' .. d.uniqueFrameArg,
		args[d.uniqueFrameArgKey]
	)
end

--------------------------------------------------------------------------
-- Test adding new arguments
--------------------------------------------------------------------------

function suite:testAddingNewArgs()
	local args = suite.getDefaultArgs()
	self:assertEquals(nil, args.newKey)
	args.newKey = 'some new key'
	self:assertEquals('some new key', args.newKey)
end

function suite:testAddingNewBlankArgs()
	local args = suite.getDefaultArgs()
	self:assertEquals(nil, args.newKey)
	args.newKey = ''
	self:assertEquals('', args.newKey)
end

function suite:testAddingNewSpacesArgs()
	local args = suite.getDefaultArgs()
	self:assertEquals(nil, args.newKey)
	args.newKey = ' '
	self:assertEquals(' ', args.newKey)
end

function suite:testOverwriting()
	local args = suite.getDefaultArgs()
	self:assertEquals(d.firstFrameArg, args[1])
	args[1] = 'a new first frame argument'
	self:assertEquals('a new first frame argument', args[1])
end

function suite:testOverwritingWithNil()
	local args = suite.getDefaultArgs()
	self:assertEquals(d.firstFrameArg, args[1])
	args[1] = nil
	self:assertEquals(nil, args[1])
end

function suite:testOverwritingWithBlank()
	local args = suite.getDefaultArgs()
	self:assertEquals(d.firstFrameArg, args[1])
	args[1] = ''
	self:assertEquals('', args[1])
end

function suite:testOverwritingWithSpaces()
	local args = suite.getDefaultArgs()
	self:assertEquals(d.firstFrameArg, args[1])
	args[1] = ' '
	self:assertEquals(' ', args[1])
end

function suite:testReadOnly()
	local args = suite.getDefaultArgs{readOnly = true}
	local function testFunc()
		args.newKey = 'some new value'
	end
	self:assertError(testFunc)
end

function suite:testNoOverwriteExistingKey()
	local args = suite.getDefaultArgs{noOverwrite = true}
	self:assertEquals(d.firstFrameArg, args[1])
	local function testFunc()
		args[1] = 'a new first frame argument'
	end
	self:assertError(testFunc)
end

function suite:testNoOverwriteNewKey()
	local args = suite.getDefaultArgs{noOverwrite = true}
	self:assertEquals(nil, args.newKey)
	args.newKey = 'some new value'
	self:assertEquals('some new value', args.newKey)
end

--------------------------------------------------------------------------
-- Test bad input
--------------------------------------------------------------------------

function suite:testBadFrameInput()
	self:assertError(getArgs, 'foo')
	self:assertError(getArgs, 9)
	self:assertError(getArgs, true)
	self:assertError(getArgs, function() return true end)
end

function suite:testBadOptionsInput()
	self:assertError(getArgs, {}, 'foo')
	self:assertError(getArgs, {}, 9)
	self:assertError(getArgs, {}, true)
	self:assertError(getArgs, {}, function() return true end)
end

function suite:testBadValueFuncInput()
	self:assertError(getArgs, {}, {valueFunc = 'foo'})
	self:assertError(getArgs, {}, {valueFunc = 9})
	self:assertError(getArgs, {}, {valueFunc = true})
	self:assertError(getArgs, {}, {valueFunc = {}})
end

--------------------------------------------------------------------------
-- Test iterator metamethods
--------------------------------------------------------------------------

function suite:testPairs()
	local args = getArgs{'foo', 'bar', baz = 'qux'}
	self:assertNumberOfIterations(3, pairs, args)
end

function suite:testIpairs()
	local args = getArgs{'foo', 'bar', baz = 'qux'}
	self:assertNumberOfIterations(2, ipairs, args)
end

function suite:testNoNilsinPairs()
	-- Test that when we use pairs, we don't iterate over any nil values
	-- that have been memoized.
	local args = getArgs{''}
	local temp = args[1] -- Memoizes the nil
	self:assertNumberOfIterations(0, pairs, args)
end

function suite:testNoNilsinIpairs()
	-- Test that when we use ipairs, we don't iterate over any nil values
	-- that have been memoized.
	local args = getArgs{''}
	local temp = args[1] -- Memoizes the nil
	self:assertNumberOfIterations(0, ipairs, args)
end

function suite:testNoNilsInPairsAfterIndex()
	-- Test that when we use pairs, we don't iterate over any nils that
	-- might have been memoized after a value that is not present in the
	-- original args table is indexed.
	local args = getArgs{}
	local temp = args.someRandomValue -- Memoizes the nil
	self:assertNumberOfIterations(0, pairs, args)
end

function suite:testNoNilsInPairsAfterNewindex()
	-- Test that when we use pairs, we don't iterate over any nils that
	-- might have been memoized after a value that is not present in the
	-- original args table is added to the args table.
	local args = getArgs{}
	args.newKey = nil -- The nil is memoized
	self:assertNumberOfIterations(0, pairs, args)
end

function suite:testNoTableLengthChangeWhileIterating()
	-- Test that the number of arguments doesn't change if we index the
	-- args table while iterating.
	-- (Note that the equivalent test is not needed for new arg table
	-- indexes, as that would be a user error - doing so produces
	-- undetermined behaviour in Lua's next() function.)
	local args = getArgs{'foo', 'bar', baz = 'qux'}
	self:assertNumberOfIterations(3, pairs, args)
	for k, v in pairs(args) do
		local temp = args[k .. 'foo']
	end
	self:assertNumberOfIterations(3, pairs, args)
end

function suite:testPairsPrecedenceWithWhitespace()
	local frame, parent = suite.getFrames(
		d.frameTitle,
		{'  '},
		d.parentTitle,
		{d.firstParentArg}
	)
	local args = getArgs(frame)
	local actual
	for k, v in pairs(args) do
		actual = v
	end
	self:assertEquals(d.firstParentArg, actual)
	-- Check that we have actually iterated.
	self:assertNumberOfIterations(1, pairs, args)
end

function suite:testPairsPrecedenceWithNil()
	local frame, parent = suite.getFrames(
		d.frameTitle,
		{},
		d.parentTitle,
		{d.firstParentArg}
	)
	local args = getArgs(frame)
	local actual
	for k, v in pairs(args) do
		actual = v
	end
	self:assertEquals(d.firstParentArg, actual)
	-- Check that we have actually iterated.
	self:assertNumberOfIterations(1, pairs, args)
end

return suite