package; import flixel.math.FlxPoint; import flixel.math.FlxRect; import haxe.PosInfos; import massive.munit.Assert; using flixel.util.FlxArrayUtil; /** * @see https://github.com/HaxeFlixel/flixel/tree/dev/tests/unit */ @:nullSafety class FunkinAssert { /** * Assert if `expected` is within `margin` of `actual`, and fail if not. * Useful for comparting Float values. * * @param expected The expected value of the test. * @param actual The actual value of the test. * @param margin The allowed margin of error between the expected and actual values. * @param info Info on the position this function was called from. Magic value, passed automatically. */ public static function areNear(expected:Float, ?actual:Float, margin:Float = 0.001, ?info:PosInfos):Void { if (actual == null) Assert.fail('Value [$actual] is null, and cannot be compared to [$expected]', info); if (areNearHelper(expected, actual)) Assert.assertionCount++; else Assert.fail('Value [$actual] is not within [$margin] of [$expected]', info); } public static function rectsNear(expected:FlxRect, ?actual:FlxRect, margin:Float = 0.001, ?info:PosInfos):Void { if (actual == null) Assert.fail('Value [$actual] is null, and cannot be compared to [$expected]', info); var areNear = areNearHelper(expected.x, actual.x, margin) && areNearHelper(expected.y, actual.y, margin) && areNearHelper(expected.width, actual.width, margin) && areNearHelper(expected.height, actual.height, margin); if (areNear) Assert.assertionCount++; else Assert.fail('Value [$actual] is not within [$margin] of [$expected]', info); } static function areNearHelper(expected:Float, actual:Float, margin:Float = 0.001):Bool { return actual >= expected - margin && actual <= expected + margin; } public static function arraysEqual(expected:Array, ?actual:Array, ?info:PosInfos):Void { if (actual == null) Assert.fail('Value [$actual] is null, and cannot be compared to [$expected]', info); if (expected.equals(actual)) Assert.assertionCount++; else Assert.fail('\nExpected\n ${expected}\nbut was\n ${actual}\n', info); } public static function arraysNotEqual(expected:Array, ?actual:Array, ?info:PosInfos):Void { if (actual == null) Assert.fail('Value [$actual] is null, and cannot be compared to [$expected]', info); if (!expected.equals(actual)) Assert.assertionCount++; else Assert.fail('\nValue\n ${actual}\nwas equal to\n ${expected}\n', info); } public static function pointsEqual(expected:FlxPoint, ?actual:FlxPoint, ?msg:String, ?info:PosInfos) { if (actual == null) Assert.fail('Value [$actual] is null, and cannot be compared to [$expected]', info); if (expected.equals(actual)) Assert.assertionCount++; else if (msg != null) Assert.fail(msg, info); else Assert.fail("Value [" + actual + "] was not equal to expected value [" + expected + "]", info); } public static function pointsNotEqual(expected:FlxPoint, ?actual:FlxPoint, ?msg:String, ?info:PosInfos) { if (actual == null) Assert.fail('Value [$actual] is null, and cannot be compared to [$expected]', info); if (!expected.equals(actual)) Assert.assertionCount++; else if (msg != null) Assert.fail(msg, info); else Assert.fail("Value [" + actual + "] was equal to value [" + expected + "]", info); } /** * Execute `targetFunc`, expecting it to throw an exception. * If it doesn't, or if the exception doesn't validate against the provided `predicate`, fail. */ public static function validateThrows(targetFunc:Void->Void, predicate:Dynamic->Bool, ?info:PosInfos) { try { targetFunc(); Assert.fail("Expected exception to be thrown, got no failure.", info); } catch (e:Dynamic) { if (predicate(e)) { Assert.assertionCount++; } else { Assert.fail('Expected exception to match predicate, but failed (got ${e})', info); } } } /** * Execute `targetFunc`, expecting it to throw a `json2object.Error.CustomFunctionException` with a message matching `expected`. * I made this its own function since it's the most common specific use case of `validateThrows`. */ public static function validateThrowsJ2OCustom(targetFunc:Void->Void, expected:String, ?info:PosInfos) { var predicate:Dynamic->Bool = function(err:Dynamic):Bool { if (!Std.isOfType(err, json2object.Error)) Assert.fail('Expected error of type json2object.Error, got ${Type.typeof(err)}'); switch (err) { case json2object.Error.CustomFunctionException(msg, pos): if (msg != expected) Assert.fail('Expected message [${expected}], got [${msg}].'); default: Assert.fail('Expected error of type CustomFunctionException, got [${err}].'); } return true; }; validateThrows(targetFunc, predicate, info); } static var capturedTraces:Array = []; public static function initAssertTrace():Void { var oldTrace = haxe.Log.trace; haxe.Log.trace = function(v:Dynamic, ?infos:haxe.PosInfos) { onTrace(v, infos); // oldTrace(v, infos); }; } public static function clearTraces():Void { capturedTraces = []; } @:nullSafety(Off) // Why isn't haxe.std null-safe? static function onTrace(v:Dynamic, ?infos:haxe.PosInfos) { // var str:String = haxe.Log.formatOutput(v, infos); var str:String = Std.string(v); capturedTraces.push(str); #if (sys && echo_traces) Sys.println('[TESTLOG] $str'); #end } /** * Check the first string that was traced and validate it. * @param expected */ public static inline function assertTrace(expected:String):Void { var actual:Null = capturedTraces.shift(); Assert.isNotNull(actual); Assert.areEqual(expected, actual); } /** * Check the first string that was traced and validate it. * @param expected */ public static inline function assertLastTrace(expected:String):Void { var actual:Null = capturedTraces.pop(); Assert.isNotNull(actual); Assert.areEqual(expected, actual); } }