diff --git a/docs/CNAME b/docs/CNAME deleted file mode 100644 index 3d0ec4d52..000000000 --- a/docs/CNAME +++ /dev/null @@ -1 +0,0 @@ -funkin.me \ No newline at end of file diff --git a/docs/Funkin' Debug Hotkeys.md b/docs/Funkin' Debug Hotkeys.md new file mode 100644 index 000000000..c560c5422 --- /dev/null +++ b/docs/Funkin' Debug Hotkeys.md @@ -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!) diff --git a/docs/img/skin-funkin-cardbordtoast.jpg b/docs/img/skin-funkin-cardbordtoast.jpg deleted file mode 100644 index dd0e96e60..000000000 Binary files a/docs/img/skin-funkin-cardbordtoast.jpg and /dev/null differ diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 11137c674..000000000 --- a/docs/index.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - Friday Night Funkin' - - - - - - - - - -
-
- - - Friday Night Funkin - -
-

Friday Night Funkin' - Rhythm game extraordinaire

- - -
-
- -
-
- - -
-

Friday Night Funkin' is a dope ass rhythm game.

-

It is made by ninjamuffin99 (programmer), PhantomArcade (animator), evilsk8r (artist), - and Kawaisprite (musician) originally for Ludum Dare 47.

-
- -
- Play demo on Newgrounds -
- -
- Support the game on Itch.io -
- -
- - \ No newline at end of file diff --git a/docs/readme.md b/docs/readme.md deleted file mode 100644 index aba261b5f..000000000 --- a/docs/readme.md +++ /dev/null @@ -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!! diff --git a/docs/styles.css b/docs/styles.css deleted file mode 100644 index 5ee1062fb..000000000 --- a/docs/styles.css +++ /dev/null @@ -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%; - -} diff --git a/hmm.json b/hmm.json index 52d5ffad8..150a4f242 100644 --- a/hmm.json +++ b/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" } ] } diff --git a/source/funkin/Conductor.hx b/source/funkin/Conductor.hx index 3c6470373..9bd668b69 100644 --- a/source/funkin/Conductor.hx +++ b/source/funkin/Conductor.hx @@ -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); diff --git a/source/funkin/Controls.hx b/source/funkin/Controls.hx index e8a66cb14..81055fb34 100644 --- a/source/funkin/Controls.hx +++ b/source/funkin/Controls.hx @@ -50,7 +50,7 @@ enum Control #end } -@:enum +enum abstract Action(String) to String from String { var UI_UP = "ui_up"; diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 9ad27bfcc..dd87e7d36 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -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); diff --git a/source/funkin/data/BaseRegistry.hx b/source/funkin/data/BaseRegistry.hx index 36b1d26e3..98393fda4 100644 --- a/source/funkin/data/BaseRegistry.hx +++ b/source/funkin/data/BaseRegistry.hx @@ -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 & Constructible; + /** + * 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 = 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(); } @@ -125,6 +133,13 @@ abstract class BaseRegistry & Constructible + { + 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 & Constructible; + /** + * 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 + { + // 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. diff --git a/source/funkin/data/level/LevelRegistry.hx b/source/funkin/data/level/LevelRegistry.hx index 54ed81093..36ce883ea 100644 --- a/source/funkin/data/level/LevelRegistry.hx +++ b/source/funkin/data/level/LevelRegistry.hx @@ -11,13 +11,15 @@ class LevelRegistry extends BaseRegistry * 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 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); diff --git a/source/funkin/data/notestyle/NoteStyleRegistry.hx b/source/funkin/data/notestyle/NoteStyleRegistry.hx index d666a037c..65f6f627a 100644 --- a/source/funkin/data/notestyle/NoteStyleRegistry.hx +++ b/source/funkin/data/notestyle/NoteStyleRegistry.hx @@ -11,7 +11,9 @@ class NoteStyleRegistry extends BaseRegistry * 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 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 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); diff --git a/source/funkin/freeplayStuff/DJBoyfriend.hx b/source/funkin/freeplayStuff/DJBoyfriend.hx index b109fb03f..5bee4129a 100644 --- a/source/funkin/freeplayStuff/DJBoyfriend.hx +++ b/source/funkin/freeplayStuff/DJBoyfriend.hx @@ -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: diff --git a/source/funkin/freeplayStuff/SongMenuItem.hx b/source/funkin/freeplayStuff/SongMenuItem.hx index 3d9f9dd04..37198f6d7 100644 --- a/source/funkin/freeplayStuff/SongMenuItem.hx +++ b/source/funkin/freeplayStuff/SongMenuItem.hx @@ -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); } diff --git a/source/funkin/modding/PolymodHandler.hx b/source/funkin/modding/PolymodHandler.hx index 1ff2c0caa..3f5752acc 100644 --- a/source/funkin/modding/PolymodHandler.hx +++ b/source/funkin/modding/PolymodHandler.hx @@ -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. diff --git a/source/funkin/modding/module/ModuleHandler.hx b/source/funkin/modding/module/ModuleHandler.hx index 5a1c4dd6a..3cc7b7984 100644 --- a/source/funkin/modding/module/ModuleHandler.hx +++ b/source/funkin/modding/module/ModuleHandler.hx @@ -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 diff --git a/source/funkin/play/HealthIcon.hx b/source/funkin/play/HealthIcon.hx index e4802b99c..7785fb4b1 100644 --- a/source/funkin/play/HealthIcon.hx +++ b/source/funkin/play/HealthIcon.hx @@ -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'; } } diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index a28c35c67..d01c7b049 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -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(); diff --git a/source/funkin/play/character/CharacterData.hx b/source/funkin/play/character/CharacterData.hx index 18a537a40..710eb884b 100644 --- a/source/funkin/play/character/CharacterData.hx +++ b/source/funkin/play/character/CharacterData.hx @@ -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; diff --git a/source/funkin/play/cutscene/dialogue/Conversation.hx b/source/funkin/play/cutscene/dialogue/Conversation.hx index 0816e1d25..a6851f0f9 100644 --- a/source/funkin/play/cutscene/dialogue/Conversation.hx +++ b/source/funkin/play/cutscene/dialogue/Conversation.hx @@ -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 { diff --git a/source/funkin/play/notes/notestyle/NoteStyle.hx b/source/funkin/play/notes/notestyle/NoteStyle.hx index 6cb9e7cdc..fd45342a0 100644 --- a/source/funkin/play/notes/notestyle/NoteStyle.hx +++ b/source/funkin/play/notes/notestyle/NoteStyle.hx @@ -104,6 +104,10 @@ class NoteStyle implements IRegistryEntry 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 { if (raw) { - var rawPath:Null = _data?.assets?.holdNote?.assetPath; + // TODO: figure out why ?. didn't work here + var rawPath:Null = (_data?.assets?.holdNote == null) ? null : _data?.assets?.holdNote?.assetPath; return (rawPath == null) ? fallback.getHoldNoteAssetPath(true) : rawPath; } @@ -299,6 +304,6 @@ class NoteStyle implements IRegistryEntry public function _fetchData(id:String):Null { - return NoteStyleRegistry.instance.parseEntryData(id); + return NoteStyleRegistry.instance.parseEntryDataWithMigration(id, NoteStyleRegistry.instance.fetchEntryVersion(id)); } } diff --git a/source/funkin/play/song/SongMigrator.hx b/source/funkin/play/song/SongMigrator.hx index f561c4d3e..bb8718bb7 100644 --- a/source/funkin/play/song/SongMigrator.hx +++ b/source/funkin/play/song/SongMigrator.hx @@ -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.'); diff --git a/source/funkin/play/stage/StageData.hx b/source/funkin/play/stage/StageData.hx index de8804cfb..867c6e1a5 100644 --- a/source/funkin/play/stage/StageData.hx +++ b/source/funkin/play/stage/StageData.hx @@ -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; diff --git a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx index 43e8ac96c..df5a25b62 100644 --- a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx +++ b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx @@ -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); diff --git a/source/funkin/ui/debug/charting/ChartEditorEventSprite.hx b/source/funkin/ui/debug/charting/ChartEditorEventSprite.hx index 768e0be52..2cd9ab2fe 100644 --- a/source/funkin/ui/debug/charting/ChartEditorEventSprite.hx +++ b/source/funkin/ui/debug/charting/ChartEditorEventSprite.hx @@ -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; } diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index e1ed75b06..aa6e70714 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -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; diff --git a/source/funkin/ui/story/Level.hx b/source/funkin/ui/story/Level.hx index 7913ac8ca..1e5d60bf3 100644 --- a/source/funkin/ui/story/Level.hx +++ b/source/funkin/ui/story/Level.hx @@ -181,6 +181,6 @@ class Level implements IRegistryEntry public function _fetchData(id:String):Null { - return LevelRegistry.instance.parseEntryData(id); + return LevelRegistry.instance.parseEntryDataWithMigration(id, LevelRegistry.instance.fetchEntryVersion(id)); } } diff --git a/source/funkin/util/BezierUtil.hx b/source/funkin/util/BezierUtil.hx index 56d20cf48..b3cf51d1d 100644 --- a/source/funkin/util/BezierUtil.hx +++ b/source/funkin/util/BezierUtil.hx @@ -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. diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx index c06911e4e..71cfe7b8e 100644 --- a/source/funkin/util/Constants.hx +++ b/source/funkin/util/Constants.hx @@ -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 = [0xFFFF22AA, 0xFF00EEFF, 0xFF00CC00, 0xFFCC1111]; + public static var COLOR_NOTES:Array = [ + 0xFFFF22AA, // left (0) + 0xFF00EEFF, // down (1) + 0xFF00CC00, // up (2) + 0xFFCC1111 // right (3) + ]; /** - * STAGE DEFAULTS + * GAME DEFAULTS */ // ============================== diff --git a/source/funkin/util/DateUtil.hx b/source/funkin/util/DateUtil.hx index 715371f80..72838477c 100644 --- a/source/funkin/util/DateUtil.hx +++ b/source/funkin/util/DateUtil.hx @@ -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)}'; } diff --git a/source/funkin/util/SaveDataUtil.hx b/source/funkin/util/SaveDataUtil.hx deleted file mode 100644 index 202536aad..000000000 --- a/source/funkin/util/SaveDataUtil.hx +++ /dev/null @@ -1 +0,0 @@ -package funkin.util; diff --git a/source/funkin/util/SerializerUtil.hx b/source/funkin/util/SerializerUtil.hx index d731aee8f..0075e83c0 100644 --- a/source/funkin/util/SerializerUtil.hx +++ b/source/funkin/util/SerializerUtil.hx @@ -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(input:String, type:Class):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; + } } diff --git a/source/funkin/util/VersionUtil.hx b/source/funkin/util/VersionUtil.hx index 08cde69a6..368bb12de 100644 --- a/source/funkin/util/VersionUtil.hx +++ b/source/funkin/util/VersionUtil.hx @@ -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 + { + 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; + } } diff --git a/source/funkin/util/assets/FlxAnimationUtil.hx b/source/funkin/util/assets/FlxAnimationUtil.hx index 0e32a1918..f94dc4dc3 100644 --- a/source/funkin/util/assets/FlxAnimationUtil.hx +++ b/source/funkin/util/assets/FlxAnimationUtil.hx @@ -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 diff --git a/source/funkin/util/tools/ArraySortTools.hx b/source/funkin/util/tools/ArraySortTools.hx index 3af114b98..9a3f8b349 100644 --- a/source/funkin/util/tools/ArraySortTools.hx +++ b/source/funkin/util/tools/ArraySortTools.hx @@ -108,6 +108,9 @@ class ArraySortTools input[i] = input[j]; input[j] = temp; } + + // Don't expect to get here. + return -1; } /** diff --git a/source/funkin/util/tools/MapTools.hx b/source/funkin/util/tools/MapTools.hx index 461b88bbf..739c5efdb 100644 --- a/source/funkin/util/tools/MapTools.hx +++ b/source/funkin/util/tools/MapTools.hx @@ -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(map:Map):Array { diff --git a/source/funkin/util/tools/StringTools.hx b/source/funkin/util/tools/StringTools.hx index 8ff76a080..0585ffeae 100644 --- a/source/funkin/util/tools/StringTools.hx +++ b/source/funkin/util/tools/StringTools.hx @@ -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. */ diff --git a/tests/unit/.gitignore b/tests/unit/.gitignore new file mode 100644 index 000000000..abb6c9cf9 --- /dev/null +++ b/tests/unit/.gitignore @@ -0,0 +1,7 @@ + +bin/ +# auto-generated +source/TestSuite.hx +.temp/ +report/ +build/ diff --git a/tests/unit/.munit b/tests/unit/.munit new file mode 100644 index 000000000..61de57963 --- /dev/null +++ b/tests/unit/.munit @@ -0,0 +1,7 @@ +version=2.3.5 +src=source +bin=bin +report=report +hxml=test.hxml +classPaths=../../source +coveragePackages=funkin diff --git a/tests/unit/.vscode/launch.json b/tests/unit/.vscode/launch.json new file mode 100644 index 000000000..a255f1644 --- /dev/null +++ b/tests/unit/.vscode/launch.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/tests/unit/README.md b/tests/unit/README.md new file mode 100644 index 000000000..00fed78e5 --- /dev/null +++ b/tests/unit/README.md @@ -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. diff --git a/tests/unit/assets/preload/data/anotherFile.txt b/tests/unit/assets/preload/data/anotherFile.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/assets/preload/data/levels/test.json b/tests/unit/assets/preload/data/levels/test.json new file mode 100644 index 000000000..8b004e213 --- /dev/null +++ b/tests/unit/assets/preload/data/levels/test.json @@ -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"] +} diff --git a/tests/unit/assets/preload/data/mytype/blablabla.json b/tests/unit/assets/preload/data/mytype/blablabla.json new file mode 100644 index 000000000..cf7d16746 --- /dev/null +++ b/tests/unit/assets/preload/data/mytype/blablabla.json @@ -0,0 +1,6 @@ +{ + "version": "1.0.0", + "id": "blablabla", + "name": "blablabla API", + "data": [] +} diff --git a/tests/unit/assets/preload/data/mytype/fizzbuzz.json b/tests/unit/assets/preload/data/mytype/fizzbuzz.json new file mode 100644 index 000000000..9b160ff55 --- /dev/null +++ b/tests/unit/assets/preload/data/mytype/fizzbuzz.json @@ -0,0 +1,15 @@ +{ + "version": "1.0.0", + "id": "fizzbuzz", + "name": "nodejs-express-mongoose-demo", + "data": [ + { + "foo": "bar", + "bar": "foo" + }, + { + "foo": "baz", + "bar": "baz" + } + ] +} diff --git a/tests/unit/assets/preload/data/mytype/foobar.json b/tests/unit/assets/preload/data/mytype/foobar.json new file mode 100644 index 000000000..0377a2262 --- /dev/null +++ b/tests/unit/assets/preload/data/mytype/foobar.json @@ -0,0 +1,5 @@ +{ + "version": "0.1.0", + "id": 1, + "name": "My First App" +} diff --git a/tests/unit/assets/preload/data/mytype/junk.json b/tests/unit/assets/preload/data/mytype/junk.json new file mode 100644 index 000000000..13ae5d1a0 --- /dev/null +++ b/tests/unit/assets/preload/data/mytype/junk.json @@ -0,0 +1,4 @@ +{ + "version": "3.0.0", + "info": "stuff" +} diff --git a/tests/unit/assets/preload/data/notestyles/funkin.json b/tests/unit/assets/preload/data/notestyles/funkin.json new file mode 100644 index 000000000..abb039150 --- /dev/null +++ b/tests/unit/assets/preload/data/notestyles/funkin.json @@ -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 + } + } + } +} diff --git a/tests/unit/assets/preload/data/notestyles/test2.json b/tests/unit/assets/preload/data/notestyles/test2.json new file mode 100644 index 000000000..95a669077 --- /dev/null +++ b/tests/unit/assets/preload/data/notestyles/test2.json @@ -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" } + } + } + } +} diff --git a/tests/unit/assets/preload/data/test/blablabla.json b/tests/unit/assets/preload/data/test/blablabla.json new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/assets/preload/data/test/test1.json b/tests/unit/assets/preload/data/test/test1.json new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/assets/preload/data/test/test2.json b/tests/unit/assets/preload/data/test/test2.json new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/assets/shared/images/arrows.png b/tests/unit/assets/shared/images/arrows.png new file mode 100644 index 000000000..a44368432 Binary files /dev/null and b/tests/unit/assets/shared/images/arrows.png differ diff --git a/tests/unit/assets/shared/images/arrows.xml b/tests/unit/assets/shared/images/arrows.xml new file mode 100644 index 000000000..8f3355462 --- /dev/null +++ b/tests/unit/assets/shared/images/arrows.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/unit/project.xml b/tests/unit/project.xml new file mode 100644 index 000000000..ccfadce8c --- /dev/null +++ b/tests/unit/project.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/unit/source/FunkinAssert.hx b/tests/unit/source/FunkinAssert.hx new file mode 100644 index 000000000..acfba6723 --- /dev/null +++ b/tests/unit/source/FunkinAssert.hx @@ -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(expected:Array, actual:Array, ?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(expected:Array, actual:Array, ?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); + } +} diff --git a/tests/unit/source/FunkinTest.hx b/tests/unit/source/FunkinTest.hx new file mode 100644 index 000000000..7e0e0b26c --- /dev/null +++ b/tests/unit/source/FunkinTest.hx @@ -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(); + } + } +} diff --git a/tests/unit/source/MockTest.hx b/tests/unit/source/MockTest.hx new file mode 100644 index 000000000..c1ae0a90d --- /dev/null +++ b/tests/unit/source/MockTest.hx @@ -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. + } + } +} diff --git a/tests/unit/source/TestMain.hx b/tests/unit/source/TestMain.hx new file mode 100644 index 000000000..a3fbdb428 --- /dev/null +++ b/tests/unit/source/TestMain.hx @@ -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>(); + 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) {} + } +} diff --git a/tests/unit/source/_template.hxtemplate b/tests/unit/source/_template.hxtemplate new file mode 100644 index 000000000..47a546bd0 --- /dev/null +++ b/tests/unit/source/_template.hxtemplate @@ -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() {} +} diff --git a/tests/unit/source/flixel/FlxGTest.hx b/tests/unit/source/flixel/FlxGTest.hx new file mode 100644 index 000000000..5f201a9f0 --- /dev/null +++ b/tests/unit/source/flixel/FlxGTest.hx @@ -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); + } +} diff --git a/tests/unit/source/funkin/ConductorTest.hx b/tests/unit/source/funkin/ConductorTest.hx new file mode 100644 index 000000000..856c52186 --- /dev/null +++ b/tests/unit/source/funkin/ConductorTest.hx @@ -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 = [ + { + 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 = [ + { + 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 = [ + { + 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); + } +} diff --git a/tests/unit/source/funkin/data/BaseRegistryTest.hx b/tests/unit/source/funkin/data/BaseRegistryTest.hx new file mode 100644 index 000000000..3532c4403 --- /dev/null +++ b/tests/unit/source/funkin/data/BaseRegistryTest.hx @@ -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; +} + +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 +{ + /** + * 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 + { + return MyTypeRegistry.instance.parseEntryDataWithMigration(id, MyTypeRegistry.instance.fetchEntryVersion(id)); + } + + public function getSubData():Array + { + return _data.data; + } +} + +class MyTypeRegistry extends BaseRegistry +{ + /** + * 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 + { + // JsonParser does not take type parameters, + // otherwise this function would be in BaseRegistry. + var parser = new json2object.JsonParser(); + 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 + { + // JsonParser does not take type parameters, + // otherwise this function would be in BaseRegistry. + var parser = new json2object.JsonParser(); + 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 + { + 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 + { + return []; + } +} diff --git a/tests/unit/source/funkin/data/notestyle/NoteStyleRegistryTest.hx b/tests/unit/source/funkin/data/notestyle/NoteStyleRegistryTest.hx new file mode 100644 index 000000000..ec33b37d7 --- /dev/null +++ b/tests/unit/source/funkin/data/notestyle/NoteStyleRegistryTest.hx @@ -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)); + } +} diff --git a/tests/unit/source/funkin/play/notes/notestyle/NoteStyleTest.hx b/tests/unit/source/funkin/play/notes/notestyle/NoteStyleTest.hx new file mode 100644 index 000000000..31e8e38ae --- /dev/null +++ b/tests/unit/source/funkin/play/notes/notestyle/NoteStyleTest.hx @@ -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)); + } +} diff --git a/tests/unit/source/funkin/util/BezierUtilTest.hx b/tests/unit/source/funkin/util/BezierUtilTest.hx new file mode 100644 index 000000000..90186c111 --- /dev/null +++ b/tests/unit/source/funkin/util/BezierUtilTest.hx @@ -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); + } +} diff --git a/tests/unit/source/funkin/util/ClipboardUtilTest.hx b/tests/unit/source/funkin/util/ClipboardUtilTest.hx new file mode 100644 index 000000000..9efd96d34 --- /dev/null +++ b/tests/unit/source/funkin/util/ClipboardUtilTest.hx @@ -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; + } +} diff --git a/tests/unit/source/funkin/util/DateUtilTest.hx b/tests/unit/source/funkin/util/DateUtilTest.hx new file mode 100644 index 000000000..c8adb3824 --- /dev/null +++ b/tests/unit/source/funkin/util/DateUtilTest.hx @@ -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); + } +} diff --git a/tests/unit/source/funkin/util/SerializerUtilTest.hx b/tests/unit/source/funkin/util/SerializerUtilTest.hx new file mode 100644 index 000000000..d2adc3350 --- /dev/null +++ b/tests/unit/source/funkin/util/SerializerUtilTest.hx @@ -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); + } +} diff --git a/tests/unit/source/funkin/util/SortUtilTest.hx b/tests/unit/source/funkin/util/SortUtilTest.hx new file mode 100644 index 000000000..4720c3da6 --- /dev/null +++ b/tests/unit/source/funkin/util/SortUtilTest.hx @@ -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 = ["b", "a", "c"]; + + arr.sort(SortUtil.alphabetically); + + Assert.areEqual(["a", "b", "c"], arr); + } + + @Test + public function testByZIndex() + { + var arr:Array = [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); + } +} diff --git a/tests/unit/source/funkin/util/VersionUtilTest.hx b/tests/unit/source/funkin/util/VersionUtilTest.hx new file mode 100644 index 000000000..55848955a --- /dev/null +++ b/tests/unit/source/funkin/util/VersionUtilTest.hx @@ -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()); + } +} diff --git a/tests/unit/source/funkin/util/assets/DataAssetsTest.hx b/tests/unit/source/funkin/util/assets/DataAssetsTest.hx new file mode 100644 index 000000000..7612d4706 --- /dev/null +++ b/tests/unit/source/funkin/util/assets/DataAssetsTest.hx @@ -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/')); + } +} diff --git a/tests/unit/source/funkin/util/assets/FlxAnimationUtilTest.hx b/tests/unit/source/funkin/util/assets/FlxAnimationUtilTest.hx new file mode 100644 index 000000000..990d998f2 --- /dev/null +++ b/tests/unit/source/funkin/util/assets/FlxAnimationUtilTest.hx @@ -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 = [ + { + 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(); + } +} diff --git a/tests/unit/source/funkin/util/tools/ArraySortToolsTest.hx b/tests/unit/source/funkin/util/tools/ArraySortToolsTest.hx new file mode 100644 index 000000000..0bcef00b5 --- /dev/null +++ b/tests/unit/source/funkin/util/tools/ArraySortToolsTest.hx @@ -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 = [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 = [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 = [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 = [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 = [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 = [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); + } + } +} diff --git a/tests/unit/source/funkin/util/tools/ArrayToolsTest.hx b/tests/unit/source/funkin/util/tools/ArrayToolsTest.hx new file mode 100644 index 000000000..68adb833b --- /dev/null +++ b/tests/unit/source/funkin/util/tools/ArrayToolsTest.hx @@ -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 = [1, 2, 3, 4, 5, 6, 10, 7, 8, 9, 3, 4, 5, 6, 11, 7, 8]; + + var uniqueArray:Array = 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 = ["Foo", "Bar", "HelloWorld", "Baz", "HelloTest"]; + + var result = ArrayTools.find(testArray, predicate); + + Assert.areEqual(result, "HelloWorld"); + } +} diff --git a/tests/unit/source/funkin/util/tools/IteratorToolsTest.hx b/tests/unit/source/funkin/util/tools/IteratorToolsTest.hx new file mode 100644 index 000000000..deaea352b --- /dev/null +++ b/tests/unit/source/funkin/util/tools/IteratorToolsTest.hx @@ -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++); + } +} diff --git a/tests/unit/source/funkin/util/tools/MapToolsTest.hx b/tests/unit/source/funkin/util/tools/MapToolsTest.hx new file mode 100644 index 000000000..c3fe34461 --- /dev/null +++ b/tests/unit/source/funkin/util/tools/MapToolsTest.hx @@ -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 = ["key1" => "value1", "key2" => "value2", "key3" => "value3"]; + + Assert.areEqual(3, MapTools.size(testMap)); + } + + @Test + public function testValues() + { + var testMap:Map = ["key1" => "value1", "key2" => "value2", "key3" => "value3"]; + + var result:Array = MapTools.values(testMap); + result.sort(SortUtil.alphabetically); + Assert.areEqual(["value1", "value2", "value3"], result); + } + + @Test + public function testKeyValues() + { + var testMap:Map = ["key1" => "value1", "key2" => "value2", "key3" => "value3"]; + + var result:Array = MapTools.keyValues(testMap); + result.sort(SortUtil.alphabetically); + Assert.areEqual(["key1", "key2", "key3"], result); + } +} diff --git a/tests/unit/source/funkin/util/tools/StringToolsTest.hx b/tests/unit/source/funkin/util/tools/StringToolsTest.hx new file mode 100644 index 000000000..215b5e402 --- /dev/null +++ b/tests/unit/source/funkin/util/tools/StringToolsTest.hx @@ -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)); + } +} diff --git a/tests/unit/source/import.hx b/tests/unit/source/import.hx new file mode 100644 index 000000000..89264c0c9 --- /dev/null +++ b/tests/unit/source/import.hx @@ -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; diff --git a/tests/unit/start-mac-native.sh b/tests/unit/start-mac-native.sh new file mode 100755 index 000000000..fb6c812bd --- /dev/null +++ b/tests/unit/start-mac-native.sh @@ -0,0 +1,3 @@ +#!/bin/zsh + +haxe test-cpp.hxml diff --git a/tests/unit/start-mac-web.sh b/tests/unit/start-mac-web.sh new file mode 100755 index 000000000..66af469f7 --- /dev/null +++ b/tests/unit/start-mac-web.sh @@ -0,0 +1,3 @@ +#!/bin/zsh + +haxe test-web.hxml diff --git a/tests/unit/start-win-native.bat b/tests/unit/start-win-native.bat new file mode 100644 index 000000000..7678a86ea --- /dev/null +++ b/tests/unit/start-win-native.bat @@ -0,0 +1,3 @@ +REM Launches the unit tests for the native target on Windows. + +haxe test-cpp.hxml diff --git a/tests/unit/test-cpp.hxml b/tests/unit/test-cpp.hxml new file mode 100644 index 000000000..9379f841d --- /dev/null +++ b/tests/unit/test-cpp.hxml @@ -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 diff --git a/tests/unit/test-web.hxml b/tests/unit/test-web.hxml new file mode 100644 index 000000000..c7c2ab444 --- /dev/null +++ b/tests/unit/test-web.hxml @@ -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 diff --git a/tests/unit/test.hxml b/tests/unit/test.hxml new file mode 100644 index 000000000..cece0c43a --- /dev/null +++ b/tests/unit/test.hxml @@ -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') diff --git a/tests/unit/test.hxml-old b/tests/unit/test.hxml-old new file mode 100644 index 000000000..d86661fc8 --- /dev/null +++ b/tests/unit/test.hxml-old @@ -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