Module:Lua-mock/ValueMatcher

--- @module ValueMatcher

local valueMismatchMessage = 'did not match:\n'.. '    was: %s\n'.. 'expected: %s'

local valueCountMismatchMessage = 'mismatch:\n'.. '    was: %d\n'.. 'expected: %d'

--- A matcher is used to determine if a value statisfies some condition. -- The test function is called with the value that shall be tested. -- The function returns `true` if the condition is statisfied. -- Otherwise the function shall return `false` and an error message that -- describes the problem. local function createMatcher( testFn ) return { isMatcher = true, match = testFn } end

local function createEqualityMatcher( matchedValue ) return createMatcher(function( value )       if value == matchedValue then            return true        else            return false, valueMismatchMessage:format(tostring(value), tostring(matchedValue))       end    end) end

local function createTableMatcher( matchedTable ) return createMatcher(function( value )       if value == matchedTable then            return true        elseif type(value) == 'table' then            -- TODO            return false, 'Can\'t recursively match tables at the moment.'        else            return false, valueMismatchMessage:format(tostring(value), tostring(matchedTable))       end    end) end

--- Test a single value. -- Will automatically create a matcher if `matchedValue` is not one. -- Like a matcher it returns `true` if the condition is statisfied or `false` -- with an error message if the condition is not statisfied. local function matchValue( value, matchedValue ) local matcher

if type(matchedValue) == 'table' then if matchedValue.isMatcher then matcher = matchedValue else matcher = createTableMatcher(matchedValue) end else matcher = createEqualityMatcher(matchedValue) end

return matcher.match(value) end

--- Tests multiple values. -- Like a #matchValue it returns `true` if all values matched or `false` with -- the according index and an error message if a value did not match. local function matchValues( values, matchedValues ) if #values ~= #matchedValues then return false, valueCountMismatchMessage:format(#values,                                                      #matchedValues) end

for i,matchedValue in ipairs(matchedValues) do       local value = values[i] local matched, message = matchValue(value, matchedValue) if not matched then return false, i, message end end

return true end

local ValueMatcher = {}

--- Tests if the values match `matchedValues`. -- -- @param value -- -- @param matchedValues -- A list that consists of regular values or matchers. -- -- @return -- `true` if all values match or `false` if at least one don't. -- Also returns the value index and a reason when failing. function ValueMatcher.matches( value, matchedValues ) return matchValues(value, matchedValues) end

--- Matches any value. ValueMatcher.any = createMatcher(function( value )   return true end)

--- Matches any value but nil. ValueMatcher.notNil = createMatcher(function( value )   if value == nil then        return false, 'was nil.'    else        return true    end end)

--- Matches a specific value type. ValueMatcher.matchType = function( typeName ) return createMatcher(function( value )       if type(value) == typeName then            return true        else            return false, ('was not a %s, but a %s.'):format(typeName, type(value))        end    end) end

--- Matches a boolean value. ValueMatcher.anyBoolean = ValueMatcher.matchType('boolean')

--- Matches a number. ValueMatcher.anyNumber  = ValueMatcher.matchType('number')

--- Matches a string. ValueMatcher.anyString  = ValueMatcher.matchType('string')

--- Matches a table. ValueMatcher.anyTable   = ValueMatcher.matchType('table')

--- Matches a function. ValueMatcher.anyFunction = ValueMatcher.matchType('function')

--- Matches a thread. ValueMatcher.anyThread  = ValueMatcher.matchType('thread')

--- Matches a user data. ValueMatcher.anyUserData = ValueMatcher.matchType('userdata')

return ValueMatcher