mirror of
https://github.com/ninjamuffin99/Funkin.git
synced 2024-11-21 22:33:26 +00:00
Unit Test Suite (#119)
* Initial test suite * Fix some build warnings * Implemented working unit tests with coverage * Reduced some warnings * Fix a mac-specific issue * Add 2 additional unit test classes. * Multiple new unit tests * Some fixins * Remove auto-generated file * WIP on hiding ignored tests * Added list of debug hotkeys * Remove old website * Remove empty file * Add more unit tests * Fix bug where arrows would nudge BF * Fix bug where ctrl/alt would flash capsules * Fixed bug where bf-old easter egg broke * Remove duplicate lines * More test-related stuff * Some code cleanup * Add mocking and a test assets folder * More TESTS! * Update Hmm... * Update artist on Monster * More minor fixes to individual functions * 1.38% unit test coverage! * Even more tests? :O * More unit test work * Rework migration for BaseRegistry * gameover fix * Fix an issue with Lime * Fix issues with version parsing on data files * 100 total unit tests! * Added even MORE unit tests! * Additional test tweaks :3 * Fixed tests on windows by updating libraries. * Set versions for flixel-ui and hamcrest --------- Co-authored-by: Cameron Taylor <cameron.taylor.ninja@gmail.com>
This commit is contained in:
parent
ab34bbdcee
commit
42d8d55067
|
@ -1 +0,0 @@
|
|||
funkin.me
|
31
docs/Funkin' Debug Hotkeys.md
Normal file
31
docs/Funkin' Debug Hotkeys.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Funkin' Debug Hotkeys
|
||||
|
||||
`F4` (EVERYWHERE) - Leave Current State and move to Main Menu
|
||||
`F5` (EVERYWHERE) - Hot Reload Data Files
|
||||
|
||||
`Y` (Title Screen) - WOAH
|
||||
|
||||
`~` (Main Menu) - Access Debug Menu
|
||||
|
||||
`U` (Play) - Open Stage Editor State
|
||||
`H` (Play) - Show/Hide HUD
|
||||
`1` (Play) - End Song
|
||||
`2` (Play) - Add 10% Health
|
||||
`3` (Play) - Subtract 5% Health
|
||||
`7` (Play) - (NOT WORKING) Open Chart Editor
|
||||
`8` (Play) - Open Animation Editor
|
||||
`9` (Play) - (Easter Egg) Classic Health Icon
|
||||
`PGUP`/`Fn+Up` (Play) - Skip Forward In Time
|
||||
`PGDN`/`Fn+Down` (Play) - 🦃 That's right, we're going to go BACK IN TIME
|
||||
|
||||
`F` (Freeplay Menu) - Move to Favorites
|
||||
`P` (Freeplay Menu) - Switch to Pico (probably doesn't work)
|
||||
`T` (Freeplay Menu) - Start typing in search bar
|
||||
`Q` (Freeplay Menu) - Back one letter
|
||||
`E` (Freeplay Menu) - Forward one letter
|
||||
|
||||
`Arrows` (Stage Editor) - Move Prop
|
||||
`Ctrl-Z` (Stage Editor) - Undo
|
||||
`Y` (Stage Editor) - Leave Stage Editor
|
||||
|
||||
`H` (Pause Menu) - Hide the Pause Menu UI (good for screenshots!)
|
Binary file not shown.
Before Width: | Height: | Size: 232 KiB |
|
@ -1,60 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en">
|
||||
|
||||
<!--
|
||||
@brandybuizel
|
||||
@ninja_muffin2.4
|
||||
-->
|
||||
|
||||
<!--Let's get meta bois-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Friday Night Funkin'</title>
|
||||
<meta name="description" content="A dope ass rhythm game">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="styles.css" rel="stylesheet" type="text/css">
|
||||
<!-- <link href="website/mm.ico" rel="icon" type="image/x-icon"> -->
|
||||
<link rel="shortcut icon" type="image/png" href="../art/icon.png" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!--Header-->
|
||||
<header>
|
||||
</header>
|
||||
|
||||
|
||||
<marquee width="1920" scrollamount="2" scrolldelay="10" truespeed="truespeed">Friday Night Funkin</marquee>
|
||||
<!--Hot tortilla wrap-->
|
||||
<div id="wrapper">
|
||||
<h1>Friday Night Funkin' - Rhythm game extraordinaire</h1>
|
||||
|
||||
|
||||
<div id="coolervidwrapper">
|
||||
<div class='videoWrapper'>
|
||||
<iframe width="560" height="349" src="https://www.youtube.com/embed/HMNKUo3CCpU" frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="coolBox hovertext" id="twitter">
|
||||
<p class="description">Friday Night Funkin' is a dope ass rhythm game.</p>
|
||||
<p class='description'>It is made by <a href="https://twitter.com/ninja_muffin99">ninjamuffin99 (programmer)</a>, <a
|
||||
href="https://twitter.com/PhantomArcade3K">PhantomArcade (animator)</a>, <a
|
||||
href="https://twitter.com/evilsk8r">evilsk8r (artist)</a>,
|
||||
and <a href="https://twitter.com/kawaisprite">Kawaisprite (musician)</a> originally for Ludum Dare 47.</p>
|
||||
</div>
|
||||
|
||||
<div class="linktext hovertext" id="ng">
|
||||
<a href="https://www.newgrounds.com/portal/view/770371">Play demo on Newgrounds</a>
|
||||
</div>
|
||||
|
||||
<div class='linktext hovertext' id="itch">
|
||||
<a href="https://ninja-muffin24.itch.io/funkin">Support the game on Itch.io</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,7 +0,0 @@
|
|||
this is for funkin website stuff
|
||||
if you're compiling game, you can ignore this folder completely!
|
||||
|
||||
if not, dawg check out [funkin.me](https://funkin.me)
|
||||
|
||||
note that i probably won't accept any major pull requests for this
|
||||
cuz I wanna make the website as a nice lil project to teach myself HTML and CSS and all that!!
|
|
@ -1,97 +0,0 @@
|
|||
.coolBox {
|
||||
background: #1a1a1aCC;
|
||||
margin: 40px 4vw;
|
||||
padding: 25px;
|
||||
font-size: 120%;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#wrapper
|
||||
{
|
||||
padding: 20px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
marquee {
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
body {
|
||||
background-image: url('img/skin-funkin-cardbordtoast.jpg');
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
background-size: cover;
|
||||
color:white;
|
||||
background-color: #000;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
|
||||
}
|
||||
|
||||
a { color:inherit;
|
||||
text-decoration: none;}
|
||||
|
||||
|
||||
.hovertext a{
|
||||
color:white;
|
||||
text-shadow: 0px 0px #00000077;
|
||||
transition: color 1s, text-shadow 1s;
|
||||
}
|
||||
|
||||
.hovertext a:hover
|
||||
{
|
||||
text-shadow: 2px 2px #00000077;
|
||||
color: #ffb50e;
|
||||
}
|
||||
|
||||
.linktext {
|
||||
text-align: center;
|
||||
display:block;
|
||||
font-size: 130%;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.linktext a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.linktext a:hover {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#itch a:hover
|
||||
{
|
||||
color: #fa5c5c;
|
||||
}
|
||||
#twitter a:hover
|
||||
{
|
||||
color: #00acee;
|
||||
}
|
||||
|
||||
|
||||
#coolervidwrapper {
|
||||
padding-left: min(12em, 75%);
|
||||
padding-right: min(12em, 75%);
|
||||
min-width: 30%;
|
||||
}
|
||||
|
||||
.videoWrapper {
|
||||
position: relative;
|
||||
padding-bottom: 56.25%; /* 16:9 */
|
||||
height: 0;
|
||||
|
||||
max-block-size: 1400px;
|
||||
}
|
||||
.videoWrapper iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
}
|
37
hmm.json
37
hmm.json
|
@ -24,7 +24,7 @@
|
|||
{
|
||||
"name": "flixel-ui",
|
||||
"type": "haxelib",
|
||||
"version": "2.4.0"
|
||||
"version": "2.5.0"
|
||||
},
|
||||
{
|
||||
"name": "flxanimate",
|
||||
|
@ -38,6 +38,11 @@
|
|||
"type": "haxelib",
|
||||
"version": "3.5.0"
|
||||
},
|
||||
{
|
||||
"name": "hamcrest",
|
||||
"type": "haxelib",
|
||||
"version": "3.0.0"
|
||||
},
|
||||
{
|
||||
"name": "haxeui-core",
|
||||
"type": "git",
|
||||
|
@ -92,12 +97,33 @@
|
|||
"url": "https://github.com/EliteMasterEric/lime"
|
||||
},
|
||||
{
|
||||
"name": "mockatoo",
|
||||
"name": "mconsole",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "master",
|
||||
"url": "https://github.com/massive-oss/mconsole"
|
||||
},
|
||||
{
|
||||
"name": "mcover",
|
||||
"type": "git",
|
||||
"dir": "src",
|
||||
"ref": "master",
|
||||
"url": "https://github.com/massive-oss/mcover"
|
||||
},
|
||||
{
|
||||
"name": "mockatoo",
|
||||
"type": "git",
|
||||
"dir": "src",
|
||||
"ref": "master",
|
||||
"url": "https://github.com/EliteMasterEric/mockatoo"
|
||||
},
|
||||
{
|
||||
"name": "munit",
|
||||
"type": "git",
|
||||
"dir": "src",
|
||||
"ref": "master",
|
||||
"url": "https://github.com/EliteMasterEric/MassiveUnit"
|
||||
},
|
||||
{
|
||||
"name": "openfl",
|
||||
"type": "git",
|
||||
|
@ -121,6 +147,13 @@
|
|||
"name": "tink_json",
|
||||
"type": "haxelib",
|
||||
"version": "0.11.0"
|
||||
},
|
||||
{
|
||||
"name": "hmm",
|
||||
"type": "git",
|
||||
"dir": null,
|
||||
"ref": "d514d7786cabf18b90e60fcee38399fd44c2ddfb",
|
||||
"url": "https://github.com/andywhite37/hmm"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -180,9 +180,15 @@ class Conductor
|
|||
*/
|
||||
public static function forceBPM(?bpm:Float = null)
|
||||
{
|
||||
if (bpm != null) trace('[CONDUCTOR] Forcing BPM to ' + bpm);
|
||||
if (bpm != null)
|
||||
{
|
||||
trace('[CONDUCTOR] Forcing BPM to ${bpm}');
|
||||
}
|
||||
else
|
||||
trace('[CONDUCTOR] Resetting BPM to default');
|
||||
{
|
||||
// trace('[CONDUCTOR] Resetting BPM to default');
|
||||
}
|
||||
|
||||
Conductor.bpmOverride = bpm;
|
||||
}
|
||||
|
||||
|
@ -272,9 +278,9 @@ class Conductor
|
|||
if (currentTimeChange.timeStamp > 0.0 && timeChanges.length > 0)
|
||||
{
|
||||
var prevTimeChange:SongTimeChange = timeChanges[timeChanges.length - 1];
|
||||
currentTimeChange.beatTime = prevTimeChange.beatTime
|
||||
+ ((currentTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC)
|
||||
+ 0.01;
|
||||
currentTimeChange.beatTime = FlxMath.roundDecimal(prevTimeChange.beatTime
|
||||
+ ((currentTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC),
|
||||
4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -282,7 +288,10 @@ class Conductor
|
|||
timeChanges.push(currentTimeChange);
|
||||
}
|
||||
|
||||
trace('Done mapping time changes: ' + timeChanges);
|
||||
if (timeChanges.length > 0)
|
||||
{
|
||||
trace('Done mapping time changes: ${timeChanges}' + timeChanges);
|
||||
}
|
||||
|
||||
// Update currentStepTime
|
||||
Conductor.update(Conductor.songPosition);
|
||||
|
|
|
@ -50,7 +50,7 @@ enum Control
|
|||
#end
|
||||
}
|
||||
|
||||
@:enum
|
||||
enum
|
||||
abstract Action(String) to String from String
|
||||
{
|
||||
var UI_UP = "ui_up";
|
||||
|
|
|
@ -492,22 +492,6 @@ class FreeplayState extends MusicBeatSubState
|
|||
}
|
||||
}
|
||||
|
||||
// if (regexp != null)
|
||||
// tempSongs = songs.filter(item -> regexp.match(item.songName));
|
||||
|
||||
// tempSongs.sort(function(a, b):Int
|
||||
// {
|
||||
// var tempA = a.songName.toUpperCase();
|
||||
// var tempB = b.songName.toUpperCase();
|
||||
|
||||
// if (tempA < tempB)
|
||||
// return -1;
|
||||
// else if (tempA > tempB)
|
||||
// return 1;
|
||||
// else
|
||||
// return 0;
|
||||
// });
|
||||
|
||||
for (i in 0...tempSongs.length)
|
||||
{
|
||||
var funnyMenu:SongMenuItem = new SongMenuItem(FlxG.width, (i * 150) + 160, tempSongs[i].songName);
|
||||
|
|
|
@ -2,6 +2,7 @@ package funkin.data;
|
|||
|
||||
import openfl.Assets;
|
||||
import funkin.util.assets.DataAssets;
|
||||
import funkin.util.VersionUtil;
|
||||
import haxe.Constraints.Constructible;
|
||||
|
||||
/**
|
||||
|
@ -24,16 +25,23 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
|
||||
final entries:Map<String, T>;
|
||||
|
||||
/**
|
||||
* The version rule to use when loading entries.
|
||||
* If the entry's version does not match this rule, migration is needed.
|
||||
*/
|
||||
final versionRule:thx.semver.VersionRule;
|
||||
|
||||
// public abstract static final instance:BaseRegistry<T, J> = new BaseRegistry<>();
|
||||
|
||||
/**
|
||||
* @param registryId A readable ID for this registry, used when logging.
|
||||
* @param dataFilePath The path (relative to `assets/data`) to search for JSON files.
|
||||
*/
|
||||
public function new(registryId:String, dataFilePath:String)
|
||||
public function new(registryId:String, dataFilePath:String, versionRule:thx.semver.VersionRule = null)
|
||||
{
|
||||
this.registryId = registryId;
|
||||
this.dataFilePath = dataFilePath;
|
||||
this.versionRule = versionRule == null ? "1.0.x" : versionRule;
|
||||
|
||||
this.entries = new Map<String, T>();
|
||||
}
|
||||
|
@ -125,6 +133,13 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
return 'Registry(' + registryId + ', ${countEntries()} entries)';
|
||||
}
|
||||
|
||||
public function fetchEntryVersion(id:String):Null<thx.semver.Version>
|
||||
{
|
||||
var entryStr:String = loadEntryFile(id);
|
||||
var entryVersion:thx.semver.Version = VersionUtil.getVersionFromJSON(entryStr);
|
||||
return entryVersion;
|
||||
}
|
||||
|
||||
function log(message:String):Void
|
||||
{
|
||||
trace('[' + registryId + '] ' + message);
|
||||
|
@ -154,10 +169,36 @@ abstract class BaseRegistry<T:(IRegistryEntry<J> & Constructible<EntryConstructo
|
|||
/**
|
||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
||||
*
|
||||
* NOTE: Must be implemented on the implementation class annd
|
||||
* NOTE: Must be implemented on the implementation class.
|
||||
*/
|
||||
public abstract function parseEntryData(id:String):Null<J>;
|
||||
|
||||
/**
|
||||
* Read, parse, and validate the JSON data and produce the corresponding data object,
|
||||
* accounting for old versions of the data.
|
||||
*
|
||||
* NOTE: Extend this function to handle migration.
|
||||
*/
|
||||
public function parseEntryDataWithMigration(id:String, version:thx.semver.Version):Null<J>
|
||||
{
|
||||
// If a version rule is not specified, do not check against it.
|
||||
if (versionRule == null || VersionUtil.validateVersion(version, versionRule))
|
||||
{
|
||||
return parseEntryData(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw '[${registryId}] Entry ${id} does not support migration.';
|
||||
}
|
||||
|
||||
// Example:
|
||||
// if (VersionUtil.validateVersion(version, "0.1.x")) {
|
||||
// return parseEntryData_v0_1_x(id);
|
||||
// } else {
|
||||
// super.parseEntryDataWithMigration(id, version);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the list of scripted class names to load.
|
||||
* @return An array of scripted class names.
|
||||
|
|
|
@ -11,13 +11,15 @@ class LevelRegistry extends BaseRegistry<Level, LevelData>
|
|||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateStageData()` function.
|
||||
*/
|
||||
public static final LEVEL_DATA_VERSION:String = "1.0.0";
|
||||
public static final LEVEL_DATA_VERSION:thx.semver.Version = "1.0.0";
|
||||
|
||||
public static final LEVEL_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
||||
|
||||
public static final instance:LevelRegistry = new LevelRegistry();
|
||||
|
||||
public function new()
|
||||
{
|
||||
super('LEVEL', 'levels');
|
||||
super('LEVEL', 'levels', LEVEL_DATA_VERSION_RULE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -34,7 +36,7 @@ class LevelRegistry extends BaseRegistry<Level, LevelData>
|
|||
|
||||
if (parser.errors.length > 0)
|
||||
{
|
||||
trace('Failed to parse entry data: ${id}');
|
||||
trace('[${registryId}] Failed to parse entry data: ${id}');
|
||||
for (error in parser.errors)
|
||||
{
|
||||
trace(error);
|
||||
|
|
|
@ -11,7 +11,9 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
|
|||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateNoteStyleData()` function.
|
||||
*/
|
||||
public static final NOTE_STYLE_DATA_VERSION:String = "1.0.0";
|
||||
public static final NOTE_STYLE_DATA_VERSION:thx.semver.Version = "1.0.0";
|
||||
|
||||
public static final NOTE_STYLE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x";
|
||||
|
||||
public static final DEFAULT_NOTE_STYLE_ID:String = "funkin";
|
||||
|
||||
|
@ -19,7 +21,7 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
|
|||
|
||||
public function new()
|
||||
{
|
||||
super('NOTESTYLE', 'notestyles');
|
||||
super('NOTESTYLE', 'notestyles', NOTE_STYLE_DATA_VERSION_RULE);
|
||||
}
|
||||
|
||||
public function fetchDefault():NoteStyle
|
||||
|
@ -43,7 +45,7 @@ class NoteStyleRegistry extends BaseRegistry<NoteStyle, NoteStyleData>
|
|||
|
||||
if (parser.errors.length > 0)
|
||||
{
|
||||
trace('Failed to parse entry data: ${id}');
|
||||
trace('[${registryId}] Failed to parse entry data: ${id}');
|
||||
for (error in parser.errors)
|
||||
{
|
||||
trace(error);
|
||||
|
|
|
@ -40,27 +40,6 @@ class DJBoyfriend extends FlxSprite
|
|||
{
|
||||
super.update(elapsed);
|
||||
|
||||
if (FlxG.keys.justPressed.LEFT)
|
||||
{
|
||||
animOffsets["confirm"] = [animOffsets["confirm"][0] + 1, animOffsets["confirm"][1]];
|
||||
applyAnimOffset();
|
||||
}
|
||||
else if (FlxG.keys.justPressed.RIGHT)
|
||||
{
|
||||
animOffsets["confirm"] = [animOffsets["confirm"][0] - 1, animOffsets["confirm"][1]];
|
||||
applyAnimOffset();
|
||||
}
|
||||
else if (FlxG.keys.justPressed.UP)
|
||||
{
|
||||
animOffsets["confirm"] = [animOffsets["confirm"][0], animOffsets["confirm"][1] + 1];
|
||||
applyAnimOffset();
|
||||
}
|
||||
else if (FlxG.keys.justPressed.DOWN)
|
||||
{
|
||||
animOffsets["confirm"] = [animOffsets["confirm"][0], animOffsets["confirm"][1] - 1];
|
||||
applyAnimOffset();
|
||||
}
|
||||
|
||||
switch (currentState)
|
||||
{
|
||||
case Intro:
|
||||
|
|
|
@ -103,9 +103,6 @@ class SongMenuItem extends FlxSpriteGroup
|
|||
y = CoolUtil.coolLerp(y, targetPos.y, 0.4);
|
||||
}
|
||||
|
||||
if (FlxG.keys.justPressed.ALT) selected = false;
|
||||
if (FlxG.keys.justPressed.CONTROL) selected = true;
|
||||
|
||||
super.update(elapsed);
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ class PolymodHandler
|
|||
* Bug fixes increment the patch version, new features increment the minor version.
|
||||
* Changes that break old mods increment the major version.
|
||||
*/
|
||||
static final API_VERSION = "0.1.0";
|
||||
static final API_VERSION:String = "0.1.0";
|
||||
|
||||
/**
|
||||
* Where relative to the executable that mods are located.
|
||||
|
|
|
@ -67,19 +67,26 @@ class ModuleHandler
|
|||
{
|
||||
modulePriorityOrder = moduleCache.keys().array();
|
||||
|
||||
modulePriorityOrder.sort(function(a:String, b:String):Int {
|
||||
var aModule:Module = moduleCache.get(a);
|
||||
var bModule:Module = moduleCache.get(b);
|
||||
modulePriorityOrder.sort(sortByPriority);
|
||||
}
|
||||
|
||||
if (aModule.priority != bModule.priority)
|
||||
{
|
||||
return aModule.priority - bModule.priority;
|
||||
}
|
||||
else
|
||||
{
|
||||
return SortUtil.alphabetically(a, b);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Given two module IDs, sort them by priority.
|
||||
* @return 1 or -1 depending on which module has a higher priority.
|
||||
*/
|
||||
static function sortByPriority(a:String, b:String)
|
||||
{
|
||||
var aModule:Module = moduleCache.get(a);
|
||||
var bModule:Module = moduleCache.get(b);
|
||||
|
||||
if (aModule.priority != bModule.priority)
|
||||
{
|
||||
return aModule.priority - bModule.priority;
|
||||
}
|
||||
else
|
||||
{
|
||||
return SortUtil.alphabetically(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getModule(moduleId:String):Module
|
||||
|
|
|
@ -146,13 +146,13 @@ class HealthIcon extends FlxSprite
|
|||
*/
|
||||
public function toggleOldIcon():Void
|
||||
{
|
||||
if (characterId == 'beta')
|
||||
if (characterId == 'bf-old')
|
||||
{
|
||||
characterId = PlayState.instance.currentPlayerId;
|
||||
}
|
||||
else
|
||||
{
|
||||
characterId = 'beta';
|
||||
characterId = 'bf-old';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -666,14 +666,6 @@ class PlayState extends MusicBeatSubState
|
|||
|
||||
super.update(elapsed);
|
||||
|
||||
if (FlxG.keys.justPressed.U)
|
||||
{
|
||||
// hack for HaxeUI generation, doesn't work unless persistentUpdate is false at state creation!!
|
||||
disableKeys = true;
|
||||
persistentUpdate = false;
|
||||
openSubState(new StageOffsetSubState());
|
||||
}
|
||||
|
||||
updateHealthBar();
|
||||
updateScoreText();
|
||||
|
||||
|
|
|
@ -382,7 +382,7 @@ class CharacterDataParser
|
|||
input.version = CHARACTER_DATA_VERSION;
|
||||
}
|
||||
|
||||
if (!VersionUtil.validateVersion(input.version, CHARACTER_DATA_VERSION_RULE))
|
||||
if (!VersionUtil.validateVersionStr(input.version, CHARACTER_DATA_VERSION_RULE))
|
||||
{
|
||||
trace('ERROR: Could not load character data for "$id": bad version (got ${input.version}, expected ${CHARACTER_DATA_VERSION_RULE})');
|
||||
return null;
|
||||
|
|
|
@ -5,7 +5,7 @@ import flixel.group.FlxSpriteGroup;
|
|||
import flixel.util.FlxColor;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.system.FlxSound;
|
||||
import flixel.sound.FlxSound;
|
||||
import funkin.util.SortUtil;
|
||||
import flixel.util.FlxSort;
|
||||
import funkin.modding.events.ScriptEvent;
|
||||
|
@ -18,7 +18,7 @@ import flixel.addons.display.FlxPieDial;
|
|||
|
||||
/**
|
||||
* A high-level handler for dialogue.
|
||||
*
|
||||
*
|
||||
* This shit is great for modders but it's pretty elaborate for how much it'll actually be used, lolol. -Eric
|
||||
*/
|
||||
class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
||||
|
@ -299,7 +299,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
/**
|
||||
* Dispatch an event to attempt to advance the conversation.
|
||||
* This is done once at the start of the conversation, and once whenever the user presses CONFIRM to advance the conversation.
|
||||
*
|
||||
*
|
||||
* The broadcast event may be cancelled by modules or ScriptedConversations. This will prevent the conversation from actually advancing.
|
||||
* This is useful if you want to manually play an animation or something.
|
||||
*/
|
||||
|
@ -365,7 +365,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
|
||||
/**
|
||||
* Dispatch an event to attempt to immediately end the conversation.
|
||||
*
|
||||
*
|
||||
* The broadcast event may be cancelled by modules or ScriptedConversations. This will prevent the conversation from being cancelled.
|
||||
* This is useful if you want to prevent an animation from being skipped or something.
|
||||
*/
|
||||
|
@ -567,7 +567,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass
|
|||
|
||||
/**
|
||||
* As this event is dispatched to the Conversation, it is also dispatched to the active speaker.
|
||||
* @param event
|
||||
* @param event
|
||||
*/
|
||||
function propagateEvent(event:ScriptEvent):Void
|
||||
{
|
||||
|
|
|
@ -104,6 +104,10 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
|||
|
||||
noteFrames = Paths.getSparrowAtlas(getNoteAssetPath(), getNoteAssetLibrary());
|
||||
|
||||
if (noteFrames == null) {
|
||||
throw 'Could not load note frames for note style: $id';
|
||||
}
|
||||
|
||||
noteFrames.parent.persist = true;
|
||||
|
||||
return noteFrames;
|
||||
|
@ -161,7 +165,8 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
|||
{
|
||||
if (raw)
|
||||
{
|
||||
var rawPath:Null<String> = _data?.assets?.holdNote?.assetPath;
|
||||
// TODO: figure out why ?. didn't work here
|
||||
var rawPath:Null<String> = (_data?.assets?.holdNote == null) ? null : _data?.assets?.holdNote?.assetPath;
|
||||
return (rawPath == null) ? fallback.getHoldNoteAssetPath(true) : rawPath;
|
||||
}
|
||||
|
||||
|
@ -299,6 +304,6 @@ class NoteStyle implements IRegistryEntry<NoteStyleData>
|
|||
|
||||
public function _fetchData(id:String):Null<NoteStyleData>
|
||||
{
|
||||
return NoteStyleRegistry.instance.parseEntryData(id);
|
||||
return NoteStyleRegistry.instance.parseEntryDataWithMigration(id, NoteStyleRegistry.instance.fetchEntryVersion(id));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ class SongMigrator
|
|||
{
|
||||
if (jsonData.version != null)
|
||||
{
|
||||
if (VersionUtil.validateVersion(jsonData.version, CHART_VERSION_RULE))
|
||||
if (VersionUtil.validateVersionStr(jsonData.version, CHART_VERSION_RULE))
|
||||
{
|
||||
trace('Song (${songId}) metadata version (${jsonData.version}) is valid and up-to-date.');
|
||||
|
||||
|
@ -70,7 +70,7 @@ class SongMigrator
|
|||
{
|
||||
if (jsonData.version)
|
||||
{
|
||||
if (VersionUtil.validateVersion(jsonData.version, CHART_VERSION_RULE))
|
||||
if (VersionUtil.validateVersionStr(jsonData.version, CHART_VERSION_RULE))
|
||||
{
|
||||
trace('Song (${songId}) chart version (${jsonData.version}) is valid and up-to-date.');
|
||||
|
||||
|
|
|
@ -217,7 +217,7 @@ class StageDataParser
|
|||
return null;
|
||||
}
|
||||
|
||||
if (!VersionUtil.validateVersion(input.version, STAGE_DATA_VERSION_RULE))
|
||||
if (!VersionUtil.validateVersionStr(input.version, STAGE_DATA_VERSION_RULE))
|
||||
{
|
||||
trace('ERROR: Could not load stage data for "$id": bad version (got ${input.version}, expected ${STAGE_DATA_VERSION_RULE})');
|
||||
return null;
|
||||
|
|
|
@ -257,6 +257,7 @@ class ChartEditorDialogHandler
|
|||
* @param closable Whether the dialog can be closed by the user.
|
||||
* @return The dialog that was opened.
|
||||
*/
|
||||
@:haxe.warning("-WVarInit")
|
||||
public static function openUploadInstDialog(state:ChartEditorState, ?closable:Bool = true):Dialog
|
||||
{
|
||||
var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_UPLOAD_INST_LAYOUT, true, closable);
|
||||
|
@ -416,6 +417,7 @@ class ChartEditorDialogHandler
|
|||
* @param state The ChartEditorState instance.
|
||||
* @return The dialog to open.
|
||||
*/
|
||||
@:haxe.warning("-WVarInit")
|
||||
public static function openSongMetadataDialog(state:ChartEditorState):Dialog
|
||||
{
|
||||
var dialog:Dialog = openDialog(state, CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT, true, false);
|
||||
|
|
|
@ -95,7 +95,7 @@ class ChartEditorEventSprite extends FlxSprite
|
|||
public function correctAnimationName(name:String):String
|
||||
{
|
||||
if (this.animation.exists(name)) return name;
|
||||
trace('Warning: Invalid animation name "' + name + '" for song event. Using "${DEFAULT_EVENT}"');
|
||||
trace('Warning: Invalid animation name "${name}" for song event. Using "${DEFAULT_EVENT}"');
|
||||
return DEFAULT_EVENT;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import flixel.addons.transition.FlxTransitionableState;
|
|||
import flixel.input.keyboard.FlxKey;
|
||||
import flixel.math.FlxPoint;
|
||||
import flixel.math.FlxRect;
|
||||
import flixel.system.FlxSound;
|
||||
import flixel.sound.FlxSound;
|
||||
import flixel.tweens.FlxEase;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.tweens.misc.VarTween;
|
||||
|
|
|
@ -181,6 +181,6 @@ class Level implements IRegistryEntry<LevelData>
|
|||
|
||||
public function _fetchData(id:String):Null<LevelData>
|
||||
{
|
||||
return LevelRegistry.instance.parseEntryData(id);
|
||||
return LevelRegistry.instance.parseEntryDataWithMigration(id, LevelRegistry.instance.fetchEntryVersion(id));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ class BezierUtil
|
|||
|
||||
/**
|
||||
* Linearly interpolate between three values.
|
||||
* Depending on p, 0 = a, 0.5 = b, 1 = c, 0.25 = halfway between a and c, etc.
|
||||
* Depending on p, 0 = a, 0.5 = b, 1 = c, 0.25 = halfway between a and b, etc.
|
||||
*/
|
||||
static inline function mix3(p:Float, a:Float, b:Float, c:Float):Float
|
||||
{
|
||||
|
@ -32,11 +32,6 @@ class BezierUtil
|
|||
return mix2(p, mix4(p, a, b, c, d), mix4(p, b, c, d, e));
|
||||
}
|
||||
|
||||
static inline function mix6(p:Float, a:Float, b:Float, c:Float, d:Float, e:Float, f:Float):Float
|
||||
{
|
||||
return mix2(p, mix5(p, a, b, c, d, e), mix5(p, b, c, d, e, f));
|
||||
}
|
||||
|
||||
/**
|
||||
* A bezier curve with two points.
|
||||
* This is really just linear interpolation but whatever.
|
||||
|
|
|
@ -88,12 +88,17 @@ class Constants
|
|||
public static final COLOR_HEALTH_BAR_GREEN:FlxColor = 0xFF66FF33;
|
||||
|
||||
/**
|
||||
* The base colors of the notes.
|
||||
* The base colors used by notes.
|
||||
*/
|
||||
public static final COLOR_NOTES:Array<FlxColor> = [0xFFFF22AA, 0xFF00EEFF, 0xFF00CC00, 0xFFCC1111];
|
||||
public static var COLOR_NOTES:Array<FlxColor> = [
|
||||
0xFFFF22AA, // left (0)
|
||||
0xFF00EEFF, // down (1)
|
||||
0xFF00CC00, // up (2)
|
||||
0xFFCC1111 // right (3)
|
||||
];
|
||||
|
||||
/**
|
||||
* STAGE DEFAULTS
|
||||
* GAME DEFAULTS
|
||||
*/
|
||||
// ==============================
|
||||
|
||||
|
|
|
@ -2,9 +2,10 @@ package funkin.util;
|
|||
|
||||
class DateUtil
|
||||
{
|
||||
public static function generateTimestamp():String
|
||||
public static function generateTimestamp(?date:Date = null):String
|
||||
{
|
||||
var date = Date.now();
|
||||
if (date == null) date = Date.now();
|
||||
|
||||
return
|
||||
'${date.getFullYear()}-${Std.string(date.getMonth() + 1).lpad('0', 2)}-${Std.string(date.getDate()).lpad('0', 2)}-${Std.string(date.getHours()).lpad('0', 2)}-${Std.string(date.getMinutes()).lpad('0', 2)}-${Std.string(date.getSeconds()).lpad('0', 2)}';
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
package funkin.util;
|
|
@ -11,26 +11,21 @@ typedef ScoreInput =
|
|||
var t:Int; // Start timestamp
|
||||
}
|
||||
|
||||
/**
|
||||
* A class of functions dedicated to serializing and deserializing data.
|
||||
*/
|
||||
class SerializerUtil
|
||||
{
|
||||
static final INDENT_CHAR = "\t";
|
||||
|
||||
/**
|
||||
* Convert a Haxe object to a JSON string.
|
||||
**/
|
||||
*/
|
||||
public static function toJSON(input:Dynamic, ?pretty:Bool = true):String
|
||||
{
|
||||
return Json.stringify(input, replacer, pretty ? INDENT_CHAR : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a JSON string to a Haxe object of the chosen type.
|
||||
*/
|
||||
public static function fromJSONTyped<T>(input:String, type:Class<T>):T
|
||||
{
|
||||
return cast Json.parse(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a JSON string to a Haxe object.
|
||||
*/
|
||||
|
@ -76,14 +71,19 @@ class SerializerUtil
|
|||
if (Std.isOfType(value, String)) return value;
|
||||
|
||||
// Stringify Version objects.
|
||||
var valueVersion:thx.semver.Version = cast value;
|
||||
var result = '${valueVersion.major}.${valueVersion.minor}.${valueVersion.patch}';
|
||||
if (valueVersion.hasPre) result += '-${valueVersion.pre}';
|
||||
if (valueVersion.hasBuild) result += '+${valueVersion.build}';
|
||||
return result;
|
||||
return serializeVersion(cast value);
|
||||
}
|
||||
|
||||
// Else, return the value as-is.
|
||||
return value;
|
||||
}
|
||||
|
||||
static inline function serializeVersion(value:thx.semver.Version):String
|
||||
{
|
||||
var result = '${value.major}.${value.minor}.${value.patch}';
|
||||
if (value.hasPre) result += '-${value.pre}';
|
||||
// TODO: Merge fix for version.hasBuild
|
||||
if (value.build.length > 0) result += '+${value.build}';
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,13 +14,11 @@ class VersionUtil
|
|||
* Checks that a given verison number satisisfies a given version rule.
|
||||
* Version rule can be complex, e.g. "1.0.x" or ">=1.0.0,<1.1.0", or anything NPM supports.
|
||||
*/
|
||||
public static function validateVersion(version:String, versionRule:String):Bool
|
||||
public static function validateVersion(version:thx.semver.Version, versionRule:thx.semver.VersionRule):Bool
|
||||
{
|
||||
try
|
||||
{
|
||||
var v:Version = version; // Perform a cast.
|
||||
var vr:VersionRule = versionRule; // Perform a cast.
|
||||
return v.satisfies(vr);
|
||||
return version.satisfies(versionRule);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
|
@ -28,4 +26,38 @@ class VersionUtil
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a given verison number satisisfies a given version rule.
|
||||
* Version rule can be complex, e.g. "1.0.x" or ">=1.0.0,<1.1.0", or anything NPM supports.
|
||||
*/
|
||||
public static function validateVersionStr(version:String, versionRule:String):Bool
|
||||
{
|
||||
try
|
||||
{
|
||||
var version:thx.semver.Version = version;
|
||||
var versionRule:thx.semver.VersionRule = versionRule;
|
||||
return version.satisfies(versionRule);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
trace('[VERSIONUTIL] Invalid semantic version: ${version}');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and parse the semantic version from a JSON string.
|
||||
* @param input The JSON string to parse.
|
||||
* @return The semantic version, or null if it could not be parsed.
|
||||
*/
|
||||
public static function getVersionFromJSON(input:String):Null<thx.semver.Version>
|
||||
{
|
||||
var parsed = SerializerUtil.fromJSON(input);
|
||||
if (parsed == null) return null;
|
||||
if (parsed.version == null) return null;
|
||||
var versionStr:String = parsed.version; // Dynamic -> String cast
|
||||
var version:thx.semver.Version = versionStr; // Implicit, not explicit, cast.
|
||||
return version;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ class FlxAnimationUtil
|
|||
if (anim.frameIndices != null && anim.frameIndices.length > 0)
|
||||
{
|
||||
// trace('addByIndices(${anim.name}, ${anim.prefix}, ${anim.frameIndices}, ${frameRate}, ${looped}, ${flipX}, ${flipY})');
|
||||
target.animation.addByIndices(anim.name, anim.prefix, anim.frameIndices, "", frameRate, looped, flipX, flipY);
|
||||
target.animation.addByIndices(anim.name, anim.prefix, anim.frameIndices, '', frameRate, looped, flipX, flipY);
|
||||
// trace('RESULT:${target.animation.getAnimationList()}');
|
||||
}
|
||||
else
|
||||
|
|
|
@ -108,6 +108,9 @@ class ArraySortTools
|
|||
input[i] = input[j];
|
||||
input[j] = temp;
|
||||
}
|
||||
|
||||
// Don't expect to get here.
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -27,6 +27,7 @@ class MapTools
|
|||
|
||||
/**
|
||||
* Return a list of keys from the map (as an array, rather than an iterator).
|
||||
* TODO: Rename this?
|
||||
*/
|
||||
public static function keyValues<K, T>(map:Map<K, T>):Array<K>
|
||||
{
|
||||
|
|
|
@ -51,6 +51,7 @@ class StringTools
|
|||
|
||||
/**
|
||||
* Parses the string data as JSON and returns the resulting object.
|
||||
* This is here so you can use `string.parseJSON()` when `using StringTools`.
|
||||
*
|
||||
* @return The parsed object.
|
||||
*/
|
||||
|
|
7
tests/unit/.gitignore
vendored
Normal file
7
tests/unit/.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
|
||||
bin/
|
||||
# auto-generated
|
||||
source/TestSuite.hx
|
||||
.temp/
|
||||
report/
|
||||
build/
|
7
tests/unit/.munit
Normal file
7
tests/unit/.munit
Normal file
|
@ -0,0 +1,7 @@
|
|||
version=2.3.5
|
||||
src=source
|
||||
bin=bin
|
||||
report=report
|
||||
hxml=test.hxml
|
||||
classPaths=../../source
|
||||
coveragePackages=funkin
|
13
tests/unit/.vscode/launch.json
vendored
Normal file
13
tests/unit/.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Lime",
|
||||
"type": "lime",
|
||||
"request": "launch"
|
||||
}
|
||||
]
|
||||
}
|
50
tests/unit/README.md
Normal file
50
tests/unit/README.md
Normal file
|
@ -0,0 +1,50 @@
|
|||
Flixel Unit Tests
|
||||
-----------------
|
||||
|
||||
This is a unit test project using [munit](https://github.com/massiveinteractive/MassiveUnit). It's good practice to add tests for fixed bugs or new features.
|
||||
|
||||
TODO Make sure the unit tests are automatically run on GitHub Actions.
|
||||
|
||||
There's a 1:1 mapping between `.hx` files in `source/` and the unit test project - tests for `funkin.Conductor` go into `funkin.ConductorTest` etc.
|
||||
|
||||
### Building
|
||||
|
||||
Run one of the `test-*.hxml` files in this directory to run the tests on that specific target, e.g. `haxe test-cpp.hxml`. Currently supported are:
|
||||
|
||||
- `web` (HTML5)
|
||||
- `cpp` (Native)
|
||||
|
||||
Alternatively, this can be done from within Visual Studio Code - (`F1` -> `Tasks: Run Task` -> Choose the target to test).
|
||||
|
||||
#### Adding Tests
|
||||
|
||||
- Run `haxelib run munit create com.FooBarTest -for com.Foo`
|
||||
- Use `@:allow(full.package.name.ClassName)` to allow a test class to call private functions.
|
||||
- Use `mockatoo.Mockatoo.mock(ClassName)` to mock a class. See [Mockatoo docs](https://github.com/misprintt/mockatoo).
|
||||
|
||||
#### Functions
|
||||
|
||||
- `@Before` functions are named `before()`
|
||||
- Each `@Test` function starts with `test` and describes what exactly it tests. This can lead to long function names like `FlxEmitter#testStartShouldNotReviveMembers()` and serves as self-documentation.
|
||||
- Another thing that helps with self-documentation is adding a comment for tests that are related an issue on GitHub.
|
||||
|
||||
```haxe
|
||||
@Test // #1203
|
||||
function testColorWithAlphaComparison()
|
||||
```
|
||||
|
||||
### `FunkinTest` base class
|
||||
|
||||
Test classes extend `FunkinTest`, which is a base class with some utility functions for testing.
|
||||
|
||||
### `step()`
|
||||
|
||||
`step()` advances the `FlxGame` exactly one step. This is useful for tests that depend on game time advancing / `FlxGame#step()` being executed, such as physics of `add()`ed objects, state switches, or just time passing for tweens or timers.
|
||||
|
||||
There are two parameters:
|
||||
- `steps` - specifies the amount of steps to advance (defaults to 1)
|
||||
- `callback` - an optional callback function that is executed after each step
|
||||
|
||||
### `testDestroy()`
|
||||
|
||||
`testDestroy()` tests whether an `IFlxDestroyable` can safely be `destroy()`ed more than once (null reference errors are fairly common here). For this, `destroyable` has to be set during `before()` of the test class.
|
0
tests/unit/assets/preload/data/anotherFile.txt
Normal file
0
tests/unit/assets/preload/data/anotherFile.txt
Normal file
47
tests/unit/assets/preload/data/levels/test.json
Normal file
47
tests/unit/assets/preload/data/levels/test.json
Normal file
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"version": "1.0.0",
|
||||
"name": "TEACHING TIME",
|
||||
"titleAsset": "storymenu/titles/tutorial",
|
||||
"props": [
|
||||
{
|
||||
"assetPath": "storymenu/props/gf",
|
||||
"scale": 1.0,
|
||||
"danceEvery": 2,
|
||||
"offsets": [80, 80],
|
||||
"animations": [
|
||||
{
|
||||
"name": "danceLeft",
|
||||
"prefix": "idle0",
|
||||
"frameIndices": [30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
|
||||
},
|
||||
{
|
||||
"name": "danceRight",
|
||||
"prefix": "idle0",
|
||||
"frameIndices": [
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"assetPath": "storymenu/props/bf",
|
||||
"scale": 1.0,
|
||||
"danceEvery": 2,
|
||||
"offsets": [150, 80],
|
||||
"animations": [
|
||||
{
|
||||
"name": "idle",
|
||||
"prefix": "idle0",
|
||||
"frameRate": 24
|
||||
},
|
||||
{
|
||||
"name": "confirm",
|
||||
"prefix": "confirm0",
|
||||
"frameRate": 24
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"background": "#F9CF51",
|
||||
"songs": ["tutorial"]
|
||||
}
|
6
tests/unit/assets/preload/data/mytype/blablabla.json
Normal file
6
tests/unit/assets/preload/data/mytype/blablabla.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"version": "1.0.0",
|
||||
"id": "blablabla",
|
||||
"name": "blablabla API",
|
||||
"data": []
|
||||
}
|
15
tests/unit/assets/preload/data/mytype/fizzbuzz.json
Normal file
15
tests/unit/assets/preload/data/mytype/fizzbuzz.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"version": "1.0.0",
|
||||
"id": "fizzbuzz",
|
||||
"name": "nodejs-express-mongoose-demo",
|
||||
"data": [
|
||||
{
|
||||
"foo": "bar",
|
||||
"bar": "foo"
|
||||
},
|
||||
{
|
||||
"foo": "baz",
|
||||
"bar": "baz"
|
||||
}
|
||||
]
|
||||
}
|
5
tests/unit/assets/preload/data/mytype/foobar.json
Normal file
5
tests/unit/assets/preload/data/mytype/foobar.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"version": "0.1.0",
|
||||
"id": 1,
|
||||
"name": "My First App"
|
||||
}
|
4
tests/unit/assets/preload/data/mytype/junk.json
Normal file
4
tests/unit/assets/preload/data/mytype/junk.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"version": "3.0.0",
|
||||
"info": "stuff"
|
||||
}
|
59
tests/unit/assets/preload/data/notestyles/funkin.json
Normal file
59
tests/unit/assets/preload/data/notestyles/funkin.json
Normal file
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"version": "1.0.0",
|
||||
"name": "Funkin'",
|
||||
"author": "PhantomArcade",
|
||||
"fallback": null,
|
||||
"assets": {
|
||||
"note": {
|
||||
"assetPath": "shared:arrows",
|
||||
"scale": 1.0,
|
||||
"isPixel": true,
|
||||
"data": {
|
||||
"left": { "prefix": "noteLeft" },
|
||||
"down": { "prefix": "noteDown" },
|
||||
"up": { "prefix": "noteUp" },
|
||||
"right": { "prefix": "noteRight" }
|
||||
}
|
||||
},
|
||||
"noteStrumline": {
|
||||
"assetPath": "shared:arrows",
|
||||
"scale": 1.0,
|
||||
"offsets": [28, 32],
|
||||
"isPixel": true,
|
||||
"data": {
|
||||
"leftStatic": { "prefix": "staticLeft0" },
|
||||
"leftPress": { "prefix": "pressedLeft0" },
|
||||
"leftConfirm": { "prefix": "confirmLeft0" },
|
||||
"leftConfirmHold": { "prefix": "confirmLeft0" },
|
||||
"downStatic": { "prefix": "staticDown0" },
|
||||
"downPress": { "prefix": "pressedDown0" },
|
||||
"downConfirm": { "prefix": "confirmDown0" },
|
||||
"downConfirmHold": { "prefix": "confirmDown0" },
|
||||
"upStatic": { "prefix": "staticUp0" },
|
||||
"upPress": { "prefix": "pressedUp0" },
|
||||
"upConfirm": { "prefix": "confirmUp0" },
|
||||
"upConfirmHold": { "prefix": "confirmUp0" },
|
||||
"rightStatic": { "prefix": "staticRight0" },
|
||||
"rightPress": { "prefix": "pressedRight0" },
|
||||
"rightConfirm": { "prefix": "confirmRight0" },
|
||||
"rightConfirmHold": { "prefix": "confirmRight0" }
|
||||
}
|
||||
},
|
||||
"holdNote": {
|
||||
"assetPath": "NOTE_hold_assets",
|
||||
"data": {}
|
||||
},
|
||||
"noteSplash": {
|
||||
"assetPath": "",
|
||||
"data": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"holdNoteCover": {
|
||||
"assetPath": "",
|
||||
"data": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
tests/unit/assets/preload/data/notestyles/test2.json
Normal file
18
tests/unit/assets/preload/data/notestyles/test2.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"version": "1.0.0",
|
||||
"name": "Test2",
|
||||
"author": "Eric",
|
||||
"fallback": "funkin",
|
||||
"assets": {
|
||||
"note": {
|
||||
"assetPath": "shared:coolstuff",
|
||||
"scale": 1.8,
|
||||
"data": {
|
||||
"left": { "prefix": "noteLeft1" },
|
||||
"down": { "prefix": "noteDown3" },
|
||||
"up": { "prefix": "noteUp2" },
|
||||
"right": { "prefix": "noteRight4" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
0
tests/unit/assets/preload/data/test/blablabla.json
Normal file
0
tests/unit/assets/preload/data/test/blablabla.json
Normal file
0
tests/unit/assets/preload/data/test/test1.json
Normal file
0
tests/unit/assets/preload/data/test/test1.json
Normal file
0
tests/unit/assets/preload/data/test/test2.json
Normal file
0
tests/unit/assets/preload/data/test/test2.json
Normal file
BIN
tests/unit/assets/shared/images/arrows.png
Normal file
BIN
tests/unit/assets/shared/images/arrows.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.7 KiB |
32
tests/unit/assets/shared/images/arrows.xml
Normal file
32
tests/unit/assets/shared/images/arrows.xml
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextureAtlas imagePath="arrows.png">
|
||||
<SubTexture name="staticLeft0001" x="0" y="0" width="17" height="17"/>
|
||||
<SubTexture name="staticDown0001" x="17" y="0" width="17" height="17"/>
|
||||
<SubTexture name="staticUp0001" x="34" y="0" width="17" height="17"/>
|
||||
<SubTexture name="staticRight0001" x="51" y="0" width="17" height="17"/>
|
||||
|
||||
<SubTexture name="noteLeft0001" x="0" y="17" width="17" height="17"/>
|
||||
<SubTexture name="noteDown0001" x="17" y="17" width="17" height="17"/>
|
||||
<SubTexture name="noteUp0001" x="34" y="17" width="17" height="17"/>
|
||||
<SubTexture name="noteRight0001" x="51" y="17" width="17" height="17"/>
|
||||
|
||||
<SubTexture name="pressedLeft0001" x="0" y="17" width="17" height="17"/>
|
||||
<SubTexture name="pressedDown0001" x="17" y="17" width="17" height="17"/>
|
||||
<SubTexture name="pressedUp0001" x="34" y="17" width="17" height="17"/>
|
||||
<SubTexture name="pressedRight0001" x="51" y="17" width="17" height="17"/>
|
||||
|
||||
<SubTexture name="pressedLeft0002" x="0" y="34" width="17" height="17"/>
|
||||
<SubTexture name="pressedDown0002" x="17" y="34" width="17" height="17"/>
|
||||
<SubTexture name="pressedUp0002" x="34" y="34" width="17" height="17"/>
|
||||
<SubTexture name="pressedRight0002" x="51" y="34" width="17" height="17"/>
|
||||
|
||||
<SubTexture name="confirmLeft0001" x="0" y="51" width="17" height="17"/>
|
||||
<SubTexture name="confirmDown0001" x="17" y="51" width="17" height="17"/>
|
||||
<SubTexture name="confirmUp0001" x="34" y="51" width="17" height="17"/>
|
||||
<SubTexture name="confirmRight0001" x="51" y="51" width="17" height="17"/>
|
||||
|
||||
<SubTexture name="confirmLeft0002" x="0" y="68" width="17" height="17"/>
|
||||
<SubTexture name="confirmDown0002" x="17" y="68" width="17" height="17"/>
|
||||
<SubTexture name="confirmUp0002" x="34" y="68" width="17" height="17"/>
|
||||
<SubTexture name="confirmRight0002" x="51" y="68" width="17" height="17"/>
|
||||
</TextureAtlas>
|
91
tests/unit/project.xml
Normal file
91
tests/unit/project.xml
Normal file
|
@ -0,0 +1,91 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<project
|
||||
xmlns="http://lime.software/project/1.0.2"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://lime.software/project/1.0.2 http://lime.software/xsd/project-1.0.2.xsd">
|
||||
|
||||
<meta title="FunkinUnitTests" version="1.0.0" />
|
||||
<app file="TestMain" main="TestMain" />
|
||||
|
||||
<!-- Test sources -->
|
||||
<source path="source" />
|
||||
|
||||
<!-- Funkin' sources -->
|
||||
<source path="../../source" />
|
||||
|
||||
<!-- Funkin' dependencies -->
|
||||
<haxelib name="lime" /> <!-- Game engine -->
|
||||
<haxelib name="openfl" /> <!-- Game engine -->
|
||||
<haxelib name="flixel" /> <!-- Game engine -->
|
||||
<haxelib name="flixel-addons" /> <!-- Additional utilities for Flixel -->
|
||||
<haxelib name="hscript" /> <!-- Scripting -->
|
||||
<haxelib name="flixel-ui" /> <!-- UI framework (deprecate this? -->
|
||||
<haxelib name="haxeui-core" /> <!-- UI framework -->
|
||||
<haxelib name="haxeui-flixel" /> <!-- Integrate HaxeUI with Flixel -->
|
||||
<haxelib name="polymod" /> <!-- Modding framework -->
|
||||
<haxelib name="flxanimate" /> <!-- Texture atlas rendering -->
|
||||
<haxelib name="hxCodec" /> <!-- Video playback -->
|
||||
<haxelib name="thx.semver" /> <!-- Semantic version handling -->
|
||||
<haxelib name="json2object" /> <!-- JSON parsing -->
|
||||
<haxelib name="tink_json" /> <!-- JSON parsing -->
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<haxelib name="munit" /> <!-- Unit test execution -->
|
||||
<haxelib name="mcover" /> <!-- Code coverage -->
|
||||
<haxelib name="mockatoo" /> <!-- Mocking -->
|
||||
<haxelib name="hamcrest" /> <!-- Assertions/matching -->
|
||||
|
||||
<!-- This macro allows addition of new functionality to existing Flixel. -->
|
||||
<haxeflag name="--macro" value="addMetadata('@:build(funkin.util.macro.FlxMacro.buildFlxBasic())', 'flixel.FlxBasic')" />
|
||||
|
||||
<!-- Assets -->
|
||||
<assets path="assets/preload" rename="assets" exclude="*.ogg" if="web" />
|
||||
<assets path="assets/preload" rename="assets" exclude="*.mp3" unless="web" />
|
||||
<!--
|
||||
<assets path="assets/songs" library="songs" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/songs" library="songs" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/shared" library="shared" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/shared" library="shared" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/tutorial" library="tutorial" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/tutorial" library="tutorial" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/week1" library="week1" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/week1" library="week1" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/week2" library="week2" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/week2" library="week2" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/week3" library="week3" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/week3" library="week3" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/week4" library="week4" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/week4" library="week4" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/week5" library="week5" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/week5" library="week5" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/week6" library="week6" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/week6" library="week6" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/week7" library="week7" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/week7" library="week7" exclude="*.fla|*.mp3" unless="web" />
|
||||
<assets path="assets/weekend1" library="weekend1" exclude="*.fla|*.ogg" if="web" />
|
||||
<assets path="assets/weekend1" library="weekend1" exclude="*.fla|*.mp3" unless="web" />
|
||||
<library name="songs" preload="true" />
|
||||
<library name="shared" preload="true" />
|
||||
<library name="tutorial" preload="true" />
|
||||
<library name="week1" preload="true" />
|
||||
<library name="week2" preload="true" />
|
||||
<library name="week3" preload="true" />
|
||||
<library name="week4" preload="true" />
|
||||
<library name="week5" preload="true" />
|
||||
<library name="week6" preload="true" />
|
||||
<library name="week7" preload="true" />
|
||||
<library name="weekend1" preload="true" />
|
||||
-->
|
||||
|
||||
<!-- Test defines -->
|
||||
<set name="no-custom-backend" />
|
||||
<set name="unit-test" />
|
||||
<!--<haxedef name="no-inline" />-->
|
||||
<haxedef name="FLX_UNIT_TEST" />
|
||||
<haxedef name="FLX_RECORD" />
|
||||
|
||||
<!-- Manually set up code coverage -->
|
||||
<haxelib name="mcover" />
|
||||
<haxedef name="MCOVER" />
|
||||
<haxeflag name="--macro" value="mcover.MCover.coverage(['funkin'],['../../source', 'source/'],[''])" />
|
||||
</project>
|
77
tests/unit/source/FunkinAssert.hx
Normal file
77
tests/unit/source/FunkinAssert.hx
Normal file
|
@ -0,0 +1,77 @@
|
|||
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
|
||||
*/
|
||||
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 (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
|
||||
{
|
||||
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<T>(expected:Array<T>, actual:Array<T>, ?info:PosInfos):Void
|
||||
{
|
||||
if (expected.equals(actual)) Assert.assertionCount++;
|
||||
else
|
||||
Assert.fail('\nExpected\n ${expected}\nbut was\n ${actual}\n', info);
|
||||
}
|
||||
|
||||
public static function arraysNotEqual<T>(expected:Array<T>, actual:Array<T>, ?info:PosInfos):Void
|
||||
{
|
||||
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 (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 (!expected.equals(actual)) Assert.assertionCount++;
|
||||
else if (msg != null) Assert.fail(msg, info);
|
||||
else
|
||||
Assert.fail("Value [" + actual + "] was equal to value [" + expected + "]", info);
|
||||
}
|
||||
}
|
105
tests/unit/source/FunkinTest.hx
Normal file
105
tests/unit/source/FunkinTest.hx
Normal file
|
@ -0,0 +1,105 @@
|
|||
package;
|
||||
|
||||
import openfl.utils.Assets;
|
||||
import openfl.errors.Error;
|
||||
import flixel.FlxG;
|
||||
import flixel.FlxState;
|
||||
import flixel.tweens.FlxTween;
|
||||
import flixel.util.FlxDestroyUtil.IFlxDestroyable;
|
||||
import massive.munit.Assert;
|
||||
|
||||
/**
|
||||
* @see https://github.com/HaxeFlixel/flixel/tree/dev/tests/unit
|
||||
*/
|
||||
class FunkinTest
|
||||
{
|
||||
public static final MS_PER_STEP:Float = 1.0 / 60.0 * 1000;
|
||||
|
||||
// approx. amount of ticks at 60 fps
|
||||
static inline var TICKS_PER_FRAME:UInt = 25;
|
||||
static var totalSteps:UInt = 0;
|
||||
|
||||
var destroyable:IFlxDestroyable;
|
||||
|
||||
public function new() {}
|
||||
|
||||
@After
|
||||
@:access(flixel)
|
||||
function after()
|
||||
{
|
||||
// Redefine how the game gets the time during tests.
|
||||
FlxG.game.getTimer = function() {
|
||||
return totalSteps * TICKS_PER_FRAME;
|
||||
}
|
||||
|
||||
// make sure we have the same starting conditions for each test
|
||||
resetGame();
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance the game simulation.
|
||||
* @param steps The amount to advance the game by.
|
||||
* @param callback A function to call after each step.
|
||||
*/
|
||||
@:access(flixel)
|
||||
function step(steps:UInt = 1, ?callback:Void->Void)
|
||||
{
|
||||
for (i in 0...steps)
|
||||
{
|
||||
FlxG.game.step();
|
||||
if (callback != null) callback();
|
||||
totalSteps++;
|
||||
}
|
||||
}
|
||||
|
||||
function resetGame()
|
||||
{
|
||||
FlxG.resetGame();
|
||||
step();
|
||||
}
|
||||
|
||||
function switchState(nextState:FlxState)
|
||||
{
|
||||
FlxG.switchState(nextState);
|
||||
step();
|
||||
}
|
||||
|
||||
function resetState()
|
||||
{
|
||||
FlxG.resetState();
|
||||
step();
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testAssert()
|
||||
{
|
||||
Assert.areEqual(4, 2 + 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
function testDestroy()
|
||||
{
|
||||
if (destroyable == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
destroyable.destroy();
|
||||
destroyable.destroy();
|
||||
}
|
||||
catch (e:Error)
|
||||
{
|
||||
Assert.fail(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function finishTween(tween:FlxTween)
|
||||
{
|
||||
while (!tween.finished)
|
||||
{
|
||||
step();
|
||||
}
|
||||
}
|
||||
}
|
60
tests/unit/source/MockTest.hx
Normal file
60
tests/unit/source/MockTest.hx
Normal file
|
@ -0,0 +1,60 @@
|
|||
package;
|
||||
|
||||
import massive.munit.util.Timer;
|
||||
import massive.munit.Assert;
|
||||
import massive.munit.async.AsyncFactory;
|
||||
import funkin.util.DateUtil;
|
||||
|
||||
class MockTest extends FunkinTest
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public function beforeClass() {}
|
||||
|
||||
@AfterClass
|
||||
public function afterClass() {}
|
||||
|
||||
@Before
|
||||
public function setup() {}
|
||||
|
||||
@After
|
||||
public function tearDown() {}
|
||||
|
||||
@Test
|
||||
public function testMock()
|
||||
{
|
||||
// Test that mocking works.
|
||||
|
||||
var mockSprite = mockatoo.Mockatoo.mock(flixel.FlxSprite);
|
||||
var mockAnim = mockatoo.Mockatoo.mock(flixel.animation.FlxAnimationController);
|
||||
mockSprite.animation = mockAnim;
|
||||
|
||||
var animData:funkin.data.animation.AnimationData =
|
||||
{
|
||||
name: "testAnim",
|
||||
prefix: "blablabla"
|
||||
};
|
||||
|
||||
mockSprite.animation.addByPrefix("testAnim", "blablabla", 24, false, false, false);
|
||||
|
||||
// Verify that the method was called once.
|
||||
// If not, a VerificationException will be thrown and the test will fail.
|
||||
mockatoo.Mockatoo.verify(mockAnim.addByPrefix("testAnim", "blablabla", 24, false, false, false), times(1));
|
||||
|
||||
try
|
||||
{
|
||||
// Attempt to verify the method was called.
|
||||
// This should FAIL, since we didn't call the method.
|
||||
mockatoo.Mockatoo.verify(mockAnim.addByIndices("testAnim", "blablabla", [], "", 24, false, false, false), times(1));
|
||||
Assert.fail("Mocking function should have thrown but didn't.");
|
||||
}
|
||||
catch (_:mockatoo.exception.VerificationException)
|
||||
{
|
||||
// Expected.
|
||||
}
|
||||
}
|
||||
}
|
77
tests/unit/source/TestMain.hx
Normal file
77
tests/unit/source/TestMain.hx
Normal file
|
@ -0,0 +1,77 @@
|
|||
package;
|
||||
|
||||
import openfl.Lib;
|
||||
import flixel.FlxGame;
|
||||
import flixel.FlxState;
|
||||
import massive.munit.TestRunner;
|
||||
import massive.munit.client.HTTPClient;
|
||||
import massive.munit.client.SummaryReportClient;
|
||||
|
||||
/**
|
||||
* Auto generated Test Application.
|
||||
* Refer to munit command line tool for more information (haxelib run munit)
|
||||
*/
|
||||
class TestMain
|
||||
{
|
||||
/**
|
||||
* If true, include a report with each ignored test and their descriptions.
|
||||
*/
|
||||
static final INCLUDE_IGNORED_REPORT:Bool = false;
|
||||
|
||||
static function main()
|
||||
{
|
||||
new TestMain();
|
||||
}
|
||||
|
||||
public function new()
|
||||
{
|
||||
// Flixel was not designed for unit testing so we can only have one instance for now.
|
||||
Lib.current.stage.addChild(new FlxGame(640, 480, FlxState, 60, 60, true));
|
||||
|
||||
var suites = new Array<Class<massive.munit.TestSuite>>();
|
||||
suites.push(TestSuite);
|
||||
|
||||
#if MCOVER
|
||||
// Print individual test results alongside coverage results for each test class,
|
||||
// as well as a final coverage report for the entire test suite.
|
||||
var innerClient = new massive.munit.client.RichPrintClient(INCLUDE_IGNORED_REPORT);
|
||||
var client = new mcover.coverage.munit.client.MCoverPrintClient(innerClient);
|
||||
// Print final test results alongside detailed coverage results for the test suite.
|
||||
var httpClient = new HTTPClient(new mcover.coverage.munit.client.MCoverSummaryReportClient());
|
||||
// NOTE: You can also create a custom ICoverageTestResultClient implementation
|
||||
|
||||
mcover.coverage.MCoverage.getLogger().addClient(new mcover.coverage.client.CodecovJsonPrintClient());
|
||||
#else
|
||||
// Print individual test results.
|
||||
var client = new massive.munit.client.RichPrintClient(INCLUDE_IGNORED_REPORT);
|
||||
// Print final test suite results.
|
||||
var httpClient = new HTTPClient(new SummaryReportClient());
|
||||
#end
|
||||
|
||||
var runner = new TestRunner(client);
|
||||
runner.addResultClient(httpClient);
|
||||
|
||||
runner.completionHandler = completionHandler;
|
||||
runner.run(suites);
|
||||
}
|
||||
|
||||
/**
|
||||
* updates the background color and closes the current browser
|
||||
* for flash and html targets (useful for continuos integration servers)
|
||||
*/
|
||||
function completionHandler(successful:Bool):Void
|
||||
{
|
||||
try
|
||||
{
|
||||
#if flash
|
||||
openfl.external.ExternalInterface.call("testResult", successful);
|
||||
#elseif js
|
||||
js.Lib.eval("testResult(" + successful + ");");
|
||||
#elseif sys
|
||||
Sys.exit(successful ? 0 : 1);
|
||||
#end
|
||||
}
|
||||
// if run from outside browser can get error which we can ignore
|
||||
catch (e:Dynamic) {}
|
||||
}
|
||||
}
|
30
tests/unit/source/_template.hxtemplate
Normal file
30
tests/unit/source/_template.hxtemplate
Normal file
|
@ -0,0 +1,30 @@
|
|||
package funkin.play;
|
||||
|
||||
import massive.munit.util.Timer;
|
||||
import massive.munit.Assert;
|
||||
import massive.munit.async.AsyncFactory;
|
||||
import funkin.play.PlayState;
|
||||
|
||||
@:access(funkin.play.PlayState)
|
||||
class Template extends FunkinTest
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public function beforeClass() {}
|
||||
|
||||
@AfterClass
|
||||
public function afterClass() {}
|
||||
|
||||
@Before
|
||||
public function setup() {}
|
||||
|
||||
@After
|
||||
public function tearDown() {}
|
||||
|
||||
@Test
|
||||
public function test() {}
|
||||
}
|
107
tests/unit/source/flixel/FlxGTest.hx
Normal file
107
tests/unit/source/flixel/FlxGTest.hx
Normal file
|
@ -0,0 +1,107 @@
|
|||
package flixel;
|
||||
|
||||
import massive.munit.Assert;
|
||||
|
||||
@:access(flixel.FlxG)
|
||||
class FlxGTest extends FunkinTest
|
||||
{
|
||||
@Test function testVERSIONNull():Void
|
||||
Assert.isNotNull(FlxG.VERSION);
|
||||
|
||||
@Test function testGameNull():Void
|
||||
Assert.isNotNull(FlxG.game);
|
||||
|
||||
@Test function testStageNull():Void
|
||||
Assert.isNotNull(FlxG.stage);
|
||||
|
||||
@Test function testStateNull():Void
|
||||
Assert.isNotNull(FlxG.state);
|
||||
|
||||
@Test function testWorldBoundsNull():Void
|
||||
Assert.isNotNull(FlxG.worldBounds);
|
||||
|
||||
@Test function testSaveNull():Void
|
||||
Assert.isNotNull(FlxG.save);
|
||||
|
||||
#if FLX_MOUSE
|
||||
@Test function testMouseNull():Void
|
||||
Assert.isNotNull(FlxG.mouse);
|
||||
#end
|
||||
|
||||
#if FLX_TOUCH
|
||||
@Test function testTouchNull():Void
|
||||
Assert.isNotNull(FlxG.touches);
|
||||
#end
|
||||
|
||||
#if FLX_POINTER_INPUT
|
||||
@Test function testSwipesNull():Void
|
||||
Assert.isNotNull(FlxG.swipes);
|
||||
#end
|
||||
|
||||
#if FLX_KEYBOARD
|
||||
@Test function testKeysNull():Void
|
||||
Assert.isNotNull(FlxG.keys);
|
||||
#end
|
||||
|
||||
#if FLX_GAMEPAD
|
||||
@Test function testGamepadsNull():Void
|
||||
Assert.isNotNull(FlxG.gamepads);
|
||||
#end
|
||||
|
||||
#if android
|
||||
@Test function testAndroidNull():Void
|
||||
Assert.isNotNull(FlxG.android);
|
||||
#end
|
||||
|
||||
#if js
|
||||
@Test function testHtml5Null():Void
|
||||
Assert.isNotNull(FlxG.html5);
|
||||
#end
|
||||
|
||||
@Test function testInputsNull():Void
|
||||
Assert.isNotNull(FlxG.inputs);
|
||||
|
||||
@Test function testConsoleNull():Void
|
||||
Assert.isNotNull(FlxG.console);
|
||||
|
||||
@Test function testLogNull():Void
|
||||
Assert.isNotNull(FlxG.log);
|
||||
|
||||
@Test function testWatchNull():Void
|
||||
Assert.isNotNull(FlxG.watch);
|
||||
|
||||
@Test function testDebuggerNull():Void
|
||||
Assert.isNotNull(FlxG.debugger);
|
||||
|
||||
@Test function testVcrNull():Void
|
||||
Assert.isNotNull(FlxG.vcr);
|
||||
|
||||
@Test function testBitmapNull():Void
|
||||
Assert.isNotNull(FlxG.bitmap);
|
||||
|
||||
@Test function testCamerasNull():Void
|
||||
Assert.isNotNull(FlxG.cameras);
|
||||
|
||||
@Test function testPluginsNull():Void
|
||||
Assert.isNotNull(FlxG.plugins);
|
||||
|
||||
#if FLX_SOUND_SYSTEM
|
||||
@Test function testSoundNull():Void
|
||||
Assert.isNotNull(FlxG.sound);
|
||||
#end
|
||||
|
||||
@Test function testScaleModeNull():Void
|
||||
Assert.isNotNull(FlxG.scaleMode);
|
||||
|
||||
@Test
|
||||
function testDefaultWidth():Void
|
||||
{
|
||||
Assert.areEqual(640, FlxG.width);
|
||||
}
|
||||
|
||||
@Test
|
||||
function testDefaultHeight():Void
|
||||
{
|
||||
Assert.areEqual(480, FlxG.height);
|
||||
}
|
||||
}
|
550
tests/unit/source/funkin/ConductorTest.hx
Normal file
550
tests/unit/source/funkin/ConductorTest.hx
Normal file
|
@ -0,0 +1,550 @@
|
|||
package funkin;
|
||||
|
||||
import flixel.FlxG;
|
||||
import flixel.FlxState;
|
||||
import funkin.Conductor;
|
||||
import funkin.play.song.SongData.SongTimeChange;
|
||||
import funkin.util.Constants;
|
||||
import massive.munit.Assert;
|
||||
|
||||
@:access(funkin.Conductor)
|
||||
class ConductorTest extends FunkinTest
|
||||
{
|
||||
var conductorState:ConductorState;
|
||||
|
||||
@Before
|
||||
function before()
|
||||
{
|
||||
resetGame();
|
||||
|
||||
// The ConductorState will advance the conductor when step() is called.
|
||||
FlxG.switchState(conductorState = new ConductorState());
|
||||
|
||||
Conductor.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
function testDefaultValues():Void
|
||||
{
|
||||
// NOTE: Expected value comes first.
|
||||
|
||||
Assert.areEqual([], Conductor.timeChanges);
|
||||
Assert.areEqual(null, Conductor.currentTimeChange);
|
||||
|
||||
Assert.areEqual(0, Conductor.songPosition);
|
||||
Assert.areEqual(Constants.DEFAULT_BPM, Conductor.bpm);
|
||||
Assert.areEqual(null, Conductor.bpmOverride);
|
||||
|
||||
Assert.areEqual(600, Conductor.beatLengthMs);
|
||||
|
||||
Assert.areEqual(4, Conductor.timeSignatureNumerator);
|
||||
Assert.areEqual(4, Conductor.timeSignatureDenominator);
|
||||
|
||||
Assert.areEqual(0, Conductor.currentBeat);
|
||||
Assert.areEqual(0, Conductor.currentStep);
|
||||
Assert.areEqual(0.0, Conductor.currentStepTime);
|
||||
|
||||
Assert.areEqual(150, Conductor.stepLengthMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests implementation of `update()`, and how it affects
|
||||
* `currentBeat`, `currentStep`, `currentStepTime`, and the `beatHit` and `stepHit` signals.
|
||||
*/
|
||||
@Test
|
||||
function testUpdate():Void
|
||||
{
|
||||
Assert.areEqual(0, Conductor.songPosition);
|
||||
|
||||
step(); // 1
|
||||
|
||||
var BPM_100_STEP_TIME = 1 / 9;
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.songPosition);
|
||||
Assert.areEqual(0, Conductor.currentBeat);
|
||||
Assert.areEqual(0, Conductor.currentStep);
|
||||
FunkinAssert.areNear(1 / 9, Conductor.currentStepTime);
|
||||
|
||||
step(7); // 8
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 8, Conductor.songPosition);
|
||||
Assert.areEqual(0, Conductor.currentBeat);
|
||||
Assert.areEqual(0, Conductor.currentStep);
|
||||
FunkinAssert.areNear(8 / 9, Conductor.currentStepTime);
|
||||
|
||||
Assert.areEqual(0, conductorState.beatsHit);
|
||||
Assert.areEqual(0, conductorState.stepsHit);
|
||||
|
||||
step(); // 9
|
||||
|
||||
Assert.areEqual(0, conductorState.beatsHit);
|
||||
Assert.areEqual(1, conductorState.stepsHit);
|
||||
conductorState.beatsHit = 0;
|
||||
conductorState.stepsHit = 0;
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 9, Conductor.songPosition);
|
||||
Assert.areEqual(0, Conductor.currentBeat);
|
||||
Assert.areEqual(1, Conductor.currentStep);
|
||||
FunkinAssert.areNear(1.0, Conductor.currentStepTime);
|
||||
|
||||
step(35 - 9); // 35
|
||||
|
||||
Assert.areEqual(0, conductorState.beatsHit);
|
||||
Assert.areEqual(2, conductorState.stepsHit);
|
||||
conductorState.beatsHit = 0;
|
||||
conductorState.stepsHit = 0;
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 35, Conductor.songPosition);
|
||||
Assert.areEqual(0, Conductor.currentBeat);
|
||||
Assert.areEqual(3, Conductor.currentStep);
|
||||
FunkinAssert.areNear(3.0 + 8 / 9, Conductor.currentStepTime);
|
||||
|
||||
step(); // 36
|
||||
|
||||
Assert.areEqual(1, conductorState.beatsHit);
|
||||
Assert.areEqual(1, conductorState.stepsHit);
|
||||
conductorState.beatsHit = 0;
|
||||
conductorState.stepsHit = 0;
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 36, Conductor.songPosition);
|
||||
Assert.areEqual(1, Conductor.currentBeat);
|
||||
Assert.areEqual(4, Conductor.currentStep);
|
||||
FunkinAssert.areNear(4.0, Conductor.currentStepTime);
|
||||
|
||||
step(50 - 36); // 50
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 50, Conductor.songPosition);
|
||||
Assert.areEqual(1, Conductor.currentBeat);
|
||||
Assert.areEqual(5, Conductor.currentStep);
|
||||
FunkinAssert.areNear(5.555555, Conductor.currentStepTime);
|
||||
|
||||
step(49); // 99
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 99, Conductor.songPosition);
|
||||
Assert.areEqual(2, Conductor.currentBeat);
|
||||
Assert.areEqual(11, Conductor.currentStep);
|
||||
FunkinAssert.areNear(11.0, Conductor.currentStepTime);
|
||||
|
||||
step(1); // 100
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 100, Conductor.songPosition);
|
||||
Assert.areEqual(2, Conductor.currentBeat);
|
||||
Assert.areEqual(11, Conductor.currentStep);
|
||||
FunkinAssert.areNear(11.111111, Conductor.currentStepTime);
|
||||
}
|
||||
|
||||
@Test
|
||||
function testUpdateForcedBPM():Void
|
||||
{
|
||||
Conductor.forceBPM(60);
|
||||
|
||||
Assert.areEqual(0, Conductor.songPosition);
|
||||
|
||||
// 60 beats per minute = 1 beat per second
|
||||
// 1 beat per second = 1/60 beats per frame = 4/60 steps per frame
|
||||
step(); // Advances time 1/60 of 1 second.
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.songPosition);
|
||||
Assert.areEqual(0, Conductor.currentBeat);
|
||||
Assert.areEqual(0, Conductor.currentStep);
|
||||
FunkinAssert.areNear(4 / 60, Conductor.currentStepTime); // 1/60 of 1 beat = 4/60 of 1 step
|
||||
|
||||
step(14 - 1); // 14
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 14, Conductor.songPosition);
|
||||
Assert.areEqual(0, Conductor.currentBeat);
|
||||
Assert.areEqual(0, Conductor.currentStep);
|
||||
FunkinAssert.areNear(1.0 - 4 / 60, Conductor.currentStepTime); // 1/60 of 1 beat = 4/60 of 1 step
|
||||
|
||||
step(); // 15
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 15, Conductor.songPosition);
|
||||
Assert.areEqual(0, Conductor.currentBeat);
|
||||
Assert.areEqual(1, Conductor.currentStep);
|
||||
FunkinAssert.areNear(1.0, Conductor.currentStepTime); // 1/60 of 1 beat = 4/60 of 1 step
|
||||
|
||||
step(45 - 1); // 59
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 59, Conductor.songPosition);
|
||||
Assert.areEqual(0, Conductor.currentBeat);
|
||||
Assert.areEqual(3, Conductor.currentStep);
|
||||
FunkinAssert.areNear(4.0 - 4 / 60, Conductor.currentStepTime);
|
||||
|
||||
step(); // 60
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 60, Conductor.songPosition);
|
||||
Assert.areEqual(1, Conductor.currentBeat);
|
||||
Assert.areEqual(4, Conductor.currentStep);
|
||||
FunkinAssert.areNear(4.0, Conductor.currentStepTime);
|
||||
|
||||
step(); // 61
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 61, Conductor.songPosition);
|
||||
Assert.areEqual(1, Conductor.currentBeat);
|
||||
Assert.areEqual(4, Conductor.currentStep);
|
||||
FunkinAssert.areNear(4.0 + 4 / 60, Conductor.currentStepTime);
|
||||
}
|
||||
|
||||
@Test
|
||||
function testSingleTimeChange():Void
|
||||
{
|
||||
// Start the song with a BPM of 120.
|
||||
var songTimeChanges:Array<SongTimeChange> = [
|
||||
{
|
||||
t: 0,
|
||||
b: 0,
|
||||
bpm: 120,
|
||||
n: 4,
|
||||
d: 4,
|
||||
bt: [4, 4, 4, 4]
|
||||
}, // 120 bpm starting 0 sec/0 beats
|
||||
];
|
||||
Conductor.mapTimeChanges(songTimeChanges);
|
||||
|
||||
// All should be at 0.
|
||||
FunkinAssert.areNear(0, Conductor.songPosition);
|
||||
Assert.areEqual(0, Conductor.currentBeat);
|
||||
Assert.areEqual(0, Conductor.currentStep);
|
||||
FunkinAssert.areNear(0.0, Conductor.currentStepTime); // 2/120 of 1 beat = 8/120 of 1 step
|
||||
|
||||
// 120 beats per minute = 2 beat per second
|
||||
// 2 beat per second = 2/60 beats per frame = 16/120 steps per frame
|
||||
step(); // Advances time 1/60 of 1 second.
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.songPosition);
|
||||
Assert.areEqual(0, Conductor.currentBeat);
|
||||
Assert.areEqual(0, Conductor.currentStep);
|
||||
FunkinAssert.areNear(16 / 120, Conductor.currentStepTime); // 2/120 of 1 beat = 8/120 of 1 step
|
||||
|
||||
step(15 - 1); // 15
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 15, Conductor.songPosition);
|
||||
Assert.areEqual(0, Conductor.currentBeat);
|
||||
Assert.areEqual(2, Conductor.currentStep);
|
||||
FunkinAssert.areNear(2.0, Conductor.currentStepTime); // 2/60 of 1 beat = 8/60 of 1 step
|
||||
|
||||
step(45 - 1); // 59
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 59, Conductor.songPosition);
|
||||
Assert.areEqual(1, Conductor.currentBeat);
|
||||
Assert.areEqual(7, Conductor.currentStep);
|
||||
FunkinAssert.areNear(7.0 + 104 / 120, Conductor.currentStepTime);
|
||||
|
||||
step(); // 60
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 60, Conductor.songPosition);
|
||||
Assert.areEqual(2, Conductor.currentBeat);
|
||||
Assert.areEqual(8, Conductor.currentStep);
|
||||
FunkinAssert.areNear(8.0, Conductor.currentStepTime);
|
||||
|
||||
step(); // 61
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 61, Conductor.songPosition);
|
||||
Assert.areEqual(2, Conductor.currentBeat);
|
||||
Assert.areEqual(8, Conductor.currentStep);
|
||||
FunkinAssert.areNear(8.0 + 8 / 60, Conductor.currentStepTime);
|
||||
}
|
||||
|
||||
@Test
|
||||
function testDoubleTimeChange():Void
|
||||
{
|
||||
// Start the song with a BPM of 120.
|
||||
var songTimeChanges:Array<SongTimeChange> = [
|
||||
{
|
||||
t: 0,
|
||||
b: 0,
|
||||
bpm: 120,
|
||||
n: 4,
|
||||
d: 4,
|
||||
bt: [4, 4, 4, 4]
|
||||
}, // 120 bpm starting 0 sec/0 beats
|
||||
{
|
||||
t: 3000,
|
||||
b: 6,
|
||||
bpm: 90,
|
||||
n: 4,
|
||||
d: 4,
|
||||
bt: [4, 4, 4, 4]
|
||||
} // 90 bpm starting 3 sec/6 beats
|
||||
];
|
||||
Conductor.mapTimeChanges(songTimeChanges);
|
||||
|
||||
// All should be at 0.
|
||||
FunkinAssert.areNear(0, Conductor.songPosition);
|
||||
Assert.areEqual(0, Conductor.currentBeat);
|
||||
Assert.areEqual(0, Conductor.currentStep);
|
||||
FunkinAssert.areNear(0.0, Conductor.currentStepTime); // 2/120 of 1 beat = 8/120 of 1 step
|
||||
|
||||
// 120 beats per minute = 2 beat per second
|
||||
// 2 beat per second = 2/60 beats per frame = 16/120 steps per frame
|
||||
step(); // Advances time 1/60 of 1 second.
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.songPosition);
|
||||
Assert.areEqual(0, Conductor.currentBeat);
|
||||
Assert.areEqual(0, Conductor.currentStep);
|
||||
FunkinAssert.areNear(16 / 120, Conductor.currentStepTime); // 4/120 of 1 beat = 16/120 of 1 step
|
||||
|
||||
step(60 - 1 - 1); // 59
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 59, Conductor.songPosition);
|
||||
Assert.areEqual(1, Conductor.currentBeat);
|
||||
Assert.areEqual(7, Conductor.currentStep);
|
||||
FunkinAssert.areNear(7.0 + 104 / 120, Conductor.currentStepTime);
|
||||
|
||||
step(); // 60
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 60, Conductor.songPosition);
|
||||
Assert.areEqual(2, Conductor.currentBeat);
|
||||
Assert.areEqual(8, Conductor.currentStep);
|
||||
FunkinAssert.areNear(8.0, Conductor.currentStepTime);
|
||||
|
||||
step(); // 61
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 61, Conductor.songPosition);
|
||||
Assert.areEqual(2, Conductor.currentBeat);
|
||||
Assert.areEqual(8, Conductor.currentStep);
|
||||
FunkinAssert.areNear(8.0 + 8 / 60, Conductor.currentStepTime);
|
||||
|
||||
step(179 - 61); // 179
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 179, Conductor.songPosition);
|
||||
Assert.areEqual(5, Conductor.currentBeat);
|
||||
Assert.areEqual(23, Conductor.currentStep);
|
||||
FunkinAssert.areNear(23.0 + 52 / 60, Conductor.currentStepTime);
|
||||
|
||||
step(); // 180 (3 seconds)
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 180, Conductor.songPosition);
|
||||
Assert.areEqual(6, Conductor.currentBeat);
|
||||
Assert.areEqual(24, Conductor.currentStep);
|
||||
FunkinAssert.areNear(24.0, Conductor.currentStepTime);
|
||||
|
||||
step(); // 181 (3 + 1/60 seconds)
|
||||
// BPM has switched to 90!
|
||||
// 90 beats per minute = 1.5 beat per second
|
||||
// 1.5 beat per second = 1.5/60 beats per frame = 3/120 beats per frame
|
||||
// = 12/120 steps per frame
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 181, Conductor.songPosition);
|
||||
Assert.areEqual(6, Conductor.currentBeat);
|
||||
Assert.areEqual(24, Conductor.currentStep);
|
||||
FunkinAssert.areNear(24.0 + 12 / 120, Conductor.currentStepTime);
|
||||
|
||||
step(59); // 240 (4 seconds)
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 240, Conductor.songPosition);
|
||||
Assert.areEqual(7, Conductor.currentBeat);
|
||||
Assert.areEqual(30, Conductor.currentStep);
|
||||
FunkinAssert.areNear(30.0, Conductor.currentStepTime);
|
||||
|
||||
step(); // 241 (4 + 1/60 seconds)
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 241, Conductor.songPosition);
|
||||
Assert.areEqual(7, Conductor.currentBeat);
|
||||
Assert.areEqual(30, Conductor.currentStep);
|
||||
FunkinAssert.areNear(30.0 + 12 / 120, Conductor.currentStepTime);
|
||||
}
|
||||
|
||||
@Test
|
||||
function testTripleTimeChange():Void
|
||||
{
|
||||
// Start the song with a BPM of 120, then move to 90, then move to 180.
|
||||
var songTimeChanges:Array<SongTimeChange> = [
|
||||
{
|
||||
t: 0,
|
||||
b: null,
|
||||
bpm: 120,
|
||||
n: 4,
|
||||
d: 4,
|
||||
bt: [4, 4, 4, 4]
|
||||
}, // 120 bpm starting 0 sec/0 beats
|
||||
{
|
||||
t: 3000,
|
||||
b: null,
|
||||
bpm: 90,
|
||||
n: 4,
|
||||
d: 4,
|
||||
bt: [4, 4, 4, 4]
|
||||
}, // 90 bpm starting 3 sec/6 beats
|
||||
{
|
||||
t: 6000,
|
||||
b: null,
|
||||
bpm: 180,
|
||||
n: 4,
|
||||
d: 4,
|
||||
bt: [4, 4, 4, 4]
|
||||
} // 90 bpm starting 3 sec/6 beats
|
||||
];
|
||||
Conductor.mapTimeChanges(songTimeChanges);
|
||||
|
||||
// Verify time changes.
|
||||
Assert.areEqual(3, Conductor.timeChanges.length);
|
||||
FunkinAssert.areNear(0, Conductor.timeChanges[0].beatTime);
|
||||
FunkinAssert.areNear(6, Conductor.timeChanges[1].beatTime);
|
||||
FunkinAssert.areNear(10.5, Conductor.timeChanges[2].beatTime);
|
||||
|
||||
// All should be at 0.
|
||||
FunkinAssert.areNear(0, Conductor.songPosition);
|
||||
Assert.areEqual(0, Conductor.currentBeat);
|
||||
Assert.areEqual(0, Conductor.currentStep);
|
||||
FunkinAssert.areNear(0.0, Conductor.currentStepTime); // 2/120 of 1 beat = 8/120 of 1 step
|
||||
|
||||
// 120 beats per minute = 2 beat per second
|
||||
// 2 beat per second = 2/60 beats per frame = 16/120 steps per frame
|
||||
step(); // Advances time 1/60 of 1 second.
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 1, Conductor.songPosition);
|
||||
Assert.areEqual(0, Conductor.currentBeat);
|
||||
Assert.areEqual(0, Conductor.currentStep);
|
||||
FunkinAssert.areNear(16 / 120, Conductor.currentStepTime); // 4/120 of 1 beat = 16/120 of 1 step
|
||||
|
||||
step(60 - 1 - 1); // 59
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 59, Conductor.songPosition);
|
||||
Assert.areEqual(1, Conductor.currentBeat);
|
||||
Assert.areEqual(7, Conductor.currentStep);
|
||||
FunkinAssert.areNear(7 + 104 / 120, Conductor.currentStepTime);
|
||||
|
||||
step(); // 60
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 60, Conductor.songPosition);
|
||||
Assert.areEqual(2, Conductor.currentBeat);
|
||||
Assert.areEqual(8, Conductor.currentStep);
|
||||
FunkinAssert.areNear(8.0, Conductor.currentStepTime);
|
||||
|
||||
step(); // 61
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 61, Conductor.songPosition);
|
||||
Assert.areEqual(2, Conductor.currentBeat);
|
||||
Assert.areEqual(8, Conductor.currentStep);
|
||||
FunkinAssert.areNear(8.0 + 8 / 60, Conductor.currentStepTime);
|
||||
|
||||
step(179 - 61); // 179
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 179, Conductor.songPosition);
|
||||
Assert.areEqual(5, Conductor.currentBeat);
|
||||
Assert.areEqual(23, Conductor.currentStep);
|
||||
FunkinAssert.areNear(23.0 + 52 / 60, Conductor.currentStepTime);
|
||||
|
||||
step(); // 180 (3 seconds)
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 180, Conductor.songPosition);
|
||||
Assert.areEqual(6, Conductor.currentBeat);
|
||||
Assert.areEqual(24, Conductor.currentStep); // 23.999 => 24
|
||||
FunkinAssert.areNear(24.0, Conductor.currentStepTime);
|
||||
|
||||
step(); // 181 (3 + 1/60 seconds)
|
||||
// BPM has switched to 90!
|
||||
// 90 beats per minute = 1.5 beat per second
|
||||
// 1.5 beat per second = 1.5/60 beats per frame = 3/120 beats per frame
|
||||
// = 12/120 steps per frame
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 181, Conductor.songPosition);
|
||||
Assert.areEqual(6, Conductor.currentBeat);
|
||||
Assert.areEqual(24, Conductor.currentStep);
|
||||
FunkinAssert.areNear(24.0 + 12 / 120, Conductor.currentStepTime);
|
||||
|
||||
step(60 - 1 - 1); // 240 (4 seconds)
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 239, Conductor.songPosition);
|
||||
Assert.areEqual(7, Conductor.currentBeat);
|
||||
Assert.areEqual(29, Conductor.currentStep);
|
||||
FunkinAssert.areNear(29.0 + 108 / 120, Conductor.currentStepTime);
|
||||
|
||||
step(); // 240 (4 seconds)
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 240, Conductor.songPosition);
|
||||
Assert.areEqual(7, Conductor.currentBeat);
|
||||
Assert.areEqual(30, Conductor.currentStep);
|
||||
FunkinAssert.areNear(30.0, Conductor.currentStepTime);
|
||||
|
||||
step(); // 241 (4 + 1/60 seconds)
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 241, Conductor.songPosition);
|
||||
Assert.areEqual(7, Conductor.currentBeat);
|
||||
Assert.areEqual(30, Conductor.currentStep);
|
||||
FunkinAssert.areNear(30.0 + 12 / 120, Conductor.currentStepTime);
|
||||
|
||||
step(359 - 241); // 359 (5 + 59/60 seconds)
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 359, Conductor.songPosition);
|
||||
Assert.areEqual(10, Conductor.currentBeat);
|
||||
Assert.areEqual(41, Conductor.currentStep);
|
||||
FunkinAssert.areNear(41 + 108 / 120, Conductor.currentStepTime);
|
||||
|
||||
step(); // 360
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 360, Conductor.songPosition);
|
||||
Assert.areEqual(10, Conductor.currentBeat);
|
||||
Assert.areEqual(42, Conductor.currentStep); // 41.999
|
||||
FunkinAssert.areNear(42.0, Conductor.currentStepTime);
|
||||
|
||||
step(); // 361
|
||||
// BPM has switched to 180!
|
||||
// 180 beats per minute = 3 beat per second
|
||||
// 3 beat per second = 3/60 beats per frame
|
||||
// = 12/60 steps per frame
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 361, Conductor.songPosition);
|
||||
Assert.areEqual(10, Conductor.currentBeat);
|
||||
Assert.areEqual(42, Conductor.currentStep);
|
||||
FunkinAssert.areNear(42.0 + 12 / 60, Conductor.currentStepTime);
|
||||
|
||||
step(); // 362
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 362, Conductor.songPosition);
|
||||
Assert.areEqual(10, Conductor.currentBeat);
|
||||
Assert.areEqual(42, Conductor.currentStep);
|
||||
FunkinAssert.areNear(42.0 + 24 / 60, Conductor.currentStepTime);
|
||||
|
||||
step(3); // 365
|
||||
|
||||
FunkinAssert.areNear(FunkinTest.MS_PER_STEP * 365, Conductor.songPosition);
|
||||
Assert.areEqual(10, Conductor.currentBeat);
|
||||
Assert.areEqual(43, Conductor.currentStep); // 42.999 => 42
|
||||
FunkinAssert.areNear(43.0, Conductor.currentStepTime);
|
||||
}
|
||||
}
|
||||
|
||||
class ConductorState extends FlxState
|
||||
{
|
||||
public var beatsHit:Int = 0;
|
||||
public var stepsHit:Int = 0;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
function beatHit():Void
|
||||
{
|
||||
beatsHit += 1;
|
||||
}
|
||||
|
||||
function stepHit():Void
|
||||
{
|
||||
stepsHit += 1;
|
||||
}
|
||||
|
||||
public override function create():Void
|
||||
{
|
||||
super.create();
|
||||
Conductor.beatHit.add(this.beatHit);
|
||||
Conductor.stepHit.add(this.stepHit);
|
||||
}
|
||||
|
||||
public override function destroy():Void
|
||||
{
|
||||
super.destroy();
|
||||
Conductor.beatHit.remove(this.beatHit);
|
||||
Conductor.stepHit.remove(this.stepHit);
|
||||
}
|
||||
|
||||
public override function update(elapsed:Float)
|
||||
{
|
||||
super.update(elapsed);
|
||||
|
||||
// On each step, increment the Conductor as though the song was playing.
|
||||
Conductor.update(Conductor.songPosition + elapsed * Constants.MS_PER_SEC);
|
||||
}
|
||||
}
|
228
tests/unit/source/funkin/data/BaseRegistryTest.hx
Normal file
228
tests/unit/source/funkin/data/BaseRegistryTest.hx
Normal file
|
@ -0,0 +1,228 @@
|
|||
package funkin.data;
|
||||
|
||||
import massive.munit.util.Timer;
|
||||
import massive.munit.Assert;
|
||||
import massive.munit.async.AsyncFactory;
|
||||
import funkin.data.BaseRegistry;
|
||||
import funkin.util.SortUtil;
|
||||
import funkin.util.VersionUtil;
|
||||
|
||||
@:access(funkin.data.BaseRegistry)
|
||||
class BaseRegistryTest extends FunkinTest
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public function beforeClass() {}
|
||||
|
||||
@AfterClass
|
||||
public function afterClass() {}
|
||||
|
||||
@Before
|
||||
public function setup() {}
|
||||
|
||||
@After
|
||||
public function tearDown() {}
|
||||
|
||||
@Test
|
||||
public function testMyTypeRegistry()
|
||||
{
|
||||
// This shouldn't crash.
|
||||
MyTypeRegistry.instance.loadEntries();
|
||||
|
||||
// Ensure all entries were loaded.
|
||||
var entryList = MyTypeRegistry.instance.listEntryIds();
|
||||
entryList.sort(SortUtil.alphabetically);
|
||||
|
||||
Assert.areEqual(entryList, [
|
||||
"blablabla",
|
||||
"fizzbuzz",
|
||||
"foobar",
|
||||
// "junk"
|
||||
]);
|
||||
|
||||
// Ensure this one is not in the list.
|
||||
Assert.areEqual(entryList.indexOf("junk"), -1);
|
||||
|
||||
// Ensure blablabla got parsed correctly.
|
||||
var blablabla = MyTypeRegistry.instance.fetchEntry("blablabla");
|
||||
Assert.areEqual(blablabla.id, "blablabla");
|
||||
Assert.areEqual(blablabla._data.version, "1.0.0");
|
||||
Assert.areEqual(blablabla._data.name, "blablabla API");
|
||||
}
|
||||
}
|
||||
|
||||
typedef MyTypeData =
|
||||
{
|
||||
/**
|
||||
* The version number of the data schema.
|
||||
* When making changes to the note style data format, this should be incremented,
|
||||
* and a migration function should be added to handle old versions.
|
||||
*/
|
||||
@:default(funkin.data.BaseRegistryTest.MyTypeRegistry.DATA_VERSION)
|
||||
var version:String;
|
||||
|
||||
var id:String;
|
||||
var name:String;
|
||||
var data:Array<MySubTypeData>;
|
||||
}
|
||||
|
||||
typedef MySubTypeData =
|
||||
{
|
||||
var foo:String;
|
||||
var bar:String;
|
||||
};
|
||||
|
||||
typedef MyTypeData_v0_1_x =
|
||||
{
|
||||
var version:String;
|
||||
var id:Int;
|
||||
var name:String;
|
||||
};
|
||||
|
||||
class MyType implements IRegistryEntry<MyTypeData>
|
||||
{
|
||||
/**
|
||||
* The ID of the mytype.
|
||||
*/
|
||||
public final id:String;
|
||||
|
||||
/**
|
||||
* Mytype data as parsed from the JSON file.
|
||||
*/
|
||||
public final _data:MyTypeData;
|
||||
|
||||
/**
|
||||
* @param id The ID of the JSON file to parse.
|
||||
*/
|
||||
public function new(id:String)
|
||||
{
|
||||
this.id = id;
|
||||
_data = _fetchData(id);
|
||||
|
||||
if (_data == null)
|
||||
{
|
||||
throw 'Could not parse mytype data for id: $id';
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy():Void {}
|
||||
|
||||
public function toString():String
|
||||
{
|
||||
return 'MyType($id)';
|
||||
}
|
||||
|
||||
public function _fetchData(id:String):Null<MyTypeData>
|
||||
{
|
||||
return MyTypeRegistry.instance.parseEntryDataWithMigration(id, MyTypeRegistry.instance.fetchEntryVersion(id));
|
||||
}
|
||||
|
||||
public function getSubData():Array<MySubTypeData>
|
||||
{
|
||||
return _data.data;
|
||||
}
|
||||
}
|
||||
|
||||
class MyTypeRegistry extends BaseRegistry<MyType, MyTypeData>
|
||||
{
|
||||
/**
|
||||
* The current version string for the note style data format.
|
||||
* Handle breaking changes by incrementing this value
|
||||
* and adding migration to the `migrateMyTypeData()` function.
|
||||
*/
|
||||
public static final DATA_VERSION:String = "1.0.0";
|
||||
|
||||
public static final instance:MyTypeRegistry = new MyTypeRegistry();
|
||||
|
||||
public function new()
|
||||
{
|
||||
super('MYTYPE', 'mytype');
|
||||
}
|
||||
|
||||
/**
|
||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
||||
*/
|
||||
public function parseEntryData(id:String):Null<MyTypeData>
|
||||
{
|
||||
// JsonParser does not take type parameters,
|
||||
// otherwise this function would be in BaseRegistry.
|
||||
var parser = new json2object.JsonParser<MyTypeData>();
|
||||
var jsonStr:String = loadEntryFile(id);
|
||||
|
||||
parser.fromJson(jsonStr);
|
||||
|
||||
if (parser.errors.length > 0)
|
||||
{
|
||||
trace('[${registryId}] Failed to parse entry data: ${id}');
|
||||
for (error in parser.errors)
|
||||
{
|
||||
trace(error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return parser.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read, parse, and validate the JSON data and produce the corresponding data object.
|
||||
*/
|
||||
public function parseEntryData_v0_1_x(id:String):Null<MyTypeData>
|
||||
{
|
||||
// JsonParser does not take type parameters,
|
||||
// otherwise this function would be in BaseRegistry.
|
||||
var parser = new json2object.JsonParser<MyTypeData_v0_1_x>();
|
||||
var jsonStr:String = loadEntryFile(id);
|
||||
|
||||
parser.fromJson(jsonStr);
|
||||
|
||||
if (parser.errors.length > 0)
|
||||
{
|
||||
trace('[${registryId}] Failed to parse entry data: ${id}');
|
||||
for (error in parser.errors)
|
||||
{
|
||||
trace(error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var oldData:MyTypeData_v0_1_x = parser.value;
|
||||
|
||||
var result:MyTypeData =
|
||||
{
|
||||
version: DATA_VERSION,
|
||||
id: '${oldData.id}',
|
||||
name: oldData.name,
|
||||
data: []
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override function parseEntryDataWithMigration(id:String, version:thx.semver.Version):Null<MyTypeData>
|
||||
{
|
||||
if (VersionUtil.validateVersion(version, "0.1.x"))
|
||||
{
|
||||
trace('Migrating mytype data from ${version} to ${DATA_VERSION}');
|
||||
return parseEntryData_v0_1_x(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
trace('Parsing mytype data with version ${version}');
|
||||
return super.parseEntryDataWithMigration(id, version);
|
||||
}
|
||||
}
|
||||
|
||||
function createScriptedEntry(clsName:String):MyType
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
function getScriptedClassNames():Array<String>
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package funkin.data.notestyle;
|
||||
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import massive.munit.Assert;
|
||||
import massive.munit.async.AsyncFactory;
|
||||
import massive.munit.util.Timer;
|
||||
|
||||
@:access(funkin.play.notes.notestyle.NoteStyle)
|
||||
@:access(funkin.data.notestyle.NoteStyleRegistry)
|
||||
class NoteStyleRegistryTest extends FunkinTest
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public function beforeClass()
|
||||
{
|
||||
NoteStyleRegistry.instance.loadEntries();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public function afterClass() {}
|
||||
|
||||
@Before
|
||||
public function setup() {}
|
||||
|
||||
@After
|
||||
public function tearDown() {}
|
||||
|
||||
@Test
|
||||
public function testValid()
|
||||
{
|
||||
Assert.isNotNull(NoteStyleRegistry.instance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testParseEntryData()
|
||||
{
|
||||
var result:NoteStyleData = NoteStyleRegistry.instance.parseEntryData("test2");
|
||||
|
||||
Assert.areEqual(result.version, "1.0.0");
|
||||
Assert.areEqual(result.name, "Test2");
|
||||
Assert.areEqual(result.author, "Eric");
|
||||
Assert.areEqual(result.fallback, "funkin");
|
||||
|
||||
Assert.areEqual(result.assets.note.assetPath, "shared:coolstuff");
|
||||
Assert.areEqual(result.assets.note.scale, 1.8);
|
||||
Assert.areEqual(result.assets.note.data.left.prefix, "noteLeft1");
|
||||
Assert.areEqual(result.assets.note.data.down.prefix, "noteDown3");
|
||||
Assert.areEqual(result.assets.note.data.up.prefix, "noteUp2");
|
||||
Assert.areEqual(result.assets.note.data.right.prefix, "noteRight4");
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testFetchEntry()
|
||||
{
|
||||
var result:NoteStyle = NoteStyleRegistry.instance.fetchEntry("test2");
|
||||
|
||||
Assert.areEqual(result.toString(), "NoteStyle(test2)");
|
||||
Assert.areEqual(result.getName(), "Test2");
|
||||
Assert.areEqual(result.getAuthor(), "Eric");
|
||||
Assert.areEqual(result.getFallbackID(), "funkin");
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testFetchBadEntry()
|
||||
{
|
||||
var result:NoteStyle = NoteStyleRegistry.instance.fetchEntry("blablabla");
|
||||
|
||||
Assert.areEqual(result, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testFetchDefault()
|
||||
{
|
||||
var nsrMock:NoteStyleRegistry = mock(NoteStyleRegistry);
|
||||
|
||||
nsrMock.fetchDefault().callsRealMethod();
|
||||
|
||||
// Perform the call.
|
||||
nsrMock.fetchDefault();
|
||||
|
||||
// Verify the underlying call.
|
||||
|
||||
nsrMock.fetchEntry(NoteStyleRegistry.DEFAULT_NOTE_STYLE_ID).verify(times(1));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package funkin.play.notes.notestyle;
|
||||
|
||||
import massive.munit.util.Timer;
|
||||
import massive.munit.Assert;
|
||||
import massive.munit.async.AsyncFactory;
|
||||
import funkin.data.notestyle.NoteStyleRegistry;
|
||||
import funkin.play.notes.notestyle.NoteStyle;
|
||||
import flixel.animation.FlxAnimationController;
|
||||
|
||||
@:access(funkin.play.notes.notestyle.NoteStyle)
|
||||
class NoteStyleTest extends FunkinTest
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public function beforeClass()
|
||||
{
|
||||
NoteStyleRegistry.instance.loadEntries();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public function afterClass() {}
|
||||
|
||||
@Before
|
||||
public function setup() {}
|
||||
|
||||
@After
|
||||
public function tearDown() {}
|
||||
|
||||
@Test
|
||||
@Ignore("This test doesn't work, crashes when the project has 2 mocks of the same class???")
|
||||
public function testBuildNoteSprite()
|
||||
{
|
||||
var target:NoteStyle = NoteStyleRegistry.instance.fetchEntry("funkin");
|
||||
|
||||
var mockNoteSprite:NoteSprite = mock(NoteSprite);
|
||||
// var mockAnim = mock(FlxAnimationController);
|
||||
// mockNoteSprite.animation = mockAnim;
|
||||
|
||||
target.buildNoteSprite(mockNoteSprite);
|
||||
|
||||
Assert.areEqual(mockNoteSprite.frames, []);
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testFallbackBehavior()
|
||||
{
|
||||
var target1:NoteStyle = NoteStyleRegistry.instance.fetchEntry("funkin");
|
||||
var target2:NoteStyle = NoteStyleRegistry.instance.fetchEntry("test2");
|
||||
|
||||
Assert.areEqual("funkin", target1.id);
|
||||
Assert.areEqual("test2", target2.id);
|
||||
|
||||
Assert.areEqual("Funkin'", target1.getName());
|
||||
Assert.areEqual("Test2", target2.getName());
|
||||
|
||||
Assert.isNull(target1.getFallbackID());
|
||||
Assert.areEqual(target1.id, target2.getFallbackID());
|
||||
|
||||
// Overridden fields are different.
|
||||
Assert.areEqual("arrows", target1.getNoteAssetPath(false));
|
||||
Assert.areEqual("coolstuff", target2.getNoteAssetPath(false));
|
||||
|
||||
Assert.areEqual("shared:arrows", target1.getNoteAssetPath(true));
|
||||
Assert.areEqual("shared:coolstuff", target2.getNoteAssetPath(true));
|
||||
|
||||
// Unspecified fields use the fallback.
|
||||
// Should NOT return null!
|
||||
Assert.areEqual("assets/images/NOTE_hold_assets.png", target1.getHoldNoteAssetPath(false));
|
||||
Assert.areEqual("assets/images/NOTE_hold_assets.png", target2.getHoldNoteAssetPath(false));
|
||||
|
||||
Assert.areEqual("NOTE_hold_assets", target1.getHoldNoteAssetPath(true));
|
||||
}
|
||||
}
|
137
tests/unit/source/funkin/util/BezierUtilTest.hx
Normal file
137
tests/unit/source/funkin/util/BezierUtilTest.hx
Normal file
|
@ -0,0 +1,137 @@
|
|||
package funkin.util;
|
||||
|
||||
import flixel.math.FlxPoint;
|
||||
import massive.munit.util.Timer;
|
||||
import massive.munit.Assert;
|
||||
import massive.munit.async.AsyncFactory;
|
||||
import funkin.util.BezierUtil;
|
||||
|
||||
@:access(funkin.util.BezierUtil)
|
||||
class BezierUtilTest extends FunkinTest
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public function beforeClass() {}
|
||||
|
||||
@AfterClass
|
||||
public function afterClass() {}
|
||||
|
||||
@Before
|
||||
public function setup() {}
|
||||
|
||||
@After
|
||||
public function tearDown() {}
|
||||
|
||||
@Test
|
||||
public function testBezier2()
|
||||
{
|
||||
var point1:FlxPoint = FlxPoint.get(1, 1);
|
||||
var point2:FlxPoint = FlxPoint.get(2, 2);
|
||||
|
||||
var result:FlxPoint = BezierUtil.bezier2(0.5, point1, point2);
|
||||
|
||||
Assert.areEqual(result.x, 1.5);
|
||||
Assert.areEqual(result.y, 1.5);
|
||||
|
||||
result = BezierUtil.bezier2(0.25, point1, point2);
|
||||
|
||||
Assert.areEqual(result.x, 1.25);
|
||||
Assert.areEqual(result.y, 1.25);
|
||||
|
||||
result = BezierUtil.bezier2(0.75, point1, point2);
|
||||
|
||||
Assert.areEqual(result.x, 1.75);
|
||||
Assert.areEqual(result.y, 1.75);
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testBezier3()
|
||||
{
|
||||
var point1:FlxPoint = FlxPoint.get(1, 1);
|
||||
var point2:FlxPoint = FlxPoint.get(2, 2);
|
||||
var point3:FlxPoint = FlxPoint.get(3, 3);
|
||||
|
||||
var result:FlxPoint = BezierUtil.bezier3(0.5, point1, point2, point3);
|
||||
|
||||
Assert.areEqual(result.x, 2);
|
||||
Assert.areEqual(result.y, 2);
|
||||
|
||||
result = BezierUtil.bezier3(0.25, point1, point2, point3);
|
||||
|
||||
Assert.areEqual(result.x, 1.5);
|
||||
Assert.areEqual(result.y, 1.5);
|
||||
|
||||
result = BezierUtil.bezier3(0.75, point1, point2, point3);
|
||||
|
||||
Assert.areEqual(result.x, 2.5);
|
||||
Assert.areEqual(result.y, 2.5);
|
||||
|
||||
result = BezierUtil.bezier3(0.5, point1, point2, point3);
|
||||
|
||||
Assert.areEqual(result.x, 2);
|
||||
Assert.areEqual(result.y, 2);
|
||||
|
||||
result = BezierUtil.bezier3(0.6, point1, point2, point3);
|
||||
|
||||
Assert.areEqual(result.x, 2.2);
|
||||
Assert.areEqual(result.y, 2.2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testBezier4()
|
||||
{
|
||||
var point1:FlxPoint = FlxPoint.get(1, 1);
|
||||
var point2:FlxPoint = FlxPoint.get(2, 2);
|
||||
var point3:FlxPoint = FlxPoint.get(3, 3);
|
||||
var point4:FlxPoint = FlxPoint.get(4, 4);
|
||||
|
||||
var result:FlxPoint = BezierUtil.bezier4(0.5, point1, point2, point3, point4);
|
||||
|
||||
Assert.areEqual(result.x, 2.5);
|
||||
Assert.areEqual(result.y, 2.5);
|
||||
|
||||
result = BezierUtil.bezier4(0.25, point1, point2, point3, point4);
|
||||
|
||||
Assert.areEqual(result.x, 1.75);
|
||||
Assert.areEqual(result.y, 1.75);
|
||||
|
||||
result = BezierUtil.bezier4(0.75, point1, point2, point3, point4);
|
||||
|
||||
Assert.areEqual(result.x, 3.25);
|
||||
Assert.areEqual(result.y, 3.25);
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testBezier5()
|
||||
{
|
||||
var point1:FlxPoint = FlxPoint.get(1, 1);
|
||||
var point2:FlxPoint = FlxPoint.get(2, 2);
|
||||
var point3:FlxPoint = FlxPoint.get(3, 3);
|
||||
var point4:FlxPoint = FlxPoint.get(4, 4);
|
||||
var point5:FlxPoint = FlxPoint.get(5, 5);
|
||||
|
||||
var result:FlxPoint = BezierUtil.bezier5(0.5, point1, point2, point3, point4, point5);
|
||||
|
||||
Assert.areEqual(result.x, 3);
|
||||
Assert.areEqual(result.y, 3);
|
||||
|
||||
result = BezierUtil.bezier5(0.25, point1, point2, point3, point4, point5);
|
||||
|
||||
Assert.areEqual(result.x, 2);
|
||||
Assert.areEqual(result.y, 2);
|
||||
|
||||
result = BezierUtil.bezier5(0.75, point1, point2, point3, point4, point5);
|
||||
|
||||
Assert.areEqual(result.x, 4);
|
||||
Assert.areEqual(result.y, 4);
|
||||
|
||||
result = BezierUtil.bezier5(0.5, point1, point2, point3, point4, point5);
|
||||
|
||||
Assert.areEqual(result.x, 3);
|
||||
Assert.areEqual(result.y, 3);
|
||||
}
|
||||
}
|
71
tests/unit/source/funkin/util/ClipboardUtilTest.hx
Normal file
71
tests/unit/source/funkin/util/ClipboardUtilTest.hx
Normal file
|
@ -0,0 +1,71 @@
|
|||
package funkin.util;
|
||||
|
||||
import massive.munit.util.Timer;
|
||||
import massive.munit.Assert;
|
||||
import massive.munit.async.AsyncFactory;
|
||||
import funkin.util.ClipboardUtil;
|
||||
|
||||
@:access(funkin.util.ClipboardUtil)
|
||||
class ClipboardUtilTest extends FunkinTest
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public function beforeClass() {}
|
||||
|
||||
@AfterClass
|
||||
public function afterClass() {}
|
||||
|
||||
@Before
|
||||
public function setup() {}
|
||||
|
||||
@After
|
||||
public function tearDown() {}
|
||||
|
||||
@Test
|
||||
public function testGetSetClipboard()
|
||||
{
|
||||
var testString = "test string";
|
||||
ClipboardUtil.setClipboard(testString);
|
||||
var clipboardString = ClipboardUtil.getClipboard();
|
||||
Assert.areEqual(testString, clipboardString);
|
||||
}
|
||||
|
||||
@Ignore("This test doesn't work, Lime issue?")
|
||||
@Test
|
||||
public function testAddRemoveListener()
|
||||
{
|
||||
ClipboardUtil.addListener(onClipboardChange);
|
||||
|
||||
var testString = "test string";
|
||||
ClipboardUtil.setClipboard(testString);
|
||||
|
||||
var clipboardString = ClipboardUtil.getClipboard();
|
||||
Assert.areEqual(testString, clipboardString);
|
||||
|
||||
step();
|
||||
|
||||
// TODO: Fix issue where this test fails
|
||||
Assert.areEqual(1, count);
|
||||
|
||||
ClipboardUtil.removeListener(onClipboardChange);
|
||||
|
||||
var testString2 = "test string 2";
|
||||
ClipboardUtil.setClipboard(testString2);
|
||||
|
||||
var clipboardString2 = ClipboardUtil.getClipboard();
|
||||
Assert.areEqual(testString2, clipboardString2);
|
||||
|
||||
Assert.areEqual(1, count);
|
||||
}
|
||||
|
||||
var count:Int = 0;
|
||||
|
||||
function onClipboardChange()
|
||||
{
|
||||
count += 1;
|
||||
}
|
||||
}
|
35
tests/unit/source/funkin/util/DateUtilTest.hx
Normal file
35
tests/unit/source/funkin/util/DateUtilTest.hx
Normal file
|
@ -0,0 +1,35 @@
|
|||
package funkin.util;
|
||||
|
||||
import massive.munit.util.Timer;
|
||||
import massive.munit.Assert;
|
||||
import massive.munit.async.AsyncFactory;
|
||||
import funkin.util.DateUtil;
|
||||
|
||||
@:access(funkin.util.DateUtil)
|
||||
class DateUtilTest extends FunkinTest
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public function beforeClass() {}
|
||||
|
||||
@AfterClass
|
||||
public function afterClass() {}
|
||||
|
||||
@Before
|
||||
public function setup() {}
|
||||
|
||||
@After
|
||||
public function tearDown() {}
|
||||
|
||||
@Test
|
||||
public function testGenerateTimestamp()
|
||||
{
|
||||
var date:Date = new Date(2020, 10 - 1, 31, 3, 0, 0);
|
||||
var timestamp:String = DateUtil.generateTimestamp(date);
|
||||
Assert.areEqual("2020-10-31-03-00-00", timestamp);
|
||||
}
|
||||
}
|
87
tests/unit/source/funkin/util/SerializerUtilTest.hx
Normal file
87
tests/unit/source/funkin/util/SerializerUtilTest.hx
Normal file
|
@ -0,0 +1,87 @@
|
|||
package funkin.util;
|
||||
|
||||
import haxe.io.Bytes;
|
||||
import massive.munit.util.Timer;
|
||||
import massive.munit.Assert;
|
||||
import massive.munit.async.AsyncFactory;
|
||||
import funkin.util.SerializerUtil;
|
||||
|
||||
typedef FooBar =
|
||||
{
|
||||
a:Int,
|
||||
b:Int,
|
||||
c:Int
|
||||
};
|
||||
|
||||
@:access(funkin.util.SerializerUtil)
|
||||
class SerializerUtilTest extends FunkinTest
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public function beforeClass() {}
|
||||
|
||||
@AfterClass
|
||||
public function afterClass() {}
|
||||
|
||||
@Before
|
||||
public function setup() {}
|
||||
|
||||
@After
|
||||
public function tearDown() {}
|
||||
|
||||
@Test
|
||||
public function testToJSON()
|
||||
{
|
||||
var object = {a: 1, b: 2, c: 3};
|
||||
var json = SerializerUtil.toJSON(object);
|
||||
Assert.areEqual('{' + '\n\t"a": 1,' + '\n\t"b": 2,' + '\n\t"c": 3' + '\n}', json);
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testFromJSON()
|
||||
{
|
||||
var json = '{' + '\n\t"a": 1,' + '\n\t"b": 2,' + '\n\t"c": 3' + '\n}';
|
||||
var object = SerializerUtil.fromJSON(json);
|
||||
Assert.areEqual(1, object.a);
|
||||
Assert.areEqual(2, object.b);
|
||||
Assert.areEqual(3, object.c);
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testFromJSONBytes()
|
||||
{
|
||||
var json = '{' + '\n\t"a": 1,' + '\n\t"b": 2,' + '\n\t"c": 3' + '\n}';
|
||||
var bytes = Bytes.ofString(json);
|
||||
|
||||
var object = SerializerUtil.fromJSONBytes(bytes);
|
||||
Assert.areEqual(1, object.a);
|
||||
Assert.areEqual(2, object.b);
|
||||
Assert.areEqual(3, object.c);
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testReplacer()
|
||||
{
|
||||
var version:thx.semver.Version = '1.0.0-beta';
|
||||
|
||||
Assert.areEqual(1, version.major);
|
||||
Assert.areEqual(0, version.minor);
|
||||
Assert.areEqual(0, version.patch);
|
||||
Assert.areEqual(true, version.hasPre);
|
||||
Assert.areEqual('beta', version.pre);
|
||||
// Assert.areEqual(false, version.hasBuild);
|
||||
Assert.areEqual('', version.build);
|
||||
|
||||
var formatted = SerializerUtil.replacer('version', version);
|
||||
|
||||
Assert.areEqual('1.0.0-beta', formatted);
|
||||
|
||||
var test2 = SerializerUtil.toJSON({version: version});
|
||||
|
||||
Assert.areEqual('{' + '\n\t"version": "1.0.0-beta"' + '\n}', test2);
|
||||
}
|
||||
}
|
63
tests/unit/source/funkin/util/SortUtilTest.hx
Normal file
63
tests/unit/source/funkin/util/SortUtilTest.hx
Normal file
|
@ -0,0 +1,63 @@
|
|||
package funkin.util;
|
||||
|
||||
import flixel.FlxObject;
|
||||
import flixel.FlxSprite;
|
||||
import flixel.util.FlxSort;
|
||||
import funkin.play.song.SongData.SongNoteData;
|
||||
import massive.munit.util.Timer;
|
||||
import massive.munit.Assert;
|
||||
import massive.munit.async.AsyncFactory;
|
||||
import funkin.util.SortUtil;
|
||||
|
||||
@:access(funkin.util.SortUtil)
|
||||
class SortUtilTest extends FunkinTest
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public function beforeClass() {}
|
||||
|
||||
@AfterClass
|
||||
public function afterClass() {}
|
||||
|
||||
@Before
|
||||
public function setup() {}
|
||||
|
||||
@After
|
||||
public function tearDown() {}
|
||||
|
||||
@Test
|
||||
public function testAlphabetically()
|
||||
{
|
||||
var arr:Array<String> = ["b", "a", "c"];
|
||||
|
||||
arr.sort(SortUtil.alphabetically);
|
||||
|
||||
Assert.areEqual(["a", "b", "c"], arr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testByZIndex()
|
||||
{
|
||||
var arr:Array<FlxObject> = [new FlxSprite(), new FlxObject(), new FlxSprite()];
|
||||
|
||||
arr[0].zIndex = 2000;
|
||||
arr[1].zIndex = 1000;
|
||||
arr[2].zIndex = 3000;
|
||||
|
||||
arr.sort(SortUtil.byZIndex.bind(FlxSort.ASCENDING));
|
||||
|
||||
Assert.areEqual(1000, arr[0].zIndex);
|
||||
Assert.areEqual(2000, arr[1].zIndex);
|
||||
Assert.areEqual(3000, arr[2].zIndex);
|
||||
|
||||
arr.sort(SortUtil.byZIndex.bind(FlxSort.DESCENDING));
|
||||
|
||||
Assert.areEqual(3000, arr[0].zIndex);
|
||||
Assert.areEqual(2000, arr[1].zIndex);
|
||||
Assert.areEqual(1000, arr[2].zIndex);
|
||||
}
|
||||
}
|
64
tests/unit/source/funkin/util/VersionUtilTest.hx
Normal file
64
tests/unit/source/funkin/util/VersionUtilTest.hx
Normal file
|
@ -0,0 +1,64 @@
|
|||
package funkin.util;
|
||||
|
||||
import massive.munit.util.Timer;
|
||||
import massive.munit.Assert;
|
||||
import massive.munit.async.AsyncFactory;
|
||||
import funkin.util.VersionUtil;
|
||||
|
||||
@:access(funkin.util.VersionUtil)
|
||||
class VersionUtilTest extends FunkinTest
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public function beforeClass() {}
|
||||
|
||||
@AfterClass
|
||||
public function afterClass() {}
|
||||
|
||||
@Before
|
||||
public function setup() {}
|
||||
|
||||
@After
|
||||
public function tearDown() {}
|
||||
|
||||
@Test
|
||||
public function testValidateVersionStr()
|
||||
{
|
||||
Assert.areEqual(true, VersionUtil.validateVersionStr("1.0.0", "1.0.0"));
|
||||
|
||||
Assert.areEqual(false, VersionUtil.validateVersionStr("ehe", "test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testValidateVersion()
|
||||
{
|
||||
var version:thx.semver.Version = "1.0.0"; // implicit cast
|
||||
var versionRule:thx.semver.VersionRule = "1.0.0"; // implicit cast
|
||||
|
||||
Assert.areEqual(true, VersionUtil.validateVersion(version, versionRule));
|
||||
|
||||
var versionRule2:thx.semver.VersionRule = ">=3.1.0"; // implicit cast
|
||||
|
||||
var version1:thx.semver.Version = "3.0.0";
|
||||
var version2:thx.semver.Version = "3.1.1";
|
||||
var version3:thx.semver.Version = "4.2.0";
|
||||
|
||||
Assert.areEqual(false, VersionUtil.validateVersion(version1, versionRule2));
|
||||
Assert.areEqual(true, VersionUtil.validateVersion(version2, versionRule2));
|
||||
Assert.areEqual(true, VersionUtil.validateVersion(version3, versionRule2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testGetVersionFromJSON()
|
||||
{
|
||||
var jsonStr:String = "{ \"version\": \"3.1.0\" }";
|
||||
|
||||
var version:thx.semver.Version = VersionUtil.getVersionFromJSON(jsonStr);
|
||||
|
||||
Assert.areEqual("3.1.0", version.toString());
|
||||
}
|
||||
}
|
42
tests/unit/source/funkin/util/assets/DataAssetsTest.hx
Normal file
42
tests/unit/source/funkin/util/assets/DataAssetsTest.hx
Normal file
|
@ -0,0 +1,42 @@
|
|||
package funkin.util.assets;
|
||||
|
||||
import openfl.utils.Assets;
|
||||
import massive.munit.util.Timer;
|
||||
import massive.munit.Assert;
|
||||
import massive.munit.async.AsyncFactory;
|
||||
import funkin.util.assets.DataAssets;
|
||||
|
||||
@:access(funkin.util.assets.DataAssets)
|
||||
class DataAssetsTest extends FunkinTest
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public function beforeClass() {}
|
||||
|
||||
@AfterClass
|
||||
public function afterClass() {}
|
||||
|
||||
@Before
|
||||
public function setup() {}
|
||||
|
||||
@After
|
||||
public function tearDown() {}
|
||||
|
||||
@Test
|
||||
public function testBuildDataPath()
|
||||
{
|
||||
Assert.areEqual('assets/data/test.json', DataAssets.buildDataPath('test.json'));
|
||||
}
|
||||
|
||||
@Test
|
||||
public function listDataFilesInPath()
|
||||
{
|
||||
var expected = ['blablabla', 'test1', 'test2'];
|
||||
|
||||
Assert.areEqual(expected, DataAssets.listDataFilesInPath('test/'));
|
||||
}
|
||||
}
|
118
tests/unit/source/funkin/util/assets/FlxAnimationUtilTest.hx
Normal file
118
tests/unit/source/funkin/util/assets/FlxAnimationUtilTest.hx
Normal file
|
@ -0,0 +1,118 @@
|
|||
package funkin.util.assets;
|
||||
|
||||
import funkin.util.assets.FlxAnimationUtil;
|
||||
import flixel.animation.FlxAnimationController;
|
||||
import funkin.data.animation.AnimationData;
|
||||
import massive.munit.util.Timer;
|
||||
import massive.munit.Assert;
|
||||
import massive.munit.async.AsyncFactory;
|
||||
import funkin.util.DateUtil;
|
||||
import flixel.FlxSprite;
|
||||
|
||||
@:access(funkin.util.assets.FlxAnimationUtil)
|
||||
class FlxAnimationUtilTest extends FunkinTest
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public function beforeClass() {}
|
||||
|
||||
@AfterClass
|
||||
public function afterClass() {}
|
||||
|
||||
@Before
|
||||
public function setup() {}
|
||||
|
||||
@After
|
||||
public function tearDown() {}
|
||||
|
||||
@Test
|
||||
public function testAddAtlasAnimation()
|
||||
{
|
||||
// Build a mock child class of FlxSprite
|
||||
var mockSprite = mock(FlxSprite);
|
||||
var mockAnim = mock(FlxAnimationController);
|
||||
mockSprite.animation = mockAnim;
|
||||
|
||||
var animData:AnimationData =
|
||||
{
|
||||
name: "testAnim",
|
||||
prefix: "blablabla"
|
||||
};
|
||||
|
||||
FlxAnimationUtil.addAtlasAnimation(mockSprite, animData);
|
||||
|
||||
// Verify that the method was called once.
|
||||
// If not, a VerificationException will be thrown and the test will fail.
|
||||
mockAnim.addByPrefix("testAnim", "blablabla", 24, false, false, false).verify(times(1));
|
||||
|
||||
// Verify there were no other functions called.
|
||||
mockAnim.verifyZeroInteractions();
|
||||
mockSprite.verifyZeroInteractions();
|
||||
|
||||
var animData2:AnimationData =
|
||||
{
|
||||
name: "testAnim2",
|
||||
prefix: "blablabla2",
|
||||
frameIndices: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||
frameRate: 12,
|
||||
flipX: true,
|
||||
flipY: true,
|
||||
looped: true
|
||||
};
|
||||
|
||||
FlxAnimationUtil.addAtlasAnimation(mockSprite, animData2);
|
||||
|
||||
try
|
||||
{
|
||||
mockAnim.addByIndices("testAnim2", "blablabla2", cast anyIterator, "", 12, true, true, true).verify(times(1));
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
trace('CAUGHT EXCEPTION');
|
||||
trace(e);
|
||||
}
|
||||
|
||||
mockAnim.verifyZeroInteractions();
|
||||
mockSprite.verifyZeroInteractions();
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testAddAtlasAnimations()
|
||||
{
|
||||
// Build a mock child class of FlxSprite
|
||||
var mockSprite = mock(FlxSprite);
|
||||
var mockAnim = mock(FlxAnimationController);
|
||||
mockSprite.animation = mockAnim;
|
||||
|
||||
var animData:Array<AnimationData> = [
|
||||
{
|
||||
name: "testAnim",
|
||||
prefix: "blablabla"
|
||||
},
|
||||
{
|
||||
name: "testAnim2",
|
||||
prefix: "blablabla2"
|
||||
},
|
||||
{
|
||||
name: "testAnim3",
|
||||
prefix: "blablabla3"
|
||||
}
|
||||
];
|
||||
|
||||
FlxAnimationUtil.addAtlasAnimations(mockSprite, animData);
|
||||
|
||||
// Verify that the method was called once.
|
||||
// If not, a VerificationException will be thrown and the test will fail.
|
||||
mockAnim.addByPrefix("testAnim", "blablabla", 24, false, false, false).verify(times(1));
|
||||
mockAnim.addByPrefix("testAnim2", "blablabla2", 24, false, false, false).verify(times(1));
|
||||
mockAnim.addByPrefix("testAnim3", "blablabla3", 24, false, false, false).verify(times(1));
|
||||
|
||||
// Verify there were no other functions called.
|
||||
mockAnim.verifyZeroInteractions();
|
||||
mockSprite.verifyZeroInteractions();
|
||||
}
|
||||
}
|
165
tests/unit/source/funkin/util/tools/ArraySortToolsTest.hx
Normal file
165
tests/unit/source/funkin/util/tools/ArraySortToolsTest.hx
Normal file
|
@ -0,0 +1,165 @@
|
|||
package funkin.util.tools;
|
||||
|
||||
import massive.munit.util.Timer;
|
||||
import massive.munit.Assert;
|
||||
import massive.munit.async.AsyncFactory;
|
||||
import funkin.util.tools.ArrayTools;
|
||||
|
||||
@:access(funkin.util.tools.ArrayTools)
|
||||
class ArraySortToolsTest extends FunkinTest
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public function beforeClass() {}
|
||||
|
||||
@AfterClass
|
||||
public function afterClass() {}
|
||||
|
||||
@Before
|
||||
public function setup() {}
|
||||
|
||||
@After
|
||||
public function tearDown() {}
|
||||
|
||||
@Test
|
||||
public function testMergeSort()
|
||||
{
|
||||
var testArray:Array<Int> = [5, 4, 3, 2, 1];
|
||||
|
||||
function compare(a:Int, b:Int)
|
||||
{
|
||||
return a - b;
|
||||
}
|
||||
|
||||
ArraySortTools.mergeSort(testArray, compare);
|
||||
|
||||
Assert.areEqual(testArray[0], 1);
|
||||
Assert.areEqual(testArray[1], 2);
|
||||
Assert.areEqual(testArray[2], 3);
|
||||
Assert.areEqual(testArray[3], 4);
|
||||
Assert.areEqual(testArray[4], 5);
|
||||
|
||||
var testArray2:Array<Int> = [9, 6, 3, 12];
|
||||
|
||||
ArraySortTools.mergeSort(testArray2, compare);
|
||||
|
||||
Assert.areEqual(testArray2[0], 3);
|
||||
Assert.areEqual(testArray2[1], 6);
|
||||
Assert.areEqual(testArray2[2], 9);
|
||||
Assert.areEqual(testArray2[3], 12);
|
||||
|
||||
// Just make sure these don't crash.
|
||||
ArraySortTools.mergeSort([], compare);
|
||||
ArraySortTools.mergeSort(null, compare);
|
||||
ArraySortTools.mergeSort([], null);
|
||||
ArraySortTools.mergeSort(null, null);
|
||||
|
||||
// Make sure these throw an exception.
|
||||
try
|
||||
{
|
||||
ArraySortTools.mergeSort(testArray, null);
|
||||
|
||||
Assert.fail("Function should have thrown an exception.");
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
Assert.areEqual("No comparison function provided.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testQuickSort()
|
||||
{
|
||||
var testArray:Array<Int> = [5, 4, 3, 2, 1];
|
||||
|
||||
function compare(a:Int, b:Int)
|
||||
{
|
||||
return a - b;
|
||||
}
|
||||
|
||||
ArraySortTools.quickSort(testArray, compare);
|
||||
|
||||
Assert.areEqual(testArray[0], 1);
|
||||
Assert.areEqual(testArray[1], 2);
|
||||
Assert.areEqual(testArray[2], 3);
|
||||
Assert.areEqual(testArray[3], 4);
|
||||
Assert.areEqual(testArray[4], 5);
|
||||
|
||||
var testArray2:Array<Int> = [9, 6, 3, 12];
|
||||
|
||||
ArraySortTools.quickSort(testArray2, compare);
|
||||
|
||||
Assert.areEqual(testArray2[0], 3);
|
||||
Assert.areEqual(testArray2[1], 6);
|
||||
Assert.areEqual(testArray2[2], 9);
|
||||
Assert.areEqual(testArray2[3], 12);
|
||||
|
||||
// Just make sure these don't crash.
|
||||
ArraySortTools.quickSort([], compare);
|
||||
ArraySortTools.quickSort(null, compare);
|
||||
ArraySortTools.quickSort([], null);
|
||||
ArraySortTools.quickSort(null, null);
|
||||
|
||||
// Make sure these throw an exception.
|
||||
try
|
||||
{
|
||||
ArraySortTools.quickSort(testArray, null);
|
||||
|
||||
Assert.fail("Function should have thrown an exception.");
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
Assert.areEqual("No comparison function provided.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testInsertionSort()
|
||||
{
|
||||
var testArray:Array<Int> = [5, 4, 3, 2, 1];
|
||||
|
||||
function compare(a:Int, b:Int)
|
||||
{
|
||||
return a - b;
|
||||
}
|
||||
|
||||
ArraySortTools.insertionSort(testArray, compare);
|
||||
|
||||
Assert.areEqual(testArray[0], 1);
|
||||
Assert.areEqual(testArray[1], 2);
|
||||
Assert.areEqual(testArray[2], 3);
|
||||
Assert.areEqual(testArray[3], 4);
|
||||
Assert.areEqual(testArray[4], 5);
|
||||
|
||||
var testArray2:Array<Int> = [9, 6, 3, 12];
|
||||
|
||||
ArraySortTools.insertionSort(testArray2, compare);
|
||||
|
||||
Assert.areEqual(testArray2[0], 3);
|
||||
Assert.areEqual(testArray2[1], 6);
|
||||
Assert.areEqual(testArray2[2], 9);
|
||||
Assert.areEqual(testArray2[3], 12);
|
||||
|
||||
// Just make sure these don't crash.
|
||||
ArraySortTools.insertionSort([], compare);
|
||||
ArraySortTools.insertionSort(null, compare);
|
||||
ArraySortTools.insertionSort([], null);
|
||||
ArraySortTools.insertionSort(null, null);
|
||||
|
||||
// Make sure these throw an exception.
|
||||
try
|
||||
{
|
||||
ArraySortTools.insertionSort(testArray, null);
|
||||
|
||||
Assert.fail("Function should have thrown an exception.");
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
Assert.areEqual("No comparison function provided.", e);
|
||||
}
|
||||
}
|
||||
}
|
65
tests/unit/source/funkin/util/tools/ArrayToolsTest.hx
Normal file
65
tests/unit/source/funkin/util/tools/ArrayToolsTest.hx
Normal file
|
@ -0,0 +1,65 @@
|
|||
package funkin.util.tools;
|
||||
|
||||
import massive.munit.util.Timer;
|
||||
import massive.munit.Assert;
|
||||
import massive.munit.async.AsyncFactory;
|
||||
import funkin.util.tools.ArrayTools;
|
||||
|
||||
@:access(funkin.util.tools.ArrayTools)
|
||||
class ArrayToolsTest extends FunkinTest
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public function beforeClass() {}
|
||||
|
||||
@AfterClass
|
||||
public function afterClass() {}
|
||||
|
||||
@Before
|
||||
public function setup() {}
|
||||
|
||||
@After
|
||||
public function tearDown() {}
|
||||
|
||||
@Test
|
||||
public function testUnique()
|
||||
{
|
||||
var testArray:Array<Int> = [1, 2, 3, 4, 5, 6, 10, 7, 8, 9, 3, 4, 5, 6, 11, 7, 8];
|
||||
|
||||
var uniqueArray:Array<Int> = ArrayTools.unique(testArray);
|
||||
|
||||
Assert.areEqual(uniqueArray.length, 11);
|
||||
|
||||
// Array order doesn't change
|
||||
Assert.areEqual(uniqueArray[0], 1);
|
||||
Assert.areEqual(uniqueArray[1], 2);
|
||||
Assert.areEqual(uniqueArray[2], 3);
|
||||
Assert.areEqual(uniqueArray[3], 4);
|
||||
Assert.areEqual(uniqueArray[4], 5);
|
||||
Assert.areEqual(uniqueArray[5], 6);
|
||||
Assert.areEqual(uniqueArray[6], 10);
|
||||
Assert.areEqual(uniqueArray[7], 7);
|
||||
Assert.areEqual(uniqueArray[8], 8);
|
||||
Assert.areEqual(uniqueArray[9], 9);
|
||||
Assert.areEqual(uniqueArray[10], 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testFind()
|
||||
{
|
||||
function predicate(a:String):Bool
|
||||
{
|
||||
return a.startsWith("Hello");
|
||||
}
|
||||
|
||||
var testArray:Array<String> = ["Foo", "Bar", "HelloWorld", "Baz", "HelloTest"];
|
||||
|
||||
var result = ArrayTools.find(testArray, predicate);
|
||||
|
||||
Assert.areEqual(result, "HelloWorld");
|
||||
}
|
||||
}
|
59
tests/unit/source/funkin/util/tools/IteratorToolsTest.hx
Normal file
59
tests/unit/source/funkin/util/tools/IteratorToolsTest.hx
Normal file
|
@ -0,0 +1,59 @@
|
|||
package funkin.util.tools;
|
||||
|
||||
import massive.munit.util.Timer;
|
||||
import massive.munit.Assert;
|
||||
import massive.munit.async.AsyncFactory;
|
||||
import funkin.util.tools.IteratorTools;
|
||||
|
||||
@:access(funkin.util.tools.IteratorTools)
|
||||
class IteratorToolsTest extends FunkinTest
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public function beforeClass() {}
|
||||
|
||||
@AfterClass
|
||||
public function afterClass() {}
|
||||
|
||||
@Before
|
||||
public function setup() {}
|
||||
|
||||
@After
|
||||
public function tearDown() {}
|
||||
|
||||
@Test
|
||||
public function testArray()
|
||||
{
|
||||
var iter = new MyStringIterator("HelloWorld");
|
||||
|
||||
var arr = IteratorTools.array(iter);
|
||||
|
||||
Assert.areEqual(["H", "e", "l", "l", "o", "W", "o", "r", "l", "d"], arr);
|
||||
}
|
||||
}
|
||||
|
||||
class MyStringIterator
|
||||
{
|
||||
var s:String;
|
||||
var i:Int;
|
||||
|
||||
public function new(s:String)
|
||||
{
|
||||
this.s = s;
|
||||
i = 0;
|
||||
}
|
||||
|
||||
public function hasNext()
|
||||
{
|
||||
return i < s.length;
|
||||
}
|
||||
|
||||
public function next()
|
||||
{
|
||||
return s.charAt(i++);
|
||||
}
|
||||
}
|
55
tests/unit/source/funkin/util/tools/MapToolsTest.hx
Normal file
55
tests/unit/source/funkin/util/tools/MapToolsTest.hx
Normal file
|
@ -0,0 +1,55 @@
|
|||
package funkin.util.tools;
|
||||
|
||||
import massive.munit.util.Timer;
|
||||
import massive.munit.Assert;
|
||||
import massive.munit.async.AsyncFactory;
|
||||
import funkin.util.tools.MapTools;
|
||||
|
||||
@:access(funkin.util.tools.MapTools)
|
||||
class MapToolsTest extends FunkinTest
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public function beforeClass() {}
|
||||
|
||||
@AfterClass
|
||||
public function afterClass() {}
|
||||
|
||||
@Before
|
||||
public function setup() {}
|
||||
|
||||
@After
|
||||
public function tearDown() {}
|
||||
|
||||
@Test
|
||||
public function testSize()
|
||||
{
|
||||
var testMap:Map<String, String> = ["key1" => "value1", "key2" => "value2", "key3" => "value3"];
|
||||
|
||||
Assert.areEqual(3, MapTools.size(testMap));
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testValues()
|
||||
{
|
||||
var testMap:Map<String, String> = ["key1" => "value1", "key2" => "value2", "key3" => "value3"];
|
||||
|
||||
var result:Array<String> = MapTools.values(testMap);
|
||||
result.sort(SortUtil.alphabetically);
|
||||
Assert.areEqual(["value1", "value2", "value3"], result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testKeyValues()
|
||||
{
|
||||
var testMap:Map<String, String> = ["key1" => "value1", "key2" => "value2", "key3" => "value3"];
|
||||
|
||||
var result:Array<String> = MapTools.keyValues(testMap);
|
||||
result.sort(SortUtil.alphabetically);
|
||||
Assert.areEqual(["key1", "key2", "key3"], result);
|
||||
}
|
||||
}
|
59
tests/unit/source/funkin/util/tools/StringToolsTest.hx
Normal file
59
tests/unit/source/funkin/util/tools/StringToolsTest.hx
Normal file
|
@ -0,0 +1,59 @@
|
|||
package funkin.util.tools;
|
||||
|
||||
import massive.munit.util.Timer;
|
||||
import massive.munit.Assert;
|
||||
import massive.munit.async.AsyncFactory;
|
||||
import funkin.util.tools.StringTools;
|
||||
|
||||
@:access(funkin.util.tools.StringTools)
|
||||
class StringToolsTest extends FunkinTest
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public function beforeClass() {}
|
||||
|
||||
@AfterClass
|
||||
public function afterClass() {}
|
||||
|
||||
@Before
|
||||
public function setup() {}
|
||||
|
||||
@After
|
||||
public function tearDown() {}
|
||||
|
||||
@Test
|
||||
public function testToTitleCase()
|
||||
{
|
||||
var input = "hello world";
|
||||
|
||||
Assert.areEqual("Hello World", StringTools.toTitleCase(input));
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testToLowerKebabCase()
|
||||
{
|
||||
var input = "hello world";
|
||||
|
||||
Assert.areEqual("hello-world", StringTools.toLowerKebabCase(input));
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testToUpperKebabCase()
|
||||
{
|
||||
var input = "hello world";
|
||||
|
||||
Assert.areEqual("HELLO-WORLD", StringTools.toUpperKebabCase(input));
|
||||
}
|
||||
|
||||
@Test
|
||||
public function testParseJSON()
|
||||
{
|
||||
var input = "{ \"hello\": \"world\" }";
|
||||
|
||||
Assert.areEqual({hello: "world"}, StringTools.parseJSON(input));
|
||||
}
|
||||
}
|
21
tests/unit/source/import.hx
Normal file
21
tests/unit/source/import.hx
Normal file
|
@ -0,0 +1,21 @@
|
|||
#if !macro
|
||||
// Only import these when we aren't in a macro.
|
||||
import funkin.util.Constants;
|
||||
import funkin.Paths;
|
||||
import flixel.FlxG; // This one in particular causes a compile error if you're using macros.
|
||||
|
||||
// These are great.
|
||||
using Lambda;
|
||||
using StringTools;
|
||||
using funkin.util.tools.ArrayTools;
|
||||
using funkin.util.tools.ArraySortTools;
|
||||
using funkin.util.tools.IteratorTools;
|
||||
using funkin.util.tools.MapTools;
|
||||
using funkin.util.tools.StringTools;
|
||||
#end
|
||||
|
||||
// Testing-specific
|
||||
// Mocking
|
||||
import mockatoo.Mockatoo.*;
|
||||
|
||||
using mockatoo.Mockatoo;
|
3
tests/unit/start-mac-native.sh
Executable file
3
tests/unit/start-mac-native.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/zsh
|
||||
|
||||
haxe test-cpp.hxml
|
3
tests/unit/start-mac-web.sh
Executable file
3
tests/unit/start-mac-web.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/zsh
|
||||
|
||||
haxe test-web.hxml
|
3
tests/unit/start-win-native.bat
Normal file
3
tests/unit/start-win-native.bat
Normal file
|
@ -0,0 +1,3 @@
|
|||
REM Launches the unit tests for the native target on Windows.
|
||||
|
||||
haxe test-cpp.hxml
|
11
tests/unit/test-cpp.hxml
Normal file
11
tests/unit/test-cpp.hxml
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Updates TestSuite.hx to include all tests
|
||||
#-cmd haxelib run munit gen
|
||||
# Actually performs the tests
|
||||
#-cmd haxelib run munit test -debug -coverage
|
||||
# -debug may or may not be needed
|
||||
# -coverage adds code coverage reporting
|
||||
|
||||
# Legacy style. Doesn't give detailed coverage reports,
|
||||
# but it works without crashing.
|
||||
-cmd haxelib run munit gen
|
||||
-cmd haxelib run lime test cpp
|
5
tests/unit/test-web.hxml
Normal file
5
tests/unit/test-web.hxml
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Update the test runner classes
|
||||
# TODO: This just opens the browser and doesn't do anything?
|
||||
-cmd haxelib run munit gen
|
||||
-cmd haxelib run lime build html5
|
||||
-cmd haxelib run lime test html5
|
32
tests/unit/test.hxml
Normal file
32
tests/unit/test.hxml
Normal file
|
@ -0,0 +1,32 @@
|
|||
## CPP
|
||||
--next
|
||||
-main TestMain
|
||||
-cpp build/cpp_test
|
||||
# Funkin' deps
|
||||
-lib lime
|
||||
-lib openfl
|
||||
-lib flixel
|
||||
-lib flixel-addons
|
||||
-lib flixel-ui
|
||||
-lib hscript
|
||||
-lib polymod
|
||||
-lib haxeui-core
|
||||
-lib haxeui-flixel
|
||||
-lib flxanimate
|
||||
-lib hxCodec
|
||||
-lib thx.semver
|
||||
-lib json2object
|
||||
-lib tink_json
|
||||
# Test deps
|
||||
-lib munit
|
||||
-lib hamcrest
|
||||
-lib mcover
|
||||
-lib mockatoo
|
||||
# Class paths
|
||||
-cp source
|
||||
-cp ../../source
|
||||
# Flixel macros
|
||||
--remap flash:openfl
|
||||
--macro flixel.system.macros.FlxDefines.run()
|
||||
# Funkin' macros
|
||||
--macro addMetadata('@:build(funkin.util.macro.FlxMacro.buildFlxBasic())', 'flixel.FlxBasic')
|
24
tests/unit/test.hxml-old
Normal file
24
tests/unit/test.hxml-old
Normal file
|
@ -0,0 +1,24 @@
|
|||
## JavaScript HTML5
|
||||
--next
|
||||
-js build/js_test.js
|
||||
# Funkin' deps
|
||||
-lib lime
|
||||
-lib openfl
|
||||
-lib flixel
|
||||
-lib flixel-addons
|
||||
-lib hscript
|
||||
-lib flixel-ui
|
||||
-lib haxeui-core
|
||||
-lib haxeui-flixel
|
||||
-lib polymod
|
||||
-lib flxanimate
|
||||
-lib hxCodec
|
||||
-lib thx.semver
|
||||
-lib json2object
|
||||
-lib tink_json
|
||||
# Test deps
|
||||
-lib munit
|
||||
-lib hamcrest
|
||||
# Class paths
|
||||
-cp source
|
||||
-cp ../../source
|
Loading…
Reference in a new issue