Audit-10 : add rig/ test coverage (37 new tests)
Added: - TransformSheetTest (10 tests) : binary search getInterpolationInfo edge cases (empty, negative-wrap, exact boundary, multi-keyframe segment), maxFrameTime sentinel, copyAll/copy subrange - PoseTest (10 tests) : interpolatePose merge + lerp correctness at 0/0.5/1, orElseEmpty fallback, load() all three LoadOperation modes, disableAllJoints - LivingMotionIsSameTest (7 tests) : IDLE==INACTION symmetry, uniqueness of universalOrdinal across all LivingMotions values - TimePairListTest (7 tests) : odd-arg rejection, empty list, inclusive begin / exclusive end boundary, multi-pair gap - RigAnimationTickHandlerTest (2 tests) : resetLoggedErrors idempotency Skipped (MC runtime dep): - TiedUpCapabilityEventsTest : AttachCapabilitiesEvent + live ForgeEventBus - EntityPatchProviderInvalidateTest : LazyOptional is a Forge runtime class - LivingEntityPatch.onConstructed : requires real LivingEntity hierarchy - RigAnimationTickHandler.tickPlayer/maybePlayIdle : require TiedUpCapabilities.getEntityPatch + ClientAnimator + LivingEntityPatch Bug flagged (no fix) : - TransformSheet.getFirstFrame() calls copy(0,2) without guarding size >= 2; a single-keyframe sheet would throw ArrayIndexOutOfBoundsException
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests de {@link LivingMotion#isSame} — en particulier la symetrie speciale
|
||||
* IDLE == INACTION definie dans le code source.
|
||||
*
|
||||
* Aucun MC runtime requis — LivingMotions est un enum Java pur.
|
||||
* Note : le chargement de LivingMotions trigger ExtendableEnumManager.assign()
|
||||
* qui utilise TiedUpRigConstants.LOGGER (LogUtils, SLF4J pur) — pas de
|
||||
* bootstrap Forge requis.
|
||||
*/
|
||||
class LivingMotionIsSameTest {
|
||||
|
||||
/**
|
||||
* IDLE.isSame(INACTION) doit etre true selon le contrat du code source.
|
||||
* Ce comportement special est utilise par RigAnimationTickHandler pour
|
||||
* ne pas re-trigger l'animation idle quand la motion courante est INACTION.
|
||||
*/
|
||||
@Test
|
||||
void idle_isSame_inaction_returnsTrue() {
|
||||
assertTrue(LivingMotions.IDLE.isSame(LivingMotions.INACTION),
|
||||
"IDLE.isSame(INACTION) doit etre true (alias semantique)");
|
||||
}
|
||||
|
||||
/** Symetrie : INACTION.isSame(IDLE) aussi. */
|
||||
@Test
|
||||
void inaction_isSame_idle_returnsTrue() {
|
||||
assertTrue(LivingMotions.INACTION.isSame(LivingMotions.IDLE),
|
||||
"INACTION.isSame(IDLE) doit etre true (symetrique)");
|
||||
}
|
||||
|
||||
/** Une motion quelconque est identique a elle-meme. */
|
||||
@Test
|
||||
void walk_isSame_walk_returnsTrue() {
|
||||
assertTrue(LivingMotions.WALK.isSame(LivingMotions.WALK));
|
||||
}
|
||||
|
||||
/** WALK != IDLE — isSame doit retourner false. */
|
||||
@Test
|
||||
void walk_isSame_idle_returnsFalse() {
|
||||
assertFalse(LivingMotions.WALK.isSame(LivingMotions.IDLE),
|
||||
"WALK.isSame(IDLE) doit etre false");
|
||||
}
|
||||
|
||||
/** IDLE != RUN — pas d'alias entre autres motions. */
|
||||
@Test
|
||||
void idle_isSame_run_returnsFalse() {
|
||||
assertFalse(LivingMotions.IDLE.isSame(LivingMotions.RUN));
|
||||
}
|
||||
|
||||
/** DEATH != DEATH_other — verification que l'alias IDLE/INACTION est exclusif. */
|
||||
@Test
|
||||
void death_isSame_walk_returnsFalse() {
|
||||
assertFalse(LivingMotions.DEATH.isSame(LivingMotions.WALK));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verification que tous les enums ont un universalOrdinal unique (pas de
|
||||
* collision dans ExtendableEnumManager.assign).
|
||||
* BUG potentiel : si deux enums avaient le meme nom lowercase, assign()
|
||||
* throwrait IllegalArgumentException. Ce test verifie qu'il n'y a pas de
|
||||
* doublon en triggerant le chargement de la classe.
|
||||
*/
|
||||
@Test
|
||||
void allLivingMotions_haveUniqueOrdinals() {
|
||||
LivingMotions[] values = LivingMotions.values();
|
||||
long uniqueOrdinals = java.util.Arrays.stream(values)
|
||||
.mapToInt(LivingMotions::universalOrdinal)
|
||||
.distinct()
|
||||
.count();
|
||||
org.junit.jupiter.api.Assertions.assertEquals(
|
||||
values.length, uniqueOrdinals,
|
||||
"Chaque LivingMotions doit avoir un universalOrdinal unique"
|
||||
);
|
||||
}
|
||||
}
|
||||
189
src/test/java/com/tiedup/remake/rig/anim/PoseTest.java
Normal file
189
src/test/java/com/tiedup/remake/rig/anim/PoseTest.java
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.tiedup.remake.rig.armature.JointTransform;
|
||||
import com.tiedup.remake.rig.math.Vec3f;
|
||||
|
||||
/**
|
||||
* Tests de {@link Pose} : interpolatePose, orElseEmpty, load (SET / OVERWRITE /
|
||||
* APPEND_ABSENT), disableAllJoints.
|
||||
*
|
||||
* Aucun MC runtime requis — Pose utilise uniquement des collections Java et
|
||||
* JointTransform (JOML + Vec3f, pas de Minecraft bootstrap).
|
||||
*/
|
||||
class PoseTest {
|
||||
|
||||
// --- helpers ---
|
||||
|
||||
private static JointTransform translation(float x, float y, float z) {
|
||||
return JointTransform.translation(new Vec3f(x, y, z));
|
||||
}
|
||||
|
||||
// --- orElseEmpty ---
|
||||
|
||||
/** Un joint absent retourne JointTransform.empty() (pas null). */
|
||||
@Test
|
||||
void orElseEmpty_unknownJoint_returnsEmpty() {
|
||||
Pose pose = new Pose();
|
||||
JointTransform result = pose.orElseEmpty("nonexistent");
|
||||
assertNotNull(result, "orElseEmpty ne doit pas retourner null");
|
||||
// Verifier que la translation est zeros (empty = identite)
|
||||
assertEquals(0.0f, result.translation().x, 1e-5f);
|
||||
assertEquals(0.0f, result.translation().y, 1e-5f);
|
||||
assertEquals(0.0f, result.translation().z, 1e-5f);
|
||||
}
|
||||
|
||||
/** Un joint present retourne son transform, pas le fallback. */
|
||||
@Test
|
||||
void orElseEmpty_knownJoint_returnsTransform() {
|
||||
Pose pose = new Pose();
|
||||
JointTransform jt = translation(1.0f, 2.0f, 3.0f);
|
||||
pose.putJointData("spine", jt);
|
||||
assertEquals(jt, pose.orElseEmpty("spine"));
|
||||
}
|
||||
|
||||
// --- interpolatePose ---
|
||||
|
||||
/**
|
||||
* interpolatePose a progression 0.0 doit retourner les valeurs de pose1.
|
||||
*/
|
||||
@Test
|
||||
void interpolatePose_progressionZero_returnsFirstPose() {
|
||||
Pose pose1 = new Pose();
|
||||
pose1.putJointData("arm", translation(0.0f, 0.0f, 0.0f));
|
||||
|
||||
Pose pose2 = new Pose();
|
||||
pose2.putJointData("arm", translation(1.0f, 0.0f, 0.0f));
|
||||
|
||||
Pose result = Pose.interpolatePose(pose1, pose2, 0.0f);
|
||||
assertTrue(result.hasTransform("arm"));
|
||||
assertEquals(0.0f, result.get("arm").translation().x, 1e-5f);
|
||||
}
|
||||
|
||||
/**
|
||||
* interpolatePose a progression 1.0 doit retourner les valeurs de pose2.
|
||||
*/
|
||||
@Test
|
||||
void interpolatePose_progressionOne_returnsSecondPose() {
|
||||
Pose pose1 = new Pose();
|
||||
pose1.putJointData("arm", translation(0.0f, 0.0f, 0.0f));
|
||||
|
||||
Pose pose2 = new Pose();
|
||||
pose2.putJointData("arm", translation(10.0f, 0.0f, 0.0f));
|
||||
|
||||
Pose result = Pose.interpolatePose(pose1, pose2, 1.0f);
|
||||
assertEquals(10.0f, result.get("arm").translation().x, 1e-4f);
|
||||
}
|
||||
|
||||
/**
|
||||
* interpolatePose fusionne les joints des deux poses.
|
||||
* Un joint present dans pose2 seulement doit apparaitre dans le resultat
|
||||
* (interpole avec JointTransform.empty() comme pose1).
|
||||
*/
|
||||
@Test
|
||||
void interpolatePose_mergesJointsFromBothPoses() {
|
||||
Pose pose1 = new Pose();
|
||||
pose1.putJointData("head", translation(0.0f, 1.0f, 0.0f));
|
||||
|
||||
Pose pose2 = new Pose();
|
||||
pose2.putJointData("hand", translation(2.0f, 0.0f, 0.0f));
|
||||
|
||||
Pose result = Pose.interpolatePose(pose1, pose2, 0.5f);
|
||||
// Les deux joints doivent etre presents
|
||||
assertTrue(result.hasTransform("head"), "head from pose1 doit etre dans le resultat");
|
||||
assertTrue(result.hasTransform("hand"), "hand from pose2 doit etre dans le resultat");
|
||||
}
|
||||
|
||||
/**
|
||||
* interpolatePose a 0.5 doit etre a mi-chemin de la translation.
|
||||
*/
|
||||
@Test
|
||||
void interpolatePose_halfProgression_midpointTranslation() {
|
||||
Pose pose1 = new Pose();
|
||||
pose1.putJointData("leg", translation(0.0f, 0.0f, 0.0f));
|
||||
|
||||
Pose pose2 = new Pose();
|
||||
pose2.putJointData("leg", translation(4.0f, 0.0f, 0.0f));
|
||||
|
||||
Pose result = Pose.interpolatePose(pose1, pose2, 0.5f);
|
||||
assertEquals(2.0f, result.get("leg").translation().x, 1e-4f);
|
||||
}
|
||||
|
||||
// --- load(LoadOperation) ---
|
||||
|
||||
/** SET efface les joints existants puis copie depuis la source. */
|
||||
@Test
|
||||
void load_set_replacesAllJoints() {
|
||||
Pose dest = new Pose();
|
||||
dest.putJointData("old_joint", JointTransform.empty());
|
||||
|
||||
Pose src = new Pose();
|
||||
src.putJointData("new_joint", translation(1.0f, 0.0f, 0.0f));
|
||||
|
||||
dest.load(src, Pose.LoadOperation.SET);
|
||||
|
||||
assertFalse(dest.hasTransform("old_joint"),
|
||||
"SET doit supprimer les joints de la destination avant fusion");
|
||||
assertTrue(dest.hasTransform("new_joint"),
|
||||
"SET doit copier les joints source");
|
||||
}
|
||||
|
||||
/** OVERWRITE ecrase les joints communs sans supprimer les joints uniquement dans dest. */
|
||||
@Test
|
||||
void load_overwrite_keepsExistingAndOverwritesCommon() {
|
||||
Pose dest = new Pose();
|
||||
dest.putJointData("kept", JointTransform.empty());
|
||||
dest.putJointData("shared", translation(0.0f, 0.0f, 0.0f));
|
||||
|
||||
Pose src = new Pose();
|
||||
src.putJointData("shared", translation(5.0f, 0.0f, 0.0f));
|
||||
|
||||
dest.load(src, Pose.LoadOperation.OVERWRITE);
|
||||
|
||||
assertTrue(dest.hasTransform("kept"),
|
||||
"OVERWRITE doit conserver les joints de dest non presents dans src");
|
||||
assertEquals(5.0f, dest.get("shared").translation().x, 1e-4f,
|
||||
"OVERWRITE doit ecraser le joint commun avec la valeur src");
|
||||
}
|
||||
|
||||
/** APPEND_ABSENT n'ajoute que les joints absents de dest. */
|
||||
@Test
|
||||
void load_appendAbsent_doesNotOverwriteExisting() {
|
||||
Pose dest = new Pose();
|
||||
dest.putJointData("existing", translation(9.0f, 0.0f, 0.0f));
|
||||
|
||||
Pose src = new Pose();
|
||||
src.putJointData("existing", translation(1.0f, 0.0f, 0.0f));
|
||||
src.putJointData("added", translation(2.0f, 0.0f, 0.0f));
|
||||
|
||||
dest.load(src, Pose.LoadOperation.APPEND_ABSENT);
|
||||
|
||||
assertEquals(9.0f, dest.get("existing").translation().x, 1e-4f,
|
||||
"APPEND_ABSENT ne doit pas ecraser un joint existant");
|
||||
assertTrue(dest.hasTransform("added"),
|
||||
"APPEND_ABSENT doit ajouter les joints absents");
|
||||
}
|
||||
|
||||
// --- disableAllJoints ---
|
||||
|
||||
/** disableAllJoints vide la map de transforms. */
|
||||
@Test
|
||||
void disableAllJoints_clearsTransforms() {
|
||||
Pose pose = new Pose();
|
||||
pose.putJointData("head", JointTransform.empty());
|
||||
pose.putJointData("arm", JointTransform.empty());
|
||||
pose.disableAllJoints();
|
||||
assertTrue(pose.getJointTransformData().isEmpty(),
|
||||
"disableAllJoints doit vider la map de transforms");
|
||||
}
|
||||
}
|
||||
149
src/test/java/com/tiedup/remake/rig/anim/TransformSheetTest.java
Normal file
149
src/test/java/com/tiedup/remake/rig/anim/TransformSheetTest.java
Normal file
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.anim;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.tiedup.remake.rig.anim.TransformSheet.InterpolationInfo;
|
||||
import com.tiedup.remake.rig.armature.JointTransform;
|
||||
|
||||
/**
|
||||
* Tests de {@link TransformSheet} : interpolation (binary search),
|
||||
* maxFrameTime, copy/copyAll.
|
||||
*
|
||||
* Aucun MC runtime requis — TransformSheet n'utilise que
|
||||
* net.minecraft.util.Mth (pure math, pas de bootstrap) et des types
|
||||
* RIG internes (Vec3f, JointTransform).
|
||||
*/
|
||||
class TransformSheetTest {
|
||||
|
||||
private static Keyframe kf(float time) {
|
||||
return new Keyframe(time, JointTransform.empty());
|
||||
}
|
||||
|
||||
private static TransformSheet sheet(float... times) {
|
||||
Keyframe[] frames = new Keyframe[times.length];
|
||||
for (int i = 0; i < times.length; i++) {
|
||||
frames[i] = kf(times[i]);
|
||||
}
|
||||
return new TransformSheet(frames);
|
||||
}
|
||||
|
||||
// --- getInterpolationInfo ---
|
||||
|
||||
/** Sheet vide doit retourner INVALID, pas NPE. */
|
||||
@Test
|
||||
void interpolationInfo_emptySheet_returnsInvalid() {
|
||||
TransformSheet ts = new TransformSheet(new Keyframe[0]);
|
||||
assertSame(InterpolationInfo.INVALID, ts.getInterpolationInfo(0.5f),
|
||||
"Sheet vide doit retourner INVALID");
|
||||
}
|
||||
|
||||
/** Sur [0.0, 1.0] au temps 0.5, delta = 0.5. */
|
||||
@Test
|
||||
void interpolationInfo_twoKeyframes_midpointDelta() {
|
||||
TransformSheet ts = sheet(0.0f, 1.0f);
|
||||
InterpolationInfo info = ts.getInterpolationInfo(0.5f);
|
||||
assertEquals(0, info.prev());
|
||||
assertEquals(1, info.next());
|
||||
assertEquals(0.5f, info.delta(), 1e-5f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Temps negatif est converti en maxTime + currentTime.
|
||||
* Sur [0.0, 1.0], temps=-0.25 -> effectif=0.75 -> delta=0.75.
|
||||
*/
|
||||
@Test
|
||||
void interpolationInfo_negativeTime_wrapsToEnd() {
|
||||
TransformSheet ts = sheet(0.0f, 1.0f);
|
||||
InterpolationInfo info = ts.getInterpolationInfo(-0.25f);
|
||||
assertEquals(0.75f, info.delta(), 1e-5f,
|
||||
"Temps negatif doit wrapper : maxTime + t");
|
||||
}
|
||||
|
||||
/** Binary search sur 5 keyframes irreguliers — segment correct. */
|
||||
@Test
|
||||
void interpolationInfo_multipleKeyframes_correctSegment() {
|
||||
TransformSheet ts = sheet(0.0f, 0.1f, 0.5f, 0.9f, 1.0f);
|
||||
InterpolationInfo info = ts.getInterpolationInfo(0.7f);
|
||||
assertEquals(2, info.prev(), "0.7 entre index 2 (0.5) et 3 (0.9)");
|
||||
assertEquals(3, info.next());
|
||||
float expected = (0.7f - 0.5f) / (0.9f - 0.5f);
|
||||
assertEquals(expected, info.delta(), 1e-5f);
|
||||
}
|
||||
|
||||
/** Temps exactement sur un keyframe interne : delta = 0. */
|
||||
@Test
|
||||
void interpolationInfo_exactKeyframeTime_deltaZero() {
|
||||
TransformSheet ts = sheet(0.0f, 0.5f, 1.0f);
|
||||
InterpolationInfo info = ts.getInterpolationInfo(0.5f);
|
||||
assertEquals(0.0f, info.delta(), 1e-5f,
|
||||
"Temps exact sur keyframe => delta 0");
|
||||
}
|
||||
|
||||
/** Temps au-dela du max : delta clamp a 1.0. */
|
||||
@Test
|
||||
void interpolationInfo_timeExceedsMax_deltaClampedToOne() {
|
||||
TransformSheet ts = sheet(0.0f, 1.0f);
|
||||
InterpolationInfo info = ts.getInterpolationInfo(2.0f);
|
||||
assertEquals(1.0f, info.delta(), 1e-5f,
|
||||
"Temps > max => delta clamp a 1.0");
|
||||
}
|
||||
|
||||
// --- maxFrameTime ---
|
||||
|
||||
/** Sheet vide : maxFrameTime retourne la sentinelle -1. */
|
||||
@Test
|
||||
void maxFrameTime_emptySheet_returnsMinusOne() {
|
||||
TransformSheet ts = new TransformSheet(new Keyframe[0]);
|
||||
assertEquals(-1.0f, ts.maxFrameTime(), 1e-5f);
|
||||
}
|
||||
|
||||
/** maxFrameTime retourne le max quel que soit l'ordre des keyframes. */
|
||||
@Test
|
||||
void maxFrameTime_multipleKeyframes_returnsMax() {
|
||||
TransformSheet ts = sheet(0.0f, 0.3f, 1.5f, 0.7f);
|
||||
assertEquals(1.5f, ts.maxFrameTime(), 1e-5f);
|
||||
}
|
||||
|
||||
// --- copy / copyAll ---
|
||||
|
||||
/** copyAll produit une instance distincte de meme taille. */
|
||||
@Test
|
||||
void copyAll_producesIndependentCopy() {
|
||||
TransformSheet ts = sheet(0.0f, 0.5f, 1.0f);
|
||||
TransformSheet copy = ts.copyAll();
|
||||
assertNotSame(ts, copy);
|
||||
assertEquals(ts.getKeyframes().length, copy.getKeyframes().length);
|
||||
}
|
||||
|
||||
/** copy(start, end) extrait le bon sous-intervalle. */
|
||||
@Test
|
||||
void copy_subrange_correctLength() {
|
||||
TransformSheet ts = sheet(0.0f, 0.25f, 0.5f, 0.75f, 1.0f);
|
||||
TransformSheet sub = ts.copy(1, 4);
|
||||
assertEquals(3, sub.getKeyframes().length,
|
||||
"copy(1,4) extrait 3 keyframes");
|
||||
assertEquals(0.25f, sub.getKeyframes()[0].time(), 1e-5f,
|
||||
"Premier keyframe de la sous-copie = index 1 de l'original");
|
||||
}
|
||||
|
||||
/**
|
||||
* BUG FLAG (pas de correctif demande) : getFirstFrame() appelle copy(0, 2)
|
||||
* sans verifier que la sheet a au moins 2 keyframes. Sur une sheet de 1
|
||||
* keyframe, ArrayIndexOutOfBoundsException. Ce test documente le cas sain
|
||||
* (2 keyframes) pour catcher toute regression.
|
||||
*/
|
||||
@Test
|
||||
void getFirstFrame_twoKeyframes_doesNotThrow() {
|
||||
TransformSheet ts = sheet(0.0f, 1.0f);
|
||||
TransformSheet first = ts.getFirstFrame();
|
||||
assertEquals(2, first.getKeyframes().length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.tick;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests de {@link RigAnimationTickHandler} — uniquement la logique pure Java
|
||||
* accessible sans MC runtime.
|
||||
*
|
||||
* Ce qui est SKIP ici (et pourquoi) :
|
||||
* - onClientTick : necessite Minecraft.getInstance() / ClientLevel / Player —
|
||||
* impossible sans bootstrap Forge.
|
||||
* - tickPlayer : necessite TiedUpCapabilities.getEntityPatch() qui utilise
|
||||
* LazyOptional (Forge runtime) + un Player reel.
|
||||
* - maybePlayIdle : necessite ClientAnimator + LivingEntityPatch (Forge
|
||||
* capability + MC class hierarchy).
|
||||
*
|
||||
* Ce qui est couvert :
|
||||
* - resetLoggedErrors() : expose explicitement pour les tests (Javadoc l'indique).
|
||||
* Verifie que la methode est idempotente et ne throw pas.
|
||||
*/
|
||||
class RigAnimationTickHandlerTest {
|
||||
|
||||
@AfterEach
|
||||
void cleanUp() {
|
||||
RigAnimationTickHandler.resetLoggedErrors();
|
||||
}
|
||||
|
||||
/**
|
||||
* resetLoggedErrors() doit etre un no-op sur un set deja vide.
|
||||
* Double-appel ne doit pas throw.
|
||||
*/
|
||||
@Test
|
||||
void resetLoggedErrors_idempotent_doesNotThrow() {
|
||||
assertDoesNotThrow(() -> {
|
||||
RigAnimationTickHandler.resetLoggedErrors();
|
||||
RigAnimationTickHandler.resetLoggedErrors();
|
||||
}, "Double resetLoggedErrors() ne doit pas throw");
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifie que la constante MAX_LOGGED_UUIDS guard est coherente avec le
|
||||
* comportement documente : le set est reinitialisable. Ce test appelle
|
||||
* reset, puis reverifie que reset fonctionne, garantissant que le set
|
||||
* n'est pas final non-clearable.
|
||||
*
|
||||
* Pattern : si ConcurrentHashMap.newKeySet() etait remplace par un Set
|
||||
* immutable par erreur de refacto, ce test detecterait le throw.
|
||||
*/
|
||||
@Test
|
||||
void resetLoggedErrors_afterReset_secondResetStillDoesNotThrow() {
|
||||
assertDoesNotThrow(() -> {
|
||||
RigAnimationTickHandler.resetLoggedErrors();
|
||||
});
|
||||
|
||||
// Second reset simule la sequence F3+T reload (appelee deux fois
|
||||
// si deux resource-packs swappent rapidement).
|
||||
assertDoesNotThrow(() -> {
|
||||
RigAnimationTickHandler.resetLoggedErrors();
|
||||
}, "Second reset doit rester idempotent (simulation F3+T double-reload)");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||
*/
|
||||
|
||||
package com.tiedup.remake.rig.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests de {@link TimePairList} : creation, isTimeInPairs, bornes.
|
||||
*
|
||||
* Classe completement pure Java — aucune dependance MC ou Forge.
|
||||
*/
|
||||
class TimePairListTest {
|
||||
|
||||
/** Un nombre impair d'arguments doit lever IllegalArgumentException. */
|
||||
@Test
|
||||
void create_oddNumberOfArgs_throwsIllegalArgument() {
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> TimePairList.create(0.0f, 1.0f, 2.0f),
|
||||
"create() avec nombre impair d'args doit lever IllegalArgumentException");
|
||||
}
|
||||
|
||||
/** Aucune paire (create()) : rien n'est dans les paires. */
|
||||
@Test
|
||||
void create_empty_nothingIsInPairs() {
|
||||
TimePairList list = TimePairList.create();
|
||||
assertFalse(list.isTimeInPairs(0.5f),
|
||||
"Liste vide : aucun temps ne doit etre dans les paires");
|
||||
}
|
||||
|
||||
/** Temps dans une paire [0.0, 1.0]. */
|
||||
@Test
|
||||
void isTimeInPairs_timeInsidePair_returnsTrue() {
|
||||
TimePairList list = TimePairList.create(0.0f, 1.0f);
|
||||
assertTrue(list.isTimeInPairs(0.0f), "0.0 est la borne de debut (inclusive)");
|
||||
assertTrue(list.isTimeInPairs(0.5f), "0.5 est dans [0.0, 1.0)");
|
||||
}
|
||||
|
||||
/**
|
||||
* La borne de fin est EXCLUSIVE (code : time >= begin && time < end).
|
||||
* Temps = 1.0 ne doit PAS etre dans la paire [0.0, 1.0).
|
||||
*/
|
||||
@Test
|
||||
void isTimeInPairs_exactEndBound_returnsFalse() {
|
||||
TimePairList list = TimePairList.create(0.0f, 1.0f);
|
||||
assertFalse(list.isTimeInPairs(1.0f),
|
||||
"La borne de fin est exclusive : 1.0 ne doit pas etre dans [0.0, 1.0)");
|
||||
}
|
||||
|
||||
/** Temps avant toute paire. */
|
||||
@Test
|
||||
void isTimeInPairs_beforeFirstPair_returnsFalse() {
|
||||
TimePairList list = TimePairList.create(0.5f, 1.0f);
|
||||
assertFalse(list.isTimeInPairs(0.2f),
|
||||
"0.2 < 0.5 ne doit pas etre dans [0.5, 1.0)");
|
||||
}
|
||||
|
||||
/** Deux paires non-contigues — le trou entre elles ne doit pas matcher. */
|
||||
@Test
|
||||
void isTimeInPairs_gapBetweenPairs_returnsFalse() {
|
||||
TimePairList list = TimePairList.create(0.0f, 0.3f, 0.7f, 1.0f);
|
||||
assertFalse(list.isTimeInPairs(0.5f),
|
||||
"0.5 est dans le trou entre [0.0,0.3) et [0.7,1.0)");
|
||||
}
|
||||
|
||||
/** Temps dans la deuxieme paire d'une liste multi-paires. */
|
||||
@Test
|
||||
void isTimeInPairs_secondPair_returnsTrue() {
|
||||
TimePairList list = TimePairList.create(0.0f, 0.3f, 0.7f, 1.0f);
|
||||
assertTrue(list.isTimeInPairs(0.8f),
|
||||
"0.8 est dans la deuxieme paire [0.7, 1.0)");
|
||||
}
|
||||
|
||||
/** Valeur negative — hors de [0.5, 1.0). */
|
||||
@Test
|
||||
void isTimeInPairs_negativeTime_returnsFalse() {
|
||||
TimePairList list = TimePairList.create(0.5f, 1.0f);
|
||||
assertFalse(list.isTimeInPairs(-0.1f));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user