diff --git a/lua/gluatest/expectations/negative.lua b/lua/gluatest/expectations/negative.lua index b5a938f5..fad5bef3 100644 --- a/lua/gluatest/expectations/negative.lua +++ b/lua/gluatest/expectations/negative.lua @@ -3,6 +3,7 @@ local TypeID = TypeID local IsValid = IsValid local isstring = isstring local string_format = string.format +local Arg = include( "arg.lua" ) local GetDiff = include( "utils/table_diff.lua" ) -- Inverse checks @@ -231,10 +232,53 @@ return function( subject, ... ) end end + --- @deprecated function expectations.haveBeenCalled() GLuaTest.DeprecatedNotice( "to.haveBeenCalled()", "was.called()" ) return expectations.called() end + --- Checks if the given argument is equal to the expected argument + --- Or, if the expected argument is an argument matcher, checks if the given argument matches the matcher + --- @param givenArg any The argument that was given to the stub + --- @param expectedArg any The expected argument, which can be a matcher or a value + local function checkArg( givenArg, expectedArg ) + if givenArg == expectedArg then return true end + + if Arg.IsArgMatcher( expectedArg ) then + local argMatcher = expectedArg --[[@as GLuaTest_StubArg]] + return argMatcher.check( givenArg ) + end + + return false + end + + --- Expects the subject stub to have not been called with exactly the given arguments + --- @param ... any + function expectations.calledWith( ... ) + assert( subject.IsStub, ".calledWith expects a stub" ) + --- @cast subject GLuaTest_Stub + + local expected = { ... } + + for _, call in ipairs( subject.callHistory or {} ) do + if #call == #expected then + local match = true + + for k = 1, #expected do + if not checkArg( call[k], expected[k] ) then + match = false + break + end + end + + if match then + local formattedArgs = table.concat( expected, ", " ) + i.expected( "to not have been called with arguments: %s", formattedArgs ) + end + end + end + end + return expectations end diff --git a/lua/gluatest/expectations/positive.lua b/lua/gluatest/expectations/positive.lua index 92464d9b..b9779ab2 100644 --- a/lua/gluatest/expectations/positive.lua +++ b/lua/gluatest/expectations/positive.lua @@ -3,6 +3,7 @@ local TypeID = TypeID local IsValid = IsValid local isstring = isstring local string_format = string.format +local Arg = include( "arg.lua" ) local GetDiff = include( "utils/table_diff.lua" ) -- Positive checks @@ -220,9 +221,10 @@ return function( subject, ... ) end --- Expects the subject stub to have been called, optionally with an expected number of calls - --- @param n? number + --- @param n? number Expected number of calls, if nil then expects at least one call function expectations.called( n ) assert( subject.IsStub, ".called expects a stub" ) + --- @cast subject GLuaTest_Stub local callCount = subject.callCount @@ -237,15 +239,54 @@ return function( subject, ... ) end end + --- @deprecated function expectations.haveBeenCalled( n ) GLuaTest.DeprecatedNotice( "to.haveBeenCalled( number )", "was.called( number )" ) return expectations.called( n ) end - -- Soon.. - -- - -- function expectations.haveBeenCalledWith( ... ) - -- end + -- Called Expectations + + --- Checks if the given argument is equal to the expected argument + --- Or, if the expected argument is an argument matcher, checks if the given argument matches the matcher + --- @param givenArg any The argument that was given to the stub + --- @param expectedArg any The expected argument, which can be a matcher or a value + local function checkArg( givenArg, expectedArg ) + if givenArg == expectedArg then return true end + + if Arg.IsArgMatcher( expectedArg ) then + local argMatcher = expectedArg --[[@as GLuaTest_StubArg]] + return argMatcher.check( givenArg ) + end + + return false + end + + --- Expects the subject stub to have been called with exactly the given arguments + --- @param ... any + function expectations.calledWith( ... ) + assert( subject.IsStub, ".calledWith expects a stub" ) + --- @cast subject GLuaTest_Stub + + local expected = { ... } + + for _, call in ipairs( subject.callHistory or {} ) do + if #call == #expected then + local match = true + + for k = 1, #expected do + if not checkArg( call[k], expected[k] ) then + match = false + break + end + end + + if match then return end + end + end + + i.expected( "to have been called with arguments: %s", table.concat( expected, ", " ) ) + end return expectations end diff --git a/lua/gluatest/init.lua b/lua/gluatest/init.lua index d6303149..7497dfd1 100644 --- a/lua/gluatest/init.lua +++ b/lua/gluatest/init.lua @@ -35,7 +35,7 @@ GLuaTest = { AddCSLuaFile( "gluatest/utils/version.lua" ) -- /stubs/* - AddCSLuaFile( "gluatest/stubs/stub_maker.lua" ) + AddCSLuaFile( "gluatest/stubs/stubMaker.lua" ) -- /runner/* AddCSLuaFile( "gluatest/runner/colors.lua" ) diff --git a/lua/gluatest/runner/helpers.lua b/lua/gluatest/runner/helpers.lua index 9f6536af..3c4ecb08 100644 --- a/lua/gluatest/runner/helpers.lua +++ b/lua/gluatest/runner/helpers.lua @@ -8,7 +8,7 @@ local Helpers = { local expect = include( "gluatest/expectations/expect.lua" ) --- @type GLuaTest_StubMaker -local stubMaker = include( "gluatest/stubs/stub_maker.lua" ) +local stubMaker = include( "gluatest/stubs/stubMaker.lua" ) --- Gets a unique case ID --- @return string diff --git a/lua/gluatest/stubs/arg.lua b/lua/gluatest/stubs/arg.lua new file mode 100644 index 00000000..863ef80d --- /dev/null +++ b/lua/gluatest/stubs/arg.lua @@ -0,0 +1,197 @@ +local argMeta = { + __tostring = function( self ) + return "Arg Matcher: " .. self.name + end, +} + +--- Creates a new argument matcher +--- @param name string The name of the matcher (for use in error messages) +--- @param check fun(any): boolean A function that returns true if the argument matches +local function New( name, check ) + --- @class GLuaTest_StubArg + local arg = { + --- @type string + --- The name of the matcher, used in error messages + name = name, + + --- A function that returns true if the argument matches + check = check, + } + + return setmetatable( arg, argMeta ) +end + +--- @alias GLuaTest_StubArg_Matcher fun(any): boolean + +--- @class GLuaTest_StubArgs +local Arg = { + --- The metatable for all argument matchers + argMeta = argMeta, + + New = New, + + --- Checks if the given argument is an argument matcher + --- @param arg any The argument to check + IsArgMatcher = function( arg ) + return getmetatable( arg ) == argMeta + end, + + --- Matches any argument + Any = New( "Any", function( _ ) + return true + end ), + + --- Matches nil + Nil = New( "Nil", function( arg ) + return arg == nil + end ), + + --- Matches any boolean + Boolean = New( "Any Boolean", function( arg ) + return arg == true or arg == false + end ), + + --- Matches any number + Number = New( "Any Number", function( arg ) + return type( arg ) == "number" + end ), + + --- Matches any string + String = New( "Any String", function( arg ) + return type( arg ) == "string" + end ), + + --- Matches any table + Table = New( "Any Table", function( arg ) + return type( arg ) == "table" + end ), + + --- Matches any function + Function = New( "Any Function", function( arg ) + return type( arg ) == "function" + end ), + + --- Matches any userdata + Userdata = New( "Any Userdata", function( arg ) + return type( arg ) == "userdata" + end ), + + --- Matches any thread + Thread = New( "Any Thread", function( arg ) + return type( arg ) == "thread" + end ), + + -- GMod Types + + --- Matches any Vector + Vector = New( "Any Vector", function( arg ) + return type( arg ) == "Vector" + end ), + + --- Matches any Angle + Angle = New( "Any Angle", function( arg ) + return type( arg ) == "Angle" + end ), + + --- Matches any Color + Color = New( "Any Color", function( arg ) + return debug.getmetatable( arg ) == FindMetaTable( "Color" ) + end ), + + --- Matches any VMatrix + Matrix = New( "Any Matrix", function( arg ) + return type( arg ) == "VMatrix" + end ), + + --- Matches any Entity + Entity = New( "Any Entity", function( arg ) + return type( arg ) == "Entity" + end ), + + --- Matches any Player + Player = New( "Any Player", function( arg ) + return type( arg ) == "Player" + end ), + + --- Matches any Weapon + Weapon = New( "Any Weapon", function( arg ) + return type( arg ) == "Weapon" + end ), + + --- Matches any Vehicle + Vehicle = New( "Any Vehicle", function( arg ) + return type( arg ) == "Vehicle" + end ), + + --- Matches any Material + Material = New( "Any Material", function( arg ) + return type( arg ) == "IMaterial" + end ), + + --- Matches any PhysObj + PhysObj = New( "Any PhysObj", function( arg ) + return type( arg ) == "PhysObj" + end ), + + --- Matches any CTakeDamageInfo + DamageInfo = New( "Any CTakeDamageInfo", function( arg ) + return type( arg ) == "CTakeDamageInfo" + end ), + + --- Matches any CMoveData + MoveData = New( "Any CMoveData", function( arg ) + return type( arg ) == "CMoveData" + end ), + + --- Matches any CUserCmd + UserCmd = New( "Any CUserCmd", function( arg ) + return type( arg ) == "CUserCmd" + end ), + + --- Matches any CSoundPatch + SoundPatch = New( "Any CSoundPatch", function( arg ) + return type( arg ) == "CSoundPatch" + end ), + + --- Matches any File + File = New( "Any File", function( arg ) + -- Apparently if the file doesn't exist, type( arg) will return "no value" ? + return type( arg ) == "File" + end ), + + --- Matches any SurfaceInfo + SurfaceInfo = New( "Any SurfaceInfo", function( arg ) + return type( arg ) == "SurfaceInfo" + end ), + + --- Matches any ConVar + ConVar = New( "Any ConVar", function( arg ) + return type( arg ) == "ConVar" + end ), + + + -- Clientside + + + --- Matches any Panel + Panel = New( "Any Panel", function( arg ) + return type( arg ) == "Panel" + end ), + + --- Matches any CLuaParticle + Particle = New( "Any Particle", function( arg ) + return type( arg ) == "CLuaParticle" + end ), + + --- Matches any CLuaEmitter + ParticleEmitter = New( "Any ParticleEmitter", function( arg ) + return type( arg ) == "CLuaEmitter" + end ), + + --- Matches any IMesh + Mesh = New( "Any Mesh", function( arg ) + return type( arg ) == "IMesh" + end ), +} + +return Arg diff --git a/lua/gluatest/stubs/stub_maker.lua b/lua/gluatest/stubs/stubMaker.lua similarity index 100% rename from lua/gluatest/stubs/stub_maker.lua rename to lua/gluatest/stubs/stubMaker.lua