#if TEST_FRAMEWORK_INSTALLED using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using JetBrains.Annotations; using NUnit.Framework; using PrimeTween; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.TestTools; using Assert = NUnit.Framework.Assert; using AssertionException = UnityEngine.Assertions.AssertionException; using Object = UnityEngine.Object; using Random = UnityEngine.Random; using SuppressMessage = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; public partial class Tests { [Test] public void TweenTimeScaleWhileInSequence() { var t = Tween.Delay(0.01f); Sequence.Create(t); expectCantManipulateTweenInsideSequence(); t.timeScale = 2f; LogAssert.NoUnexpectedReceived(); Tween.StopAll(); } [Test] public async Task TweenTimeScaleOutlive() { { var tween = Tween.Delay(getDt() / 2); var timeScaleTween = Tween.TweenTimeScale(tween, Random.value + 0.01f, float.MaxValue); await tween; Assert.IsFalse(timeScaleTween.isAlive); } { var seq = Sequence.Create(Tween.Delay(getDt() / 2)); var timeScaleTween = Tween.TweenTimeScale(seq, Random.value + 0.01f, float.MaxValue); await seq; Assert.IsFalse(timeScaleTween.isAlive); } } [Test] public async Task TweenTimeScale() { var t = Tween.PositionZ(transform, 10, 1); const float iniTimeScale = 0.9f; t.timeScale = iniTimeScale; Assert.AreEqual(iniTimeScale, t.timeScale); const float targetTimeScale = 0.5f; await Tween.TweenTimeScale(t, targetTimeScale, 0.001f); Assert.AreEqual(targetTimeScale, t.timeScale); t.Complete(); } [UnityTest] public IEnumerator QuaternionDefaultValue() { { var q = Quaternion.Euler(0, 0, 45); var def = new Quaternion(); Assert.AreEqual(Quaternion.identity.normalized, def.normalized); Assert.AreNotEqual(Quaternion.Angle(Quaternion.identity, q), Quaternion.Angle(def, q)); Assert.AreEqual(Quaternion.Angle(Quaternion.identity, q), Quaternion.Angle(def.normalized, q)); } { Tween t = default; var def = new Quaternion(); int numCallback = 0; t = Tween.Custom(def, def, 0.01f, delegate { numCallback++; var startVal = t.tween.startValue.QuaternionVal; var endVal = t.tween.endValue.QuaternionVal; // Debug.Log($"{startVal}, {endVal}"); Assert.AreNotEqual(def, startVal); Assert.AreNotEqual(def, endVal); t.Stop(); }); yield return t.ToYieldInstruction(); Assert.AreEqual(1, numCallback); } } /// This test can fail if Game window is set to 'Play Unfocused' [UnityTest] public IEnumerator StartValueIsAppliedOnFirstFrame() { const int iniVal = -1; float val = iniVal; const int startValue = 0; Tween.Custom(startValue, 1, 0.01f, newVal => val = newVal); Assert.AreEqual(iniVal, val); yield return new WaitForEndOfFrame(); Assert.AreEqual(startValue, val); yield return new WaitForEndOfFrame(); Assert.AreNotEqual(startValue, val); } [Test] public void SafetyChecksEnabled() { #if !PRIME_TWEEN_SAFETY_CHECKS Assert.Inconclusive(); #endif } [UnityTest] public IEnumerator TweenIsDeadInOnComplete() { Tween t = default; t = Tween.Delay(0.01f, () => { Assert.IsFalse(t.isAlive); for (int i = 0; i < 6; i++) { expectIsDeadError(); } Assert.AreEqual(0, t.elapsedTime); Assert.AreEqual(0, t.elapsedTimeTotal); Assert.AreEqual(0, t.progress); Assert.AreEqual(0, t.progressTotal); Assert.AreEqual(0, t.duration); Assert.AreEqual(0, t.durationTotal); }); yield return t.ToYieldInstruction(); } static void expectIsDeadError(bool isCreated = true) => LogAssert.Expect(LogType.Error, new Regex(isCreated ? Constants.isDeadMessage : "Tween or Sequence is not created properly.")); [Test] public void ShakeDuplication1() { var s1 = Tween.ShakeLocalPosition(transform, Vector3.one, 0.1f, startDelay: 0.1f); var s2 = Tween.ShakeLocalPosition(transform, Vector3.one, 0.1f); Assert.IsTrue(s1.isAlive); Assert.IsTrue(s2.isAlive); } [UnityTest] public IEnumerator ShakeDuplication2() { var s1 = Tween.ShakeLocalPosition(transform, Vector3.one, 0.1f); Assert.IsTrue(s1.isAlive); var s2 = Tween.ShakeLocalPosition(transform, Vector3.one, 0.1f); Assert.IsTrue(s1.isAlive); yield return null; // because two shakes are started the same frame, the first one completes the second one Assert.IsTrue(s1.isAlive); Assert.IsTrue(s2.isAlive); } [UnityTest] public IEnumerator ShakeDuplication3() { var s1 = Tween.ShakeLocalPosition(transform, Vector3.one, 0.1f); yield return null; var s2 = Tween.ShakeLocalPosition(transform, Vector3.one, 0.1f); yield return null; Assert.IsTrue(s1.isAlive); Assert.IsTrue(s2.isAlive); } [UnityTest] public IEnumerator ShakeDuplication4() { const float startDelay = 0.05f; var s1 = Tween.ShakeLocalPosition(transform, Vector3.one, 0.1f, startDelay: startDelay); var s2 = Tween.ShakeLocalPosition(transform, Vector3.one, 0.1f, startDelay: 0.1f); yield return Tween.Delay(startDelay + Time.deltaTime).ToYieldInstruction(); Assert.IsTrue(s1.isAlive); Assert.IsTrue(s2.isAlive); } [UnityTest] public IEnumerator ShakeDuplication5() { var target = new GameObject(nameof(ShakeDuplication5)).transform; target.localPosition = new Vector3(Random.value, Random.value, Random.value); var iniPos = target.localPosition; var s1 = Tween.ShakeLocalPosition(target, Vector3.one, 0.1f); var seq = Sequence.Create(s1); Assert.IsTrue(s1.isAlive); var s2 = Tween.ShakeLocalPosition(target, Vector3.one, 0.1f); Assert.IsTrue(s1.isAlive); Assert.IsTrue(seq.isAlive); Assert.IsTrue(s2.isAlive); Assert.IsTrue(s1.tween.startFromCurrent); Assert.IsTrue(s2.tween.startFromCurrent); yield return null; Assert.IsTrue(s1.isAlive); Assert.IsTrue(seq.isAlive); Assert.IsTrue(s2.isAlive); Assert.IsFalse(s1.tween.startFromCurrent); Assert.IsFalse(s2.tween.startFromCurrent); Assert.AreEqual(iniPos, s1.tween.startValue.Vector3Val); Assert.AreEqual(iniPos, s2.tween.startValue.Vector3Val); } [Test] public void ShakeDuplicationDestroyedTarget() { var target = new GameObject(nameof(ShakeDuplicationDestroyedTarget)).transform; Tween.ShakeLocalPosition(target, Vector3.one, 0.1f); Object.DestroyImmediate(target.gameObject); Tween.ShakeLocalPosition(target, Vector3.one, 0.1f); expectTargetIsNull(); } static void expectTargetIsNull() => LogAssert.Expect(LogType.Error, new Regex("Tween's target is null")); [UnityTest] public IEnumerator FramePacing() { Tween.StopAll(); const int fps = 120; Application.targetFrameRate = fps; QualitySettings.vSyncCount = 0; Assert.AreEqual(fps, Application.targetFrameRate); yield return null; { var go = new GameObject(); go.AddComponent(); while (go != null) { yield return null; } } Application.targetFrameRate = targetFrameRate; yield return null; } #if UNITY_EDITOR [Test] public void GenerateCode() { const string path = "Packages/com.kyrylokuzyk.primetween/Editor/CodeGenerator.asset"; var cg = UnityEditor.AssetDatabase.LoadAssetAtPath(path); cg.generateAllMethods(); } #endif [Test] public void TweenCompleteInvokeOnCompleteParameter() { { int numCompleted = 0; var t = Tween.Scale(transform, 1.5f, 0.01f).OnComplete(() => numCompleted++); t.Complete(); Assert.AreEqual(1, numCompleted); t.Complete(); Assert.AreEqual(1, numCompleted); } /*{ int numCompleted = 0; var t = Tween.Scale(transform, 1.5f, 0.01f).OnComplete(() => numCompleted++); t.Complete(false); Assert.AreEqual(0, numCompleted); t.Complete(false); Assert.AreEqual(0, numCompleted); t.Complete(); Assert.AreEqual(0, numCompleted); }*/ } [Test] public void IgnoreFromInScale() { var t = Tween.Scale(transform, 1.5f, 0.01f); Assert.IsTrue(t.tween.startFromCurrent); } [UnityTest] public IEnumerator FromToValues() { { var duration = getDt() * Random.Range(0.5f, 1.5f); var t = Tween.Custom(0, 0, duration, ease: Ease.Linear, onValueChange: delegate { }); while (t.isAlive) { Assert.AreEqual(t.elapsedTime, t.progress * duration, 0.001f); Assert.AreEqual(t.interpolationFactor, t.progress); Assert.AreEqual(t.interpolationFactor, t.progressTotal); yield return null; } } var from = Random.value; var to = Random.value; var data = new TweenSettings(from, to, 0.01f); { var t = Tween.LocalPositionX(transform, data); Assert.AreEqual(from, t.tween.startValue.FloatVal); Assert.AreEqual(to, t.tween.endValue.FloatVal); } { var t = Tween.Custom(this, data, delegate { }); Assert.AreEqual(from, t.tween.startValue.FloatVal); Assert.AreEqual(to, t.tween.endValue.FloatVal); } } [UnityTest] public IEnumerator TweenCompleteWhenInterpolationCompleted() { float curVal = 0f; var t = Tween.Custom(this, 0f, 1f, 0.05f, (_, val) => curVal = val, cycles: 2, endDelay: 1f, cycleMode: CycleMode.Yoyo); while (t.interpolationFactor < 1f) { yield return null; } Assert.AreEqual(0, t.cyclesDone); Assert.AreEqual(1f, curVal); t.Complete(); Assert.AreEqual(0f, curVal); } [Test] public async Task CycleModeIncremental() { { float curVal = 0f; await Tween.Custom(this, 0f, 1f, 0.01f, (_, val) => curVal = val, cycles: 2, cycleMode: CycleMode.Incremental); Assert.AreEqual(2f, curVal); } { float curVal = 0f; await Tween.Custom(this, 0f, 1f, 0.01f, (_, val) => curVal = val, cycles: 4, cycleMode: CycleMode.Incremental); Assert.AreEqual(4f, curVal); } { float curVal = 0f; Tween.Custom(this, 0f, 1f, 0.01f, (_, val) => curVal = val, cycles: 2, cycleMode: CycleMode.Incremental) .Complete(); Assert.AreEqual(2f, curVal); } { float curVal = 0f; Tween.Custom(this, 0f, 1f, 0.01f, (_, val) => curVal = val, cycles: 4, cycleMode: CycleMode.Incremental) .Complete(); Assert.AreEqual(4f, curVal); } } [Test] public void TweenCompleteWithEvenCycles() { { float curVal = 0f; Tween.Custom(this, 0f, 1f, 0.05f, (_, val) => curVal = val, cycles: 2, cycleMode: CycleMode.Restart) .Complete(); Assert.AreEqual(1f, curVal); } { float curVal = 0f; Tween.Custom(this, 0f, 1f, 0.05f, (_, val) => curVal = val, cycles: 4, cycleMode: CycleMode.Restart) .Complete(); Assert.AreEqual(1f, curVal); } { float curVal = 0f; Tween.Custom(this, 0f, 1f, 0.05f, (_, val) => curVal = val, cycles: 2, cycleMode: CycleMode.Yoyo) .Complete(); Assert.AreEqual(0f, curVal); } { float curVal = 0f; Tween.Custom(this, 0f, 1f, 0.05f, (_, val) => curVal = val, cycles: 4, cycleMode: CycleMode.Yoyo) .Complete(); Assert.AreEqual(0f, curVal); } { float curVal = 0f; Tween.Custom(this, 0f, 1f, 0.05f, (_, val) => curVal = val, cycles: 2, cycleMode: CycleMode.Rewind) .Complete(); Assert.AreEqual(0f, curVal); } { float curVal = 0f; Tween.Custom(this, 0f, 1f, 0.05f, (_, val) => curVal = val, cycles: 4, cycleMode: CycleMode.Rewind) .Complete(); Assert.AreEqual(0f, curVal); } } [Test] public void TweenCompleteWithOddCycles() { { float curVal = 0f; Tween.Custom(this, 0f, 1f, 0.05f, (_, val) => curVal = val, cycles: 1, cycleMode: CycleMode.Yoyo) .Complete(); Assert.AreEqual(1f, curVal); } { float curVal = 0f; Tween.Custom(this, 0f, 1f, 0.05f, (_, val) => curVal = val, cycles: 3, cycleMode: CycleMode.Yoyo) .Complete(); Assert.AreEqual(1f, curVal); } } [UnityTest] public IEnumerator TweenOnCompleteIsCalledOnceForTweenInSequence() { for (int i = 0; i < 1; i++) { loopBegin: float curVal = 0f; int numCompleted = 0; float duration = Mathf.Max(minDuration, getDt()) * 10f; var t = Tween.Custom(this, 0f, 1f, duration, (_, val) => curVal = val, cycles: 1, cycleMode: CycleMode.Yoyo) .OnComplete(() => numCompleted++); var s = t.Chain(Tween.Delay(duration)); while (true) { if (!t.isAlive) { goto loopBegin; } if (t.cyclesDone == 1) { break; } yield return null; } Assert.IsTrue(t.isAlive); Assert.AreEqual(1, t.tween.getCyclesDone()); Assert.IsTrue(t.tween.sequence.IsCreated); Assert.AreEqual(1, numCompleted); Assert.IsTrue(s.isAlive); Assert.AreEqual(1f, curVal); s.Complete(); Assert.AreEqual(1f, curVal); Assert.AreEqual(1, numCompleted); } } [Test] public void TweenCompleteInSequence() { float curVal = 0f; var t = Tween.Custom(this, 0f, 1f, 0.05f, (_, val) => curVal = val, cycles: 1, cycleMode: CycleMode.Yoyo); var s = t.Chain(Tween.Delay(0.05f)); Assert.IsTrue(t.isAlive); Assert.AreEqual(0, t.tween.getCyclesDone()); Assert.IsTrue(t.tween.sequence.IsCreated); Assert.IsTrue(s.isAlive); Assert.AreNotEqual(1f, curVal); s.Complete(); Assert.AreEqual(1f, curVal); } [Test] public async Task AwaitExceptions() { expectTweenWasStoppedBecauseException(); await Tween.Custom(this, 0f, 1f, 1f, delegate { throw new Exception(); }); } [UnityTest] public IEnumerator CoroutineEnumeratorNotEnumeratedToTheEnd() { Tween.StopAll(); Assert.AreEqual(0, tweensCount); var t = Tween.Delay(TweenSettings.minDuration * 100); var e = t.ToYieldInstruction(); Assert.IsTrue(e.MoveNext()); yield return e.Current; Assert.IsTrue(t.isAlive); while (t.isAlive) { yield return null; } yield return null; Assert.AreEqual(0, tweensCount); testCompletedCorEnumerator(e); } [UnityTest] public IEnumerator CoroutineEnumeratorInfiniteTween() { { var t = Tween.Position(transform, Vector3.one, getDt(), cycles: -1); Tween.Delay(getDt() * 5f).OnComplete(() => t.Stop()); yield return t.ToYieldInstruction(); } { var t = Tween.Position(transform, Vector3.one, getDt(), cycles: -1); Tween.Delay(getDt() * 5f).OnComplete(() => t.Complete()); yield return t.ToYieldInstruction(); } } [UnityTest] public IEnumerator CoroutineEnumeratorMultipleToYieldInstruction() { var t = Tween.Delay(0.01f); var e = t.ToYieldInstruction(); Assert.Throws(() => t.ToYieldInstruction()); t.Complete(); yield return e; Assert.IsFalse(t.isAlive); testCompletedCorEnumerator(e); } [UnityTest] public IEnumerator CoroutineEnumeratorUsingDead() { var t = Tween.Delay(0.01f); var e = t.ToYieldInstruction(); yield return e; Assert.IsFalse(t.isAlive); testCompletedCorEnumerator(e); } static void testCompletedCorEnumerator(IEnumerator e) { Assert.IsFalse(e.MoveNext()); Assert.Throws(() => { _ = e.Current; }); Assert.Throws(() => e.Reset()); } [UnityTest] public IEnumerator YieldInstructionsClash2() { var test = new GameObject().AddComponent(); // ReSharper disable once LoopVariableIsNeverChangedInsideLoop while (test != null) { yield return null; } } [UnityTest] public IEnumerator YieldInstructionsClash() { Application.targetFrameRate = 100; yield return null; Assert.AreEqual(0, tweensCount); for (int i = 0; i < 1; i++) { { var t1 = Tween.Delay(minDuration); int frameStart = Time.frameCount; yield return t1.ToYieldInstruction(); Assert.AreEqual(1, Time.frameCount - frameStart); Assert.IsFalse(t1.isAlive); var t2 = Tween.Delay(minDuration); t2.ToYieldInstruction(); t2.Complete(); } { var t1 = Tween.Delay(minDuration); t1.ToYieldInstruction(); yield return null; yield return null; Assert.IsFalse(t1.isAlive); var t2 = Tween.Delay(minDuration); t2.ToYieldInstruction(); t2.Complete(); } } Application.targetFrameRate = targetFrameRate; } [Test] public void TweenDuplicateInSequence() { var t1 = Tween.Delay(0.1f); var t2 = Tween.Delay(0.1f); var s = t1.Chain(t2); expectNestTweenTwiceError(); s.Chain(t1); } /*[UnityTest] public IEnumerator WaitStopFromValueChange2() { Tween t = default; t = Tween.WaitWhile(this, _ => { Assert.IsTrue(t.isAlive); t.Stop(); return true; }); yield return t.ToYieldInstruction(); } [UnityTest] public IEnumerator WaitCompleteFromValueChange2() { Tween t = default; int numValueChanged = 0; t = Tween.WaitWhile(this, _ => { numValueChanged++; switch (numValueChanged) { case 1: Assert.IsTrue(t.isAlive); t.Complete(); break; case 2: Assert.IsFalse(t.isAlive); break; default: throw new Exception(); } return true; }); yield return t.ToYieldInstruction(); } [UnityTest] public IEnumerator WaitCompleteFromValueChange() { var target = new GameObject(); int numValueChanged = 0; yield return Tween.WaitWhile(target, _ => { numValueChanged++; switch (numValueChanged) { case 1: Assert.AreEqual(1, Tween.CompleteAll(target, 1, 1)); break; case 2: Assert.AreEqual(0, Tween.CompleteAll(target, 0, 0)); break; default: throw new Exception(); } return true; }).ToYieldInstruction(); } [UnityTest] public IEnumerator WaitStopFromValueChange() { var target = new GameObject(); yield return Tween.WaitWhile(target, _ => { Assert.AreEqual(1, Tween.StopAll(target, 1, 1)); return true; }).ToYieldInstruction(); } [UnityTest] public IEnumerator TweenWait() { var timeStart = Time.time; int numOnCompleteDone = 0; const float duration = 0.3f; var t = Tween.WaitWhile(this, _ => Time.time - timeStart < duration) .OnComplete(() => numOnCompleteDone++); yield return null; Assert.IsTrue(t.isAlive); yield return new WaitForSeconds(duration + 0.01f); Assert.IsFalse(t.isAlive); Assert.AreEqual(1, numOnCompleteDone); LogAssert.NoUnexpectedReceived(); } [Test] public void TweenWaitInstantComplete() { int numOnCompleteDone = 0; var t = Tween.WaitWhile(this, _ => true).OnComplete(() => numOnCompleteDone++); Assert.IsTrue(t.isAlive); t.Complete(); Assert.IsFalse(t.isAlive); Assert.AreEqual(1, numOnCompleteDone); } [Test] public void TweenWaitInstantStop() { int numOnCompleteDone = 0; var t = Tween.WaitWhile(this, _ => true).OnComplete(() => numOnCompleteDone++); Assert.IsTrue(t.isAlive); t.Stop(); Assert.IsFalse(t.isAlive); Assert.AreEqual(0, numOnCompleteDone); } [Test] public void TweenWaitException() { var t = Tween.WaitWhile(this, _ => throw new Exception()).OnComplete(Assert.Fail); Assert.IsTrue(t.isAlive); LogAssert.Expect(LogType.Error, new Regex("Tween was stopped because of exception")); LogAssert.Expect(LogType.Error, new Regex(Constants.onCompleteCallbackIgnored)); t.Complete(); Assert.IsFalse(t.isAlive); } [UnityTest] public IEnumerator TweenWaitDuration() { // Application.targetFrameRate = 60; var t = Tween.WaitWhile(this, _ => true); validate(); Assert.AreEqual(0, t.elapsedTime); Assert.AreEqual(0, t.elapsedTimeTotal); yield return null; Assert.AreNotEqual(0, t.elapsedTime); Assert.AreNotEqual(0, t.elapsedTimeTotal); // for (int i = 0; i < 60; i++) { // yield return null; // Debug.Log($"{t.elapsedTime}, {t.elapsedTimeTotal}"); // } validate(); t.Complete(); void validate() { Assert.AreEqual(-1, t.cyclesTotal); Assert.AreEqual(0, t.cyclesDone); Assert.IsTrue(float.IsPositiveInfinity(t.duration)); Assert.IsTrue(float.IsPositiveInfinity(t.durationTotal)); Assert.AreEqual(0, t.progress); Assert.AreEqual(0, t.progressTotal); Assert.AreEqual(0, t.interpolationFactor); } }*/ [Test] public void ZeroDurationWarning() { var oldSetting = PrimeTweenConfig.warnZeroDuration; try { PrimeTweenConfig.warnZeroDuration = true; LogAssert.Expect(LogType.Warning, new Regex(nameof(PrimeTweenManager.warnZeroDuration))); Tween.Custom(this, 0, 1, 0, delegate{}); PrimeTweenConfig.warnZeroDuration = false; Tween.Custom(this, 0, 1, 0, delegate{}); LogAssert.NoUnexpectedReceived(); } finally { PrimeTweenConfig.warnZeroDuration = oldSetting; } } [Test] public void CompleteTweenTwice() { int numCompleted = 0; var t = createCustomTween(1) .OnComplete(() => numCompleted++); t.Complete(); Assert.AreEqual(1, numCompleted); t.Complete(); Assert.AreEqual(1, numCompleted); } [UnityTest] public IEnumerator FromValueShouldNotChangeBetweenCycles() { var cube = GameObject.CreatePrimitive(PrimitiveType.Cube); var origPos = new Vector3(10, 10, 10); cube.transform.position = origPos; var tween = Tween.Position(cube.transform, new Vector3(20, 10, 10), 0.01f, cycles: 5); Assert.IsTrue(tween.isAlive); Assert.IsNotNull(tween.tween.getter); while (tween.isAlive) { yield return null; Assert.AreEqual(origPos, tween.tween.startValue.Vector3Val, "'From' should not change after a cycle. This can happen if tween resets startFromCurrent after a cycle."); } Object.Destroy(cube); } [Test] public void SettingIsPausedOnTweenInSequenceDisplayError() { var target = new object(); var t = Tween.Delay(target, 0.01f); Sequence.Create(t); expectError(); t.isPaused = true; expectError(); Tween.SetPausedAll(true, target); expectError(); Tween.SetPausedAll(false, target); void expectError() { expectCantManipulateTweenInsideSequence(); } } [Test] public void SettingCyclesOnDeadTweenDisplaysError() { var t = createTween(); Assert.IsTrue(t.isAlive); t.Complete(); Assert.IsFalse(t.isAlive); expectIsDeadError(); t.SetRemainingCycles(5); } [Test] public void TestDeadTween() { var t = createDeadTween(); expectError(); t.isPaused = true; t.Stop(); t.Complete(); // t.Revert(); expectError(); t.SetRemainingCycles(10); expectError(); t.OnComplete(delegate{}); expectError(); t.OnComplete(this, delegate { }); expectError(); t.timeScale = 0; void expectError() { expectIsDeadError(); } } static Tween createDeadTween() { var t = createCustomTween(0.1f); t.Complete(); Assert.IsFalse(t.isAlive); return t; } [UnityTest] public IEnumerator TweenIsPaused() { var val = 0f; var t = Tween.Custom(this, 0, 1, 1, (_, newVal) => { val = newVal; }); t.isPaused = true; yield return null; Assert.AreEqual(0, val); yield return null; Assert.AreEqual(0, val); yield return null; Assert.AreEqual(0, val); t.isPaused = false; yield return null; Assert.AreNotEqual(0, val); } [UnityTest] public IEnumerator SequenceIsPaused() { var val = 0f; var t = Tween.Custom(this, 0, 1, 1, (_, newVal) => { val = newVal; }); var s = Sequence.Create(t); s.isPaused = true; yield return null; Assert.AreEqual(0, val); yield return null; Assert.AreEqual(0, val); yield return null; Assert.AreEqual(0, val); s.isPaused = false; yield return null; Assert.AreNotEqual(0, val); } const int capacityForTest = 800; [UnityTest] public IEnumerator TweensCapacity() { Tween.StopAll(); Assert.AreEqual(0, tweensCount); PrimeTweenConfig.SetTweensCapacity(capacityForTest); Assert.AreEqual(capacityForTest, tweensCapacity); PrimeTweenConfig.SetTweensCapacity(0); Assert.AreEqual(0, tweensCapacity); LogAssert.Expect(LogType.Warning, new Regex("Please increase the capacity")); Tween.Delay(0.0001f); Tween.Delay(0.0001f); // created before set capacity PrimeTweenConfig.SetTweensCapacity(capacityForTest); Assert.AreEqual(capacityForTest, tweensCapacity); var delay = Tween.Delay(0.0001f); yield return delay.ToYieldInstruction(); // should not display warning Assert.IsFalse(delay.isAlive); yield return null; // the yielded tween is not yet released when the coroutine completes. The release will happen only in a frame Assert.AreEqual(0, tweensCount); LogAssert.NoUnexpectedReceived(); } static int tweensCapacity => PrimeTweenManager.Instance.currentPoolCapacity; [Test] public void ListResize() { Tween.StopAll(); Assert.AreEqual(0, tweensCount); var list = new List(); test(2, 2); Assert.AreNotEqual(list[0], list[1]); test(0, 2); test(10, 10); test(2, 5); test(10, 20); test(5, 30); test(6, 29); test(4, 31); test(5, 32); test(5, 31); test(4, 31); test(3, 31); test(0, 31); test(31, 31); Assert.Throws(() => test(32, 31)); test(0, 0); test(10, 10); void test(int newCount, int newCapacity) { PrimeTweenManager.resizeAndSetCapacity(list, newCount, newCapacity); Assert.AreEqual(newCount, list.Count); Assert.AreEqual(newCapacity, list.Capacity); PrimeTweenConfig.SetTweensCapacity(newCapacity); Assert.AreEqual(newCapacity, tweensCapacity); } PrimeTweenConfig.SetTweensCapacity(capacityForTest); } static ShakeSettings shakeSettings { get { if (Random.value < 0.5f) { return new ShakeSettings(Vector3.one, 1f, 10f, false); } return new ShakeSettings(Vector3.one, 1f, 10f, false, Ease.Linear); } } [UnityTest] public IEnumerator ShakeCompleteWhenStartDelayIsNotElapsed() { var target = new GameObject(nameof(ShakeCompleteWhenStartDelayIsNotElapsed)).transform; var iniPos = Random.value * Vector3.one; target.localPosition = iniPos; var t = Tween.ShakeLocalPosition(target, Vector3.one, 0.1f, startDelay: 0.1f); yield return null; Assert.AreEqual(0f, t.interpolationFactor); Assert.AreEqual(iniPos, target.localPosition); t.Complete(); Assert.AreEqual(iniPos, target.localPosition); } [UnityTest] public IEnumerator ShakeScale() { var shakeTransform = new GameObject("shake target").transform; shakeTransform.position = Vector3.one; Assert.AreEqual(shakeTransform.localScale, Vector3.one); var t = Tween.ShakeScale(shakeTransform, shakeSettings); yield return null; Assert.AreNotEqual(shakeTransform.localScale, Vector3.one); t.Complete(); Assert.IsTrue(shakeTransform.localScale == Vector3.one); Object.DestroyImmediate(shakeTransform.gameObject); } [UnityTest] public IEnumerator ShakeLocalRotation() { var shakeTransform = new GameObject("shake target").transform; shakeTransform.position = Vector3.one; Assert.AreEqual(shakeTransform.localRotation, Quaternion.identity); var t = Tween.ShakeLocalRotation(shakeTransform, shakeSettings); yield return null; Assert.AreNotEqual(shakeTransform.localRotation, Quaternion.identity); t.Complete(); Assert.IsTrue(shakeTransform.localRotation == Quaternion.identity); Object.DestroyImmediate(shakeTransform.gameObject); } [UnityTest] public IEnumerator ShakeLocalPosition() { var shakeTransform = new GameObject("shake target").transform; shakeTransform.position = Vector3.one; Assert.AreEqual(shakeTransform.position, Vector3.one); var t = Tween.ShakeLocalPosition(shakeTransform, shakeSettings); yield return null; Assert.AreNotEqual(shakeTransform.position, Vector3.one); t.Complete(); Assert.IsTrue(shakeTransform.position == Vector3.one, shakeTransform.position.ToString()); Object.DestroyImmediate(shakeTransform.gameObject); } [UnityTest] public IEnumerator ShakeCustom() { var shakeTransform = new GameObject("shake target").transform; var iniPos = Vector3.one; shakeTransform.position = iniPos; Assert.AreEqual(iniPos, shakeTransform.position); var t = Tween.ShakeCustom(shakeTransform, iniPos, shakeSettings, (target, val) => target.localPosition = val); yield return null; Assert.AreNotEqual(iniPos, shakeTransform.position); t.Complete(); Assert.IsTrue(iniPos == shakeTransform.position, iniPos.ToString()); } [UnityTest] public IEnumerator CreateShakeWhenTweenListHasNull() { Tween.StopAll(); Assert.AreEqual(0, tweensCount); Tween.Delay(0.0001f); LogAssert.Expect(LogType.Error, "Shake's strength is (0, 0, 0)."); LogAssert.Expect(LogType.Error, new Regex("Shake's frequency should be > 0f")); Tween.Delay(0.0001f) .OnComplete(() => { Assert.AreEqual(1, getNullTweensCount()); Tween.ShakeLocalPosition(transform, default).Complete(); }); yield return null; yield return null; yield return null; Assert.AreEqual(0, tweensCount); } static int getNullTweensCount() => PrimeTweenManager.Instance.tweensCount - Tween.GetTweensCount(); [UnityTest] public IEnumerator DelayNoTarget() { int numCallbackCalled = 0; var t = Tween.Delay(getDt() * 2f, () => numCallbackCalled++); Assert.AreEqual(0, numCallbackCalled); while (t.isAlive) { yield return null; } Assert.AreEqual(1, numCallbackCalled); } [UnityTest] public IEnumerator DelayFirstOverload() { int numCallbackCalled = 0; var t = Tween.Delay(this, getDt() * 3, () => numCallbackCalled++); Assert.AreEqual(0, numCallbackCalled); while (t.isAlive) { yield return null; } Assert.AreEqual(1, numCallbackCalled); } [UnityTest] public IEnumerator DelaySecondOverload() { int numCallbackCalled = 0; var t = Tween.Delay(this, getDt() * 3, _ => numCallbackCalled++); Assert.AreEqual(0, numCallbackCalled); while (t.isAlive) { yield return null; } Assert.AreEqual(1, numCallbackCalled); } [UnityTest] [SuppressMessage("ReSharper", "PossibleNullReferenceException")] public IEnumerator NewTweenCreatedFromManualOnComplete() { Tween.StopAll(); Assert.AreEqual(0, tweensCount); var t1 = createTween().OnComplete(() => createTween()); createTween(); t1.Complete(); Assert.AreEqual(3, tweensCount); checkTweensAreOrdered(); yield return null; Assert.AreEqual(2, tweensCount); checkTweensAreOrdered(); Tween.StopAll(); } static void checkTweensAreOrdered() { checkOrder(PrimeTweenManager.Instance.tweens); checkOrder(PrimeTweenManager.Instance.lateUpdateTweens); checkOrder(PrimeTweenManager.Instance.fixedUpdateTweens); void checkOrder(List tweens) { Assert.IsTrue(tweens.OrderBy(_ => _.id).SequenceEqual(tweens)); } } [UnityTest] [SuppressMessage("ReSharper", "PossibleNullReferenceException")] public IEnumerator NewTweenCreatedFromNormalOnComplete() { Tween.StopAll(); Assert.AreEqual(0, tweensCount); int numOnCompleteCalled = 0; var t1 = createCustomTween(0.01f).OnComplete(() => { numOnCompleteCalled++; createCustomTween(0.01f); createCustomTween(0.01f); createCustomTween(0.01f); }); Assert.AreEqual(1, tweensCount); checkTweensAreOrdered(); while (t1.isAlive) { yield return null; } Assert.AreEqual(1, numOnCompleteCalled); Assert.AreEqual(3, tweensCount); checkTweensAreOrdered(); } [UnityTest] public IEnumerator SetAllPaused() { if (tweensCount != 0) { var aliveCount = Tween.GetTweensCount(); Assert.AreEqual(Tween.StopAll(null), aliveCount); } Assert.AreEqual(0, tweensCount); const int count = 10; var tweens = new List(); for (int i = 0; i < count; i++) { tweens.Add(createCustomTween(1)); } Assert.IsTrue(tweens.All(_ => !_.isPaused)); Assert.AreEqual(Tween.SetPausedAll(true, null), count); Assert.IsTrue(tweens.All(_ => _.isPaused)); Assert.AreEqual(Tween.SetPausedAll(false, null), count); Assert.IsTrue(tweens.All(_ => !_.isPaused)); Assert.IsTrue(tweens.All(_ => _.isAlive)); yield return null; Assert.IsTrue(tweens.All(_ => _.isAlive)); foreach (var _ in tweens) { _.Complete(); } Assert.IsFalse(tweens.All(_ => _.isAlive)); } [UnityTest] public IEnumerator StopAllCalledFromOnValueChange() { Tween.StopAll(); Assert.AreEqual(0, tweensCount); int numOnValueChangeCalled = 0; var t = Tween.Custom(this, 0, 1, 1f, delegate { Assert.AreEqual(0, numOnValueChangeCalled); numOnValueChangeCalled++; var numStopped = Tween.StopAll(this); Assert.AreEqual(1, numStopped); }); Assert.IsTrue(t.isAlive); yield return null; yield return null; Assert.IsFalse(t.isAlive); LogAssert.NoUnexpectedReceived(); } [UnityTest] public IEnumerator RecursiveCompleteCallFromOnValueChange() { int numOnValueChangeCalled = 0; int numOnCompleteCalled = 0; Tween t = default; t = Tween.Custom(this, 0, 1, 1f, delegate { // Debug.Log(val); numOnValueChangeCalled++; Assert.IsTrue(numOnValueChangeCalled <= 2); t.Complete(); }).OnComplete(() => numOnCompleteCalled++); Assert.IsTrue(t.isAlive); while (t.isAlive) { yield return null; } Assert.IsFalse(t.isAlive); Assert.AreEqual(1, numOnCompleteCalled); LogAssert.NoUnexpectedReceived(); } [UnityTest] public IEnumerator RecursiveCompleteAllCallFromOnValueChange() { Tween.StopAll(); Assert.AreEqual(0, tweensCount); int numOnValueChangeCalled = 0; int numOnCompleteCalled = 0; var t = Tween.Custom(this, 0, 1, 1f, delegate { // Debug.Log(val); numOnValueChangeCalled++; switch (numOnValueChangeCalled) { case 1: { var numCompleted = Tween.CompleteAll(this); Assert.AreEqual(1, numCompleted); break; } case 2: { var numCompleted = Tween.CompleteAll(this); Assert.AreEqual(0, numCompleted); break; } default: throw new Exception(); } }).OnComplete(() => numOnCompleteCalled++); Assert.IsTrue(t.isAlive); while (t.isAlive) { yield return null; } Assert.IsFalse(t.isAlive); Assert.AreEqual(1, numOnCompleteCalled); yield return null; Assert.AreEqual(0, tweensCount); Assert.AreEqual(2, numOnValueChangeCalled); LogAssert.NoUnexpectedReceived(); } [UnityTest] public IEnumerator RecursiveCompleteCallFromOnComplete() { Tween.StopAll(); Assert.AreEqual(0, tweensCount); int numOnCompleteCalled = 0; Tween t = default; t = Tween.Custom(this, 0, 1, minDuration, delegate { }).OnComplete(() => { numOnCompleteCalled++; t.Complete(); }); Assert.IsTrue(t.isAlive); while (t.isAlive) { yield return null; } Assert.IsFalse(t.isAlive); Assert.AreEqual(1, numOnCompleteCalled); yield return null; Assert.AreEqual(0, tweensCount); } [UnityTest] public IEnumerator RecursiveCompleteAllCallFromOnComplete() { Tween.StopAll(); Assert.AreEqual(0, tweensCount); int numOnCompleteCalled = 0; var t = Tween.Custom(this, 0, 1, minDuration, delegate { }).OnComplete(() => { numOnCompleteCalled++; var numCompleted = Tween.CompleteAll(this); Assert.AreEqual(0, numCompleted); }); Assert.IsTrue(t.isAlive); while (t.isAlive) { yield return null; } Assert.IsFalse(t.isAlive); Assert.AreEqual(1, numOnCompleteCalled); yield return null; Assert.AreEqual(0, tweensCount); } [UnityTest] public IEnumerator StopAllCalledFromOnValueChange2() { int numOnValChangedCalled = 0; var t = Tween.Custom(this, 0, 1, 0.0001f, (_, val) => { // Debug.Log(val); Assert.AreEqual(0, val); Assert.AreEqual(0, numOnValChangedCalled); numOnValChangedCalled++; var numStopped = Tween.StopAll(this); Assert.AreEqual(1, numStopped); }); Assert.IsTrue(t.isAlive); yield return null; Assert.IsFalse(t.isAlive); Assert.AreEqual(1, numOnValChangedCalled); } [UnityTest] public IEnumerator TweenCanBeNullInProcessAllMethod() { Assert.AreEqual(0, tweensCount); Tween.Custom(this, 0, 1, 0.0001f, delegate { // Debug.Log($"t1 val {val}"); }); Tween.Custom(this, 0, 1, 0.0001f, delegate { // Debug.Log($"t2 val {val}"); Assert.AreEqual(0, getNullTweensCount()); Assert.AreEqual(Tween.StopAll(this), 2); }); yield return null; LogAssert.NoUnexpectedReceived(); } [UnityTest] public IEnumerator TweenCanBeNullInOnComplete() { Tween.StopAll(); Assert.AreEqual(0, tweensCount); int numOnCompleteCalled = 0; Tween.Custom(this, 0, 1, 0.0001f, delegate{}); Tween.Custom(this, 0, 1, 0.0001f, delegate { }).OnComplete(() => { numOnCompleteCalled++; Assert.AreEqual(1, getNullTweensCount()); var numStopped = Tween.StopAll(this); Assert.AreEqual(0, numStopped); }); yield return null; Assert.AreEqual(1, numOnCompleteCalled); LogAssert.NoUnexpectedReceived(); } [UnityTest] public IEnumerator TweenShouldBeDeadInOnValueChangeAfterCallingComplete() { // Debug.Log(nameof(TweenShouldBeDeadInOnValueChangeAfterCallingComplete)); var target = new GameObject(nameof(TweenShouldBeDeadInOnValueChangeAfterCallingComplete)); int numOnValueChangeCalled = 0; Tween t = default; t = Tween.Custom(target, 0, 1, minDuration, (_, val) => { // Debug.Log(val); Assert.IsTrue(val == 0 || val == 1); numOnValueChangeCalled++; switch (numOnValueChangeCalled) { case 1: Assert.IsTrue(t.isAlive); if (Random.value < 0.5f) { t.Complete(); } else { Assert.AreEqual(Tween.CompleteAll(target), 1); } break; case 2: // when Complete() is called, it's expected that onValueChange will be reported once again break; default: throw new Exception(); } }); Assert.AreEqual(1, Tween.SetPausedAll(true, target)); Assert.AreEqual(1, Tween.SetPausedAll(false, target)); yield return null; Assert.IsFalse(t.isAlive); Assert.AreEqual(2, numOnValueChangeCalled); } [Test] public void NumProcessed() { Tween.StopAll(); Assert.AreEqual(0, tweensCount); var target1 = new object(); var target2 = new object(); createWithTarget1(); // 1 createWithTarget1(); // 2 createWithTarget1(); // 3 createWithTarget2(); // 1 createWithTarget2(); // 2 createWithTarget1(); // 4 createWithTarget2(); // 3 Assert.AreEqual(4, Tween.SetPausedAll(true, target1)); Assert.AreEqual(4, Tween.SetPausedAll(false, target1)); Assert.AreEqual(4, Tween.StopAll(target1)); Assert.AreEqual(0, Tween.StopAll(target1)); Assert.AreEqual(0, Tween.CompleteAll(target1)); Assert.AreEqual(0, Tween.SetPausedAll(true, target1)); Assert.AreEqual(3, Tween.SetPausedAll(true, target2)); Assert.AreEqual(3, Tween.SetPausedAll(false, target2)); Assert.AreEqual(3, Tween.CompleteAll(target2)); Assert.AreEqual(0, Tween.CompleteAll(target2)); Assert.AreEqual(0, Tween.StopAll(target2)); void createWithTarget1() => Tween.Custom(target1, 0, 1, 0.0001f, delegate { }); void createWithTarget2() => Tween.Custom(target2, 0, 1, 0.0001f, delegate { }); } [UnityTest] public IEnumerator TweenIsAliveForWholeDuration() { int numOnValueChangedCalled = 0; int numOnValueChangedCalledAfterComplete = 0; Tween t = default; var target = new object(); bool isCompleteCalled = false; const float duration = 0.3f; t = Tween.Custom(target, 0, 1, duration, (_, val) => { // Debug.Log(val); numOnValueChangedCalled++; if (isCompleteCalled) { numOnValueChangedCalledAfterComplete++; } Assert.AreEqual(!isCompleteCalled, t.isAlive); if (val > duration / 2) { isCompleteCalled = true; t.Complete(); } }).OnComplete(() => { Assert.IsTrue(t.IsCreated); Assert.IsFalse(t.isAlive); Assert.AreEqual(0, Tween.StopAll(target)); }); while (t.isAlive) { yield return null; } Assert.IsTrue(numOnValueChangedCalled > 1); Assert.AreEqual(1, numOnValueChangedCalledAfterComplete); } [Test] public void SetPauseAll() { var target = new object(); var t = Tween.Custom(target, 0, 1, 1, delegate{}); Assert.AreEqual(0, Tween.SetPausedAll(false, target)); Assert.AreEqual(1, Tween.SetPausedAll(true, target)); Assert.AreEqual(0, Tween.SetPausedAll(true, target)); Assert.AreEqual(1, Tween.SetPausedAll(false, target)); Assert.AreEqual(0, Tween.SetPausedAll(false, target)); t.Stop(); Assert.AreEqual(0, Tween.SetPausedAll(true, target)); } [UnityTest] public IEnumerator StopByTargetFromOnValueChange() { var target = new GameObject(); int numOnValueChangeCalled = 0; var t = Tween.Custom(target, 0, 1, 1, delegate { numOnValueChangeCalled++; var numStopped = Tween.StopAll(target); Assert.AreEqual(1, numStopped); }); Assert.AreEqual(0, numOnValueChangeCalled); Assert.IsTrue(t.isAlive); yield return null; Assert.IsFalse(t.isAlive); Assert.AreEqual(1, numOnValueChangeCalled); } [UnityTest] public IEnumerator TweenPropertiesDefault() { if (tweensCount != 0) { Tween.StopAll(); Assert.AreEqual(0, tweensCount); } { yield return Tween.Delay(0.001f).ToYieldInstruction(); Assert.AreEqual(1, tweensCount); yield return null; Assert.AreEqual(0, tweensCount); } { var t = Tween.Delay(0f); Assert.IsTrue(t.isAlive); validate(t, true); } { var t = new Tween(); Assert.IsFalse(t.isAlive); expectError(false); Assert.AreEqual(0, t.cyclesTotal); validate(t, false, false); } { var t = Tween.Delay(1); t.Complete(); Assert.IsFalse(t.isAlive); expectError(); Assert.AreEqual(0, t.cyclesTotal); validate(t, false); } { var t = Tween.Delay(1); t.Stop(); Assert.IsFalse(t.isAlive); expectError(); Assert.AreEqual(0, t.cyclesTotal); validate(t, false); } void validate(Tween t, bool isAlive, bool isCreated = true) { if (!isAlive) { for (int i = 0; i < 8; i++) { expectError(isCreated); } } Assert.AreEqual(0, t.elapsedTime); Assert.AreEqual(0, t.elapsedTimeTotal); Assert.AreEqual(0, t.cyclesDone); Assert.AreEqual(0, t.duration); Assert.AreEqual(0, t.durationTotal); Assert.AreEqual(0, t.progress); Assert.AreEqual(0, t.progressTotal); Assert.AreEqual(0, t.interpolationFactor); if (!isAlive) { expectError(isCreated); } Assert.AreEqual(1, t.timeScale); } { const float duration = 0.123f; var t = Tween.PositionY(transform, 0, duration, Ease.Linear, -1); Assert.AreEqual(duration, t.duration); Assert.IsTrue(float.IsPositiveInfinity(t.durationTotal)); Assert.AreEqual(0, t.progress); Assert.AreEqual(0, t.progressTotal); t.Stop(); validate(t, false); } void expectError(bool isCreated = true) { expectIsDeadError(isCreated); } } [UnityTest] public IEnumerator TweenProperties() { float duration = Mathf.Max(minDuration, getDt() * Random.Range(0.5f, 5f)); int numCyclesExpected = Random.Range(1, 3); Tween t = default; float startDelay = getDt() * Random.Range(0.1f, 1.2f); float endDelay = getDt() * Random.Range(0.1f, 1.2f); float durationExpected = startDelay + duration + endDelay; float totalDurationExpected = durationExpected * numCyclesExpected; float timeStart = Time.time; t = Tween.Custom(this, 1f, 2f, duration, ease: Ease.Linear, cycles: numCyclesExpected, startDelay: startDelay, endDelay: endDelay, onValueChange: (_, val) => { val -= 1f; var elapsedTimeTotalExpected = Time.time - timeStart; var elapsedTimeExpected = elapsedTimeTotalExpected - durationExpected * t.cyclesDone; // Debug.Log($"val: {val}, progress: {t.progress}, elapsedTimeExpected: {elapsedTimeExpected}, elapsedTimeTotalExpected: {elapsedTimeTotalExpected}"); const float tolerance = 0.001f; if (val < 1) { Assert.AreEqual(elapsedTimeExpected, t.elapsedTime, tolerance); Assert.AreEqual(elapsedTimeTotalExpected, t.elapsedTimeTotal, tolerance, $"val: {val},duration: {duration}, numCyclesExpected: {numCyclesExpected}"); Assert.AreEqual(Mathf.Min(elapsedTimeTotalExpected / totalDurationExpected, 1f), t.progressTotal, tolerance); Assert.AreEqual(elapsedTimeExpected / durationExpected, t.progress, tolerance); } Assert.AreEqual(numCyclesExpected, t.cyclesTotal); Assert.AreEqual(durationExpected, t.duration); Assert.AreEqual(totalDurationExpected, t.durationTotal); Assert.AreEqual(t.interpolationFactor, val, tolerance); }); yield return t.ToYieldInstruction(); Assert.IsFalse(t.isAlive); for (int i = 0; i < 2; i++) { expectIsDeadError(); } Assert.AreEqual(0, t.progress); Assert.AreEqual(0, t.progressTotal); var infT = Tween.Position(transform, Vector3.one, minDuration, cycles: -1); Assert.IsTrue(infT.isAlive); Assert.AreEqual(-1, infT.cyclesTotal); infT.Complete(); } [UnityTest] public IEnumerator ZeroDurationOnTweenShouldReportValueAtLeastOnce() { Tween.StopAll(); Assert.AreEqual(0, tweensCount); Assert.AreEqual(capacityForTest, tweensCapacity); const float p1 = 0.345f; Tween.PositionZ(transform, 0, p1, 0f).Complete(); UnityEngine.Assertions.Assert.AreApproximatelyEqual(p1, transform.position.z); const float p2 = 0.123f; Tween.PositionZ(transform, p2, 0).Complete(); UnityEngine.Assertions.Assert.AreApproximatelyEqual(p2, transform.position.z); const float p3 = 0.456f; Tween.PositionZ(transform, p3, 0); yield return null; UnityEngine.Assertions.Assert.AreApproximatelyEqual(p3, transform.position.z); yield return Tween.PositionZ(transform, p1, 0).OnComplete(() => { }).ToYieldInstruction(); UnityEngine.Assertions.Assert.AreApproximatelyEqual(p1, transform.position.z); } [UnityTest] public IEnumerator OneShouldBeReportedExactlyOnce() { int numOneReported = 0; const int cycles = 1; for (int i = 0; i < 1; i++) { numOneReported = 0; yield return Tween.Custom(this, 0, 1, getDt() * Random.Range(0.5f, 1.5f), startDelay: getDt() * Random.Range(0.1f, 1.1f), endDelay: getDt() * Random.Range(0.5f, 3f), cycles: cycles, onValueChange: (_, val) => { // print($"val: {val}"); if (val == 1f) { numOneReported++; } }).ToYieldInstruction(); Assert.AreEqual(cycles, numOneReported); } numOneReported = 0; yield return Tween.Custom(this, 0, 1, 0f, startDelay: getDt() * Random.Range(0.1f, 1.1f), endDelay: getDt() * Random.Range(0.1f, 1.1f), cycles: cycles, onValueChange: (_, val) => { if (val == 1) { numOneReported++; } }).ToYieldInstruction(); Assert.AreEqual(cycles, numOneReported); numOneReported = 0; yield return Tween.Custom(this, 0, 1, 0f, cycles: cycles, onValueChange: (_, val) => { if (val == 1) { numOneReported++; } }).ToYieldInstruction(); Assert.AreEqual(cycles, numOneReported); numOneReported = 0; yield return Tween.Custom(this, 0, 1, 0f, (_, val) => { if (val == 1) { numOneReported++; } }).ToYieldInstruction(); Assert.AreEqual(1, numOneReported); yield return Tween.PositionY(transform, 3.14f, Mathf.Max(minDuration, getDt() * Random.Range(0.1f, 1.1f))).ToYieldInstruction(); yield return Tween.PositionY(transform, 3.14f, 0f).ToYieldInstruction(); yield return Tween.PositionY(transform, 0, 3.14f, 0f).ToYieldInstruction(); } [UnityTest] public IEnumerator SingleFrameTween() { Application.targetFrameRate = 200; for (int i = 0; i < 1; i++) { int numOnValueChangeCalled = 0; yield return Tween.Custom(this, 0, 1, 0.0001f, (_, val) => { numOnValueChangeCalled++; Assert.IsTrue(val == 0 || val == 1); }).ToYieldInstruction(); Assert.AreEqual(2, numOnValueChangeCalled); } Application.targetFrameRate = targetFrameRate; } [UnityTest] public IEnumerator TweensWithDurationOfDeltaTime() { for (int i = 0; i < 1; i++) { var go = new GameObject(); go.AddComponent(); // ReSharper disable once LoopVariableIsNeverChangedInsideLoop while (go != null) { yield return null; } } } [UnityTest] public IEnumerator TweenWithExactDurationOfDeltaTime1() { yield return Tween.Delay(this, Time.deltaTime).ToYieldInstruction(); } [UnityTest] public IEnumerator TweenWithExactDurationOfDeltaTime2() { int numOnCompleteCalled = 0; yield return Tween.Delay(this, Time.deltaTime, () => numOnCompleteCalled++).ToYieldInstruction(); Assert.AreEqual(1, numOnCompleteCalled); } [Test] public void TotalDurationWithCycles() { var duration = Random.value; var startDelay = Random.value; var endDelay = Random.value; var cycles = Random.Range(1, 10); var t = Tween.LocalPositionY(transform, new TweenSettings(0, 1, duration, cycles: cycles, startDelay: startDelay, endDelay: endDelay)); var durationTotalExpected = duration + startDelay + endDelay; const float tolerance = 0.0001f; Assert.AreEqual(durationTotalExpected, t.duration, tolerance); Assert.AreEqual(durationTotalExpected * cycles, t.durationTotal, tolerance); Assert.AreEqual(durationTotalExpected * cycles, t.durationTotal, tolerance); t.Complete(); } [Test] public void DurationWithWaitDependencies() { var t1Dur = Random.value; var t1Cycles = Random.Range(1, 20); var t2Dur = Random.value; var t2Cycles = Random.Range(1, 20); // const int t1Dur = 1; // const int t1Cycles = 2; // const int t2Dur = 2; // const int t2Cycles = 2; var t1 = Tween.LocalPositionX(transform, 1, t1Dur, cycles: t1Cycles); var t2 = Tween.LocalPositionX(transform, 1, t2Dur, cycles: t2Cycles); var s = t1.Chain(t2); Assert.IsTrue(t1.isAlive); Assert.IsTrue(t2.isAlive); Assert.AreEqual(t1Dur * t1Cycles, t1.durationTotal); Assert.AreEqual(t2Dur * t2Cycles, t2.durationTotal); Assert.AreEqual(t1Dur * t1Cycles, t1.durationWithWaitDelay); Assert.AreEqual(t1Dur * t1Cycles + t2Dur * t2Cycles, t2.durationWithWaitDelay, 0.001f); s.Complete(); } [Test] public void AwaitingDeadCompletesImmediately() { bool isCompleted = false; AwaitingDeadCompletesImmediatelyAsync(() => isCompleted = true); Assert.IsTrue(isCompleted); } static async void AwaitingDeadCompletesImmediatelyAsync([NotNull] Action callback) { var frame = Time.frameCount; await new Tween(); await new Sequence(); Assert.AreEqual(frame, Time.frameCount); callback(); } [UnityTest] public IEnumerator TestAwaitByCallback() { bool isCompleted = false; var t = Tween.Delay(getDt() * 5f); waitForTweenAsync(t, () => isCompleted = true); Assert.IsFalse(isCompleted); yield return null; Assert.IsFalse(isCompleted); yield return t.ToYieldInstruction(); Assert.IsTrue(isCompleted); } static async void waitForTweenAsync(Tween tween, [NotNull] Action callback) { await tween; callback(); } [Test] public async Task AwaitTweenWithCallback() { bool isCompleted = false; var t = Tween.Delay(getDt() * 2f,() => isCompleted = true); Assert.IsTrue(t.isAlive); Assert.IsTrue(t.tween.HasOnComplete); await t; Assert.IsFalse(t.isAlive); Assert.IsTrue(isCompleted); } const float minDuration = TweenSettings.minDuration; [Test] public async Task AwaitTweenWithCallbackDoesntPostpone() { bool isCompleted = false; var t = Tween.Delay(minDuration,() => isCompleted = true); Assert.IsTrue(t.isAlive); Assert.IsTrue(t.tween.HasOnComplete); var frameStart = Time.frameCount; await t; Assert.AreEqual(1, Time.frameCount - frameStart); Assert.IsFalse(t.isAlive); Assert.IsTrue(isCompleted); } [Test] public async Task AwaitSequence() { bool isCompleted1 = false; bool isCompleted2 = false; await Sequence.Create(Tween.Delay(0.01f, () => isCompleted1 = true)) .Chain(Tween.Delay(0.02f, () => isCompleted2 = true)); Assert.IsTrue(isCompleted1); Assert.IsTrue(isCompleted2); } [Test] public async Task AwaitSequence2() { var t1 = Tween.Delay(getDt()); var t2 = Tween.Delay(getDt()); await t1.Chain(t2); Assert.IsFalse(t1.isAlive); Assert.IsFalse(t2.isAlive); } [UnityTest] public IEnumerator ToYieldInstruction() { var t = Tween.Delay(0.1f); var e = t.ToYieldInstruction(); var frameStart = Time.frameCount; while (e.MoveNext()) { yield return e.Current; t.Complete(); } Assert.AreEqual(1, Time.frameCount - frameStart); Assert.IsFalse(t.isAlive); yield return t.ToYieldInstruction(); Tween defaultTween = default; defaultTween.ToYieldInstruction(); Sequence defaultSequence = default; defaultSequence.ToYieldInstruction(); t.Complete(); } [UnityTest] public IEnumerator ImplicitConversionToIterator() { PrimeTweenConfig.warnStructBoxingAllocationInCoroutine = true; { var t2 = Tween.Delay(0.0001f); var frameStart = Time.frameCount; expectCoroutineBoxingWarning(); yield return t2; Assert.AreEqual(1, Time.frameCount - frameStart); Assert.IsFalse(t2.isAlive); } { var s = Sequence.Create(Tween.Delay(0.0001f)); var frameStart = Time.frameCount; // iterator boxing warning is shown only once yield return s; Assert.AreEqual(1, Time.frameCount - frameStart); Assert.IsFalse(s.isAlive); } LogAssert.NoUnexpectedReceived(); } [Test] public async Task AwaitInfiniteTweenComplete() { Tween t = default; int numCompleted = 0; t = Tween.Custom(this, 0, 1, 1, cycles: -1, onValueChange: delegate { t.Complete(); }) .OnComplete(() => numCompleted++); await t; Assert.AreEqual(1, numCompleted); } [Test] public async Task AwaitInfiniteTweenStop() { Tween t = default; int numOnValueChanged = 0; t = Tween.Custom(this, 0, 1, 1f, cycles: -1, onValueChange: delegate { // Debug.Log(numOnValueChanged); numOnValueChanged++; Assert.AreEqual(1, numOnValueChanged); Assert.IsTrue(t.isAlive); t.Stop(); }); Assert.IsTrue(t.isAlive); await t; Assert.IsFalse(t.isAlive); Assert.AreEqual(1, numOnValueChanged); } [Test] public async Task TweenStoppedTweenWhileAwaiting() { var t = Tween.Delay(0.05f); #pragma warning disable CS4014 Tween.Delay(0.01f).OnComplete(() => t.Stop()); #pragma warning restore CS4014 Assert.IsTrue(t.isAlive); await t; Assert.IsFalse(t.isAlive); } [Test] public void InvalidDurations() { Assert.Throws(() => Tween.Delay(float.NaN), Constants.durationInvalidError); Assert.Throws(() => Tween.Delay(float.PositiveInfinity), Constants.durationInvalidError); Assert.Throws(() => Tween.Delay(float.NegativeInfinity), Constants.durationInvalidError); Assert.Throws(() => Tween.PositionZ(transform, new TweenSettings(0, 1, new TweenSettings(1, startDelay: float.NaN))), Constants.durationInvalidError); Assert.Throws(() => Tween.PositionZ(transform, new TweenSettings(0, 1, new TweenSettings(1, startDelay: float.PositiveInfinity))), Constants.durationInvalidError); Assert.Throws(() => Tween.PositionZ(transform, new TweenSettings(0, 1, new TweenSettings(1, startDelay: float.NegativeInfinity))), Constants.durationInvalidError); Assert.Throws(() => Tween.PositionZ(transform, new TweenSettings(0, 1, new TweenSettings(1, endDelay: float.NaN))), Constants.durationInvalidError); Assert.Throws(() => Tween.PositionZ(transform, new TweenSettings(0, 1, new TweenSettings(1, endDelay: float.PositiveInfinity))), Constants.durationInvalidError); Assert.Throws(() => Tween.PositionZ(transform, new TweenSettings(0, 1, new TweenSettings(1, endDelay: float.NegativeInfinity))), Constants.durationInvalidError); } [Test] public void MaterialTweens() { { var s = Shader.Find("Standard"); if (s == null) { Assert.Ignore(); return; } var m = new Material(s); { const string propName = "_EmissionColor"; #if UNITY_2021_1_OR_NEWER Assert.IsTrue(m.HasColor(propName)); #endif var to = Color.red; Tween.MaterialColor(m, Shader.PropertyToID(propName), to, 1f).Complete(); Assert.AreEqual(to, m.GetColor(propName)); } { const string propName = "_EmissionColor"; #if UNITY_2021_1_OR_NEWER Assert.IsTrue(m.HasColor(propName)); #endif var iniColor = new Color(Random.value, Random.value, Random.value, Random.value); m.SetColor(propName, iniColor); var toAlpha = Random.value; Tween.MaterialAlpha(m, Shader.PropertyToID(propName), toAlpha, 1f).Complete(); var col = m.GetColor(propName); UnityEngine.Assertions.Assert.AreApproximatelyEqual(col.r, iniColor.r); UnityEngine.Assertions.Assert.AreApproximatelyEqual(col.g, iniColor.g); UnityEngine.Assertions.Assert.AreApproximatelyEqual(col.b, iniColor.b); UnityEngine.Assertions.Assert.AreApproximatelyEqual(col.a, toAlpha); } { const string propName = "_Cutoff"; #if UNITY_2021_1_OR_NEWER Assert.IsTrue(m.HasFloat(propName)); #endif var to = Random.value; Tween.MaterialProperty(m, Shader.PropertyToID(propName), to, 1f).Complete(); UnityEngine.Assertions.Assert.AreApproximatelyEqual(to, m.GetFloat(propName)); } { const string propName = "_MainTex"; #if UNITY_2021_1_OR_NEWER Assert.IsTrue(m.HasTexture(propName)); #endif var to = Random.value * Vector2.one; Tween.MaterialTextureOffset(m, Shader.PropertyToID(propName), to, 1f).Complete(); Assert.AreEqual(to, m.GetTextureOffset(propName)); } { const string propName = "_MainTex"; #if UNITY_2021_1_OR_NEWER Assert.IsTrue(m.HasTexture(propName)); #endif var to = Random.value * Vector2.one; Tween.MaterialTextureScale(m, Shader.PropertyToID(propName), to, 1f).Complete(); Assert.IsTrue(to == m.GetTextureScale(propName)); } } { var m = Resources.Load("Custom_TestShader"); Assert.IsNotNull(m); const string propName = "_TestVectorProp"; var to = Random.value * Vector4.one; Tween.MaterialProperty(m, Shader.PropertyToID(propName), to, 1f).Complete(); Assert.IsTrue(to == m.GetVector(propName)); } } /// passing the serialized UnityEngine.Object reference that is not populated behaves like passing destroyed object [Test] public void PassingDestroyedUnityTarget() { LogAssert.NoUnexpectedReceived(); var target = new GameObject().transform; Object.DestroyImmediate(target.gameObject); var s = Sequence.Create(); expectError(); s.ChainCallback(target, delegate { }); expectError(); Assert.IsFalse(Tween.Delay(target, 0.1f, delegate { }).IsCreated); expectError(); Assert.IsFalse(Tween.Delay(target, 0.1f).IsCreated); expectError(); Assert.IsFalse(Tween.Delay(target, 0.1f, () => {}).IsCreated); expectError(); Assert.IsFalse(Tween.Position(target, new TweenSettings(default, default, 0.1f)).IsCreated); expectError(); var deadTween = Tween.Custom(target, 0f, 0, 0.1f, delegate { }); Assert.IsFalse(deadTween.isAlive); expectAddDeadToSequenceError(); Sequence.Create(deadTween); LogAssert.Expect(LogType.Error, "Shake's strength is (0, 0, 0)."); LogAssert.Expect(LogType.Error, new Regex("Shake's frequency should be > 0f")); expectError(); Tween.ShakeLocalPosition(target, default); void expectError() { expectTargetIsNull(); } } [Test] public void ShakeSettings() { { var s = new ShakeSettings(Vector3.one, 1f, 1); Assert.IsTrue(s.enableFalloff); } { var s = new ShakeSettings(Vector3.one, 1f, 1, true, Ease.InBack); Assert.IsTrue(s.enableFalloff); } { var s = new ShakeSettings(Vector3.one, 1f, 1, AnimationCurve.Linear(0,0,1,1)); Assert.IsTrue(s.enableFalloff); } } [UnityTest] public IEnumerator ForceCompleteWhenWaitingForEndDelay() { var t = Tween.ShakeLocalPosition(transform, new ShakeSettings(Vector3.one, getDt() * 2f, endDelay: 100f)); while (t.interpolationFactor < 1f) { yield return null; } Assert.IsTrue(t.isAlive); t.Complete(); Assert.IsFalse(t.isAlive); } static void print(object o) => Debug.Log($"[{Time.frameCount}] {o}"); [UnityTest] public IEnumerator StopAtEvenOrOddCycle() { for (int i = 0; i < 5; i++) { var t = Tween.Rotation(transform, Vector3.one, getDt() * 5f, cycles: 10, cycleMode: CycleMode.Yoyo); while (t.cyclesDone < Random.Range(2, 4)) { yield return null; } t.SetRemainingCycles(true); Assert.AreEqual(t.cyclesDone % 2 + 1, t.cyclesTotal - t.cyclesDone); t.SetRemainingCycles(false); Assert.AreEqual(t.cyclesDone % 2, (t.cyclesTotal - t.cyclesDone) % 2); } } [UnityTest] public IEnumerator SetCycles() { var t = Tween.Rotation(transform, Vector3.one, Mathf.Max(minDuration, getDt()) * 10, cycles: 10); while (t.cyclesDone != 2) { yield return null; } t.SetRemainingCycles(3); Assert.AreEqual(2, t.cyclesDone); Assert.AreEqual(5, t.cyclesTotal); t.Complete(); } [Test] public void DOTweenAdapterEnabled() { #if !PRIME_TWEEN_DOTWEEN_ADAPTER Assert.Inconclusive(); #endif } [Test] public void ExperimentalDefineSet() { #if PRIME_TWEEN_EXPERIMENTAL Assert.Ignore("Please remove the PRIME_TWEEN_EXPERIMENTAL define and run all tests again."); #endif } [UnityTest] public IEnumerator RecursiveKillAllCall() { // Calling Tween.StopAll/Complete() from onValueChange previously threw the 'Please don't call Tween.StopAll/CompleteAll() from the OnComplete() callback' exception before. // But this no longer the case - current impl checks if Update/FixedUpdate() is safe to call yield return Tween.Custom(0, 1, 1f, val => { if (val != 0) { Tween.StopAll(); } }).ToYieldInstruction(); yield return Tween.Custom(0, 1, 0.01f, _ => { Tween.CompleteAll(); }).ToYieldInstruction(); LogAssert.NoUnexpectedReceived(); } [UnityTest] public IEnumerator KillAllIsImmediate() { Tween.Delay(0.01f); Tween.StopAll(null); Assert.AreEqual(0, tweensCount); Tween.Delay(0.01f); Assert.AreEqual(1, tweensCount); Assert.AreEqual(Tween.CompleteAll(null), 1); Assert.AreEqual(0, tweensCount); yield return null; LogAssert.NoUnexpectedReceived(); } [Test] public void SetCapacityImmediatelyAfterStopAll() { Tween.StopAll(); Assert.AreEqual(0, tweensCount); PrimeTweenConfig.SetTweensCapacity(2); Tween.Delay(0.01f); Tween.Delay(0.01f); Assert.AreEqual(2, tweensCount); Tween.StopAll(); Assert.AreEqual(0, tweensCount); PrimeTweenConfig.SetTweensCapacity(1); PrimeTweenConfig.SetTweensCapacity(capacityForTest); LogAssert.NoUnexpectedReceived(); } [UnityTest] public IEnumerator RotationShakeEndVal() { var target = new GameObject(nameof(RotationShakeEndVal)).transform; var iniRot = Random.rotation.normalized; var strength = Random.insideUnitSphere; var freq = Random.value * 10; target.rotation = iniRot; var handle = Tween.ShakeLocalRotation(target, strength, 1f, freq); // skip frame so cacheDiff() is called yield return null; Assert.IsTrue(handle.isAlive); var t = handle.tween; Assert.IsTrue(iniRot == t.startValue.QuaternionVal); Assert.IsTrue(Quaternion.identity == t.endValue.QuaternionVal); Assert.AreEqual(strength, t.shakeData.strengthPerAxis); Assert.AreEqual(freq, t.shakeData.frequency); Object.Destroy(target.gameObject); handle.Stop(); } [UnityTest] public IEnumerator AtSpeed() { var target = new GameObject(nameof(AtSpeed)).transform; var speed = (Random.value + 0.1f) * 10f; const double tolerance = 0.001; var endValue = new Vector3(1, 0,0); { Assert.AreEqual(Vector3.zero, target.position); var t = Tween.PositionAtSpeed(target, endValue, speed); Assert.AreEqual(speed, 1 / t.duration, tolerance); yield return null; Assert.AreEqual(speed, 1 / t.duration, tolerance); t.Stop(); } { target.position = Vector3.zero; Tween.PositionAtSpeed(target, endValue, speed).Complete(); Assert.AreEqual(endValue, target.position); } { target.position = Vector3.zero; float startDelay = getDt(); var t = Tween.PositionAtSpeed(target, endValue, speed, startDelay: startDelay); var expectedDuration = 1 / speed + startDelay; while (t.interpolationFactor == 0) { Assert.AreEqual(expectedDuration, t.duration, tolerance); yield return null; } Assert.AreEqual(expectedDuration, t.duration, tolerance); yield return null; Assert.AreEqual(expectedDuration, t.duration, tolerance); t.Stop(); } } [UnityTest] public IEnumerator DelayInterpolationFactor() { for (int i = 0; i < 1; i++) { float duration = Random.Range(0.001f, 0.1f); var d = Tween.Delay(duration); float timeStart = Time.time; while (d.isAlive) { Assert.AreEqual(Mathf.Min(1f, (Time.time - timeStart) / duration), d.interpolationFactor, 0.001f); yield return null; } } } [Test] public void TweensCount() { Tween.StopAll(); int count = Random.Range(1, 10); for (int i = 0; i < count; i++) { Tween.PositionX(transform, 10, 0.01f); } Assert.AreEqual(count, Tween.GetTweensCount()); Assert.AreEqual(count, Tween.GetTweensCount(transform)); Tween.StopAll(); Assert.AreEqual(0, Tween.GetTweensCount()); } [UnityTest] public IEnumerator StopCalledOnLastTweenFrame() { float dt = getDt(); Tween tween = default; tween = Tween.Custom(0, 1, dt * 3, val => { if (val == 1f) { tween.Stop(); Assert.IsFalse(tween.isAlive); } }).OnComplete(() => Assert.Fail()); yield return tween.ToYieldInstruction(); } [UnityTest] public IEnumerator StopCalledOnLastTweenFrameFromOnUpdate() { Tween tween = default; float duration = getDt() * 3f; tween = Tween.Custom(0, 1, duration, delegate { }) .OnUpdate(this, delegate { if (tween.interpolationFactor == 1f) { tween.Stop(); Assert.IsFalse(tween.isAlive); } }) .OnComplete(() => Assert.Fail()); yield return tween.ToYieldInstruction(); } [UnityTest] public IEnumerator OnUpdateDestroyedTarget() { var onUpdateTarget = new GameObject(nameof(OnUpdateDestroyedTarget)); int numUpdated = 0; expectOnUpdateRemoved(); yield return Tween.Delay(getDt() * 5) .OnUpdate(onUpdateTarget, (target, _) => { Assert.AreEqual(0, numUpdated); numUpdated++; Object.Destroy(target); }) .ToYieldInstruction(); Assert.AreEqual(1, numUpdated); LogAssert.NoUnexpectedReceived(); } [UnityTest] public IEnumerator OnUpdateException() { var onUpdateTarget = new GameObject(nameof(OnUpdateDestroyedTarget)); expectOnUpdateRemoved(); int numCompleted = 0; yield return Tween.PositionZ(transform, Random.value, 0.001f) .OnUpdate(onUpdateTarget, delegate { throw new Exception(); }) .OnComplete(() => numCompleted++) .ToYieldInstruction(); Assert.AreEqual(1, numCompleted); LogAssert.NoUnexpectedReceived(); } [Test] public void OnUpdateInvalidUsage() { var t = Tween.Delay(0.001f); t.OnUpdate(null, delegate { }); // ok expectException(() => { t.OnUpdate(null, delegate { }); // duplicate is not allowed }, "Only one OnUpdate() is allowed for one tween."); Assert.Throws(() => { t.OnUpdate(this, null); // null onUpdate is not allowed }); } [UnityTest] public IEnumerator OnUpdate() { { // with delay int numCalled = 0; yield return Tween.Delay(minDuration, () => numCalled++).ToYieldInstruction(); Assert.AreEqual(1, numCalled); } { var t = Tween.Position(transform, Vector3.one, getDt(), endDelay: getDt() * 5); int numInterpolationCompleted = 0; yield return t.OnUpdate(null, delegate { if (t.interpolationFactor == 1f) { numInterpolationCompleted++; } }).ToYieldInstruction(); Assert.AreEqual(1, numInterpolationCompleted); } } static void expectOnUpdateRemoved() => LogAssert.Expect(LogType.Error, new Regex("will not be called again because")); [UnityTest] public IEnumerator TimescaleTweenOutliveTheTarget() { var shortTween = Tween.Delay(0.001f); var timeScaleTween = Tween.TweenTimeScale(shortTween, 0.5f, 1) .OnComplete(Assert.Fail); expectOnCompleteIgnored(); yield return shortTween.ToYieldInstruction(); Assert.IsFalse(timeScaleTween.isAlive); LogAssert.NoUnexpectedReceived(); } [UnityTest] public IEnumerator ConversionToEasing() { var curve = AnimationCurve.Linear(0, 0, 1, 1); new TweenSettings(1, curve); new TweenSettings(0, 1, curve); new TweenSettings(0, 1, 1, curve); Tween.Position(transform, Vector3.one, 1, curve); Tween.Position(transform, Vector3.one, 1, (Easing)Ease.InBack); Tween.Position(transform, Vector3.zero, Vector3.one, 1, curve); #if PRIME_TWEEN_EXPERIMENTAL Tween.PositionAdditive(transform, Vector3.one, 1, curve); #endif Tween.PositionAtSpeed(transform, Vector3.one, 10, curve); Tween.PositionAtSpeed(transform, Vector3.zero, Vector3.one, 10, curve); yield return null; Tween.StopAll(); } [UnityTest] public IEnumerator Elastic() { for (int i = 0; i < 5; i++) { Assert.AreEqual(0, Easing.Evaluate(0, parametricSettings(ParametricEase.Elastic, 1, 0.3f))); Assert.AreEqual(1, Easing.Evaluate(1, parametricSettings(ParametricEase.Elastic, 1, 0.3f))); var strength = 1 + Random.value; var period = 0.1f + Random.value; var t = Tween.Position(transform, Vector3.one, 1, Easing.Elastic(strength, period)); yield return null; Assert.IsTrue(t.isAlive); Assert.AreEqual(strength, t.tween.settings.parametricEaseStrength); Assert.AreEqual(period, t.tween.settings.parametricEasePeriod); Assert.AreEqual(Ease.Custom, t.tween.settings.ease); Assert.AreEqual(ParametricEase.Elastic, t.tween.settings.parametricEase); t.Stop(); } } [NotNull] static ReusableTween parametricSettings(ParametricEase ease, float strength, float period) { return new ReusableTween { settings = new TweenSettings { ease = Ease.Custom, duration = 1, parametricEase = ease, parametricEaseStrength = strength, parametricEasePeriod = period } }; } [UnityTest] public IEnumerator Overshoot() { Assert.AreEqual(0, Easing.Evaluate(0, parametricSettings(ParametricEase.Overshoot, 1, float.NaN))); Assert.AreEqual(1, Easing.Evaluate(1, parametricSettings(ParametricEase.Overshoot, 1, float.NaN))); var strength = Random.value; var t = Tween.Position(transform, Vector3.one, 1, Easing.Overshoot(strength)); yield return null; Assert.IsTrue(t.isAlive); Assert.AreEqual(strength * StandardEasing.backEaseConst, t.tween.settings.parametricEaseStrength); Assert.AreEqual(ParametricEase.Overshoot, t.tween.settings.parametricEase); Assert.AreEqual(Ease.Custom, t.tween.settings.ease); t.Stop(); } [UnityTest] [SuppressMessage("ReSharper", "AssignNullToNotNullAttribute")] public IEnumerator NullTarget() { const float duration = 0.05f; Transform t = null; expectTargetIsNull(); Tween.Position(t, Vector3.one, duration).ToYieldInstruction(); expectTargetIsNull(); Tween.Delay(null, duration); expectTargetIsNull(); Tween.Delay(t, duration, delegate { }); expectTargetIsNull(); Sequence.Create().ChainCallback(t, delegate { }); Tween.Custom(0, 1, duration, delegate { }); Tween.Delay(duration); Time.timeScale = 0.99f; Tween.GlobalTimeScale(1, 0.05f); Sequence.Create().ChainCallback(delegate { }); #if PRIME_TWEEN_DOTWEEN_ADAPTER DOTween.Sequence().SetLoops(2).AppendCallback(delegate {}); DOTween.Sequence().PrependInterval(0.01f); #endif yield return null; yield return null; Tween.StopAll(); LogAssert.NoUnexpectedReceived(); } [UnityTest] public IEnumerator MoreThanOneCyclePerFrame() { yield return null; { var zeroDurTween = Tween.ScaleY(transform, 2f, 0, cycles: int.MaxValue - 1); yield return null; Assert.IsFalse(zeroDurTween.isAlive, "0f duration completes all cycles immediately"); } { var infiniteZeroDurTween = Tween.ScaleY(transform, 2f, 0, cycles: -1); yield return null; Assert.AreEqual(1, infiniteZeroDurTween.cyclesDone); Assert.IsTrue(infiniteZeroDurTween.isAlive); } { float duration = 0.001f + getDt() / 10f; Assert.IsTrue(duration >= 0.001f); var t = Tween.ScaleY(transform, 2f, duration, cycles: int.MaxValue - 1); yield return null; Assert.AreEqual(Mathf.FloorToInt(Time.deltaTime / duration), t.cyclesDone); t.Complete(); } } [UnityTest] public IEnumerator ExecutionOrder() { yield return null; int i = 0; void validate(int order) { // Debug.Log($"-------------- validate {order}"); Assert.AreEqual(i, order); i++; } { for (int k = 0; k < 1; k++) { yield return Sequence.Create(2) .Chain(Tween.Custom(0, 1, getDur(), delegate { }, startDelay: getDur(), endDelay: getDur()).OnComplete(() => validate(0))) .ChainCallback(() => validate(1)) .Chain(Tween.Delay(getDur(), () => validate(2))) .Chain(Tween.Custom(0, 1, getDur(), delegate { }, startDelay: getDur(), endDelay: getDur()).OnComplete(() => validate(3))) .ChainCallback(() => validate(4)) .ChainCallback(() => validate(5)) .ChainCallback(() => { validate(6); i = 0; }) .ToYieldInstruction(); } } { for (int k = 0; k < 1; k++) { i = 0; var seq = Sequence.Create(); const int iterations = 5; for (int j = 0; j < iterations; j++) { var expected = j; if (Random.value > 0.5f) { seq.Chain(Tween.Delay(getDur(), () => validate(expected))); } else { seq.ChainCallback(() => validate(expected)); } } seq.ChainCallback(() => { validate(iterations); i = 0; }); seq.SetRemainingCycles(2); yield return seq.ToYieldInstruction(); } } float getDur() => Mathf.Max(minDuration, getDt() * Mathf.Lerp(0.1f, 2f, Random.value)); } static float getDt() => Application.targetFrameRate != -1 ? 1f / Application.targetFrameRate : Time.deltaTime; [Test] public void ShakeIsDeadOnNewReusableTween() { var t = new ReusableTween(); Assert.IsFalse(t.shakeData.isAlive); } [UnityTest] public IEnumerator SmallDurationWithInfiniteCycles() { int numChanged = 0; int numUpdated = 0; const float duration = 0.001f; var t = Tween.Custom(0f, 1f, duration, cycles: -1, onValueChange: delegate { numChanged++; // print("onValueChange"); }) .OnUpdate(this, delegate { numUpdated++; Assert.AreEqual(numChanged, numUpdated); // print("OnUpdate()"); }); float timeStart = Time.time; yield return null; yield return null; yield return null; Assert.AreEqual(4, numChanged); Assert.AreEqual(4, numUpdated); var cyclesDoneExpected = Mathf.FloorToInt((Time.time - timeStart) / duration); Assert.AreEqual(cyclesDoneExpected, t.cyclesDone); t.Complete(); } [UnityTest] public IEnumerator InvalidOperationExceptionInsideCompleteAllBug() { for (int i = 0; i < 1; i++) { const float upper = 1.5f; Tween.Delay(getDt() * Random.Range(0.5f, upper), () => Tween.Delay(getDt() * Random.Range(0.5f, 1.5f))); Tween.Delay(getDt() * Random.Range(0.5f, upper), () => Tween.Delay(getDt() * Random.Range(0.5f, 1.5f))); Tween.Delay(getDt() * Random.Range(0.5f, upper), () => Tween.Delay(getDt() * Random.Range(0.5f, 1.5f))); Tween.Delay(getDt() * Random.Range(0.5f, upper), () => Tween.Delay(getDt() * Random.Range(0.5f, 1.5f))); Tween.Delay(getDt() * Random.Range(0.5f, upper), () => Tween.Delay(getDt() * Random.Range(0.5f, 1.5f))); int delayFramesBeforeCompleteAll = Random.Range(0, 0); for (int j = 0; j < delayFramesBeforeCompleteAll; j++) { yield return null; } if (Random.value >= 0.0f) { Tween.CompleteAll(); } } yield return null; } [UnityTest] public IEnumerator TweenElapsedTimeTotal() { var duration = getDt() * 2f; var t = Tween.Custom(0f, 1f, duration, delegate { }); var s = Sequence.Create(2) .ChainDelay(duration) .Chain(t) .ChainDelay(duration); while (s.isAlive) { Assert.IsTrue(t.elapsedTimeTotal >= 0f); Assert.IsTrue(t.elapsedTimeTotal <= t.durationTotal); yield return null; } } [Test] public void SetCyclesWithDelay() { LogAssert.Expect(LogType.Error, new Regex("Applying cycles to Delay will not repeat")); var delay = Tween.Delay(this, duration: .5f, delegate {}); delay.SetRemainingCycles(10); delay.Stop(); } [Test] public void ElapsedTimeTotalIsClamped() { for (int i = 0; i < 1; i++) { float duration = Random.value + 0.001f; int cycles = Random.Range(3, 20); var t = Tween.Delay(duration); t.SetRemainingCycles(cycles); t.isPaused = true; t.elapsedTime = float.MaxValue; Assert.AreEqual(1, t.cyclesDone); Assert.AreEqual(duration, t.tween.elapsedTimeTotal); Assert.AreEqual(0f, t.elapsedTime); // new cycles has started, so should be 0f Assert.AreEqual(duration, t.elapsedTimeTotal); t.elapsedTimeTotal = 0f; Assert.AreEqual(0, t.cyclesDone); Assert.AreEqual(0f, t.tween.elapsedTimeTotal); Assert.AreEqual(0f, t.elapsedTime); Assert.AreEqual(0f, t.elapsedTimeTotal); t.elapsedTimeTotal = float.MaxValue; Assert.AreEqual(cycles, t.cyclesDone); Assert.AreEqual(t.durationTotal, t.tween.elapsedTimeTotal); Assert.AreEqual(t.duration, t.elapsedTime); Assert.AreEqual(t.durationTotal, t.elapsedTimeTotal); Assert.IsTrue(t.isAlive); t.Stop(); } } [UnityTest] public IEnumerator WarnEndValueEqualsCurrent() { Assert.AreEqual(0, tweensCount); var iniPos = Vector3.one * Random.value; { transform.position = iniPos; var t = Tween.Position(transform, iniPos, 1f); yield return null; LogAssert.Expect(LogType.Warning, new Regex("Tween's 'endValue' equals to the current animated value")); t.Stop(); } { PrimeTweenConfig.warnEndValueEqualsCurrent = false; transform.position = iniPos; var t = Tween.Position(transform, iniPos, 1f); yield return null; t.Stop(); PrimeTweenConfig.warnEndValueEqualsCurrent = true; } LogAssert.NoUnexpectedReceived(); } [Test] public void SetInfiniteTweenElapsedTime() { var t = Tween.Custom(0f, 1f, 0.01f, delegate { }, cycles: -1); for (int i = 0; i < 8; i++) { LogAssert.Expect(LogType.Error, new Regex("Invalid elapsedTime")); } t.elapsedTime = float.MaxValue; // ok t.elapsedTime = float.PositiveInfinity; // ok t.elapsedTime = -1f; t.elapsedTime = float.NegativeInfinity; t.elapsedTime = float.NaN; t.elapsedTimeTotal = 0f; // ok t.elapsedTimeTotal = 10f; // ok t.elapsedTimeTotal = -1f; t.elapsedTimeTotal = float.MaxValue; t.elapsedTimeTotal = float.PositiveInfinity; t.elapsedTimeTotal = float.NegativeInfinity; t.elapsedTimeTotal = float.NaN; t.Stop(); } [Test] public void SetInfiniteTweenProgress() { var t = Tween.Custom(0f, 1f, 0.01f, delegate { }, cycles: -1); Assert.IsTrue(float.IsPositiveInfinity(t.durationTotal)); // ok t.progress = 0f; t.progress = 0.5f; t.progress = 1f; for (int i = 0; i < 3; i++) { LogAssert.Expect(LogType.Error, new Regex("It's not allowed to set progressTotal on infinite tween")); } t.progressTotal = 0f; t.progressTotal = 0.5f; t.progressTotal = 1f; Assert.IsTrue(float.IsPositiveInfinity(t.durationTotal)); t.Stop(); } [UnityTest] public IEnumerator ZZ_SceneLoadSetsUnityObjectBug1() { var t = Tween.Delay(1f); LoadTestScene(); yield return null; Assert.IsTrue(t.isAlive); t.Stop(); } [UnityTest] public IEnumerator ZZ_SceneLoadSetsUnityObjectBug2() { var seq = Sequence.Create().ChainDelay(1f); LoadTestScene(); yield return null; Assert.IsTrue(seq.isAlive); seq.Stop(); } static void LoadTestScene() { const string testScenePath = "Packages/com.kyrylokuzyk.primetween/Tests/SceneLoadSetsUnityObjectBug.unity"; #if UNITY_EDITOR if (Application.isEditor) { UnityEditor.SceneManagement.EditorSceneManager.LoadSceneInPlayMode(testScenePath, new LoadSceneParameters(LoadSceneMode.Single)); return; } #endif SceneManager.LoadScene(testScenePath, LoadSceneMode.Single); } [UnityTest] public IEnumerator SetElapsedTimeRecursively() { Tween t = default; int i = 0; t = Tween.Custom(0f, 1f, getDt() / 5f, delegate { // print("custom"); onUpdate(); }) .OnUpdate(this, delegate { // print("OnUpdate"); onUpdate(); }); yield return t.ToYieldInstruction(); void onUpdate() { i++; Assert.IsTrue(i < 100); expectRecursiveCallError(); t.progress += 0.01f; expectRecursiveCallError(); t.progressTotal += 0.01f; expectRecursiveCallError(); t.elapsedTime += 0.01f; expectRecursiveCallError(); t.elapsedTimeTotal += 0.01f; } } [UnityTest] public IEnumerator CompleteInfiniteTween() { int numCallbacks = 0; var duration = Mathf.Max(minDuration, Random.Range(0f, getDt())); Assert.IsTrue(duration >= minDuration); var t = Tween.Position(transform, Vector3.one, duration, cycles: -1) .OnComplete(() => numCallbacks++); float timeStart = Time.time; yield return null; yield return null; var cyclesDoneExpected = Mathf.FloorToInt((Time.time - timeStart) / duration); Assert.AreEqual(cyclesDoneExpected, t.cyclesDone); t.Complete(); Assert.AreEqual(1, numCallbacks); } [Test] public void CompleteInfiniteTween2() { var target = new GameObject().transform; var iniValue = Vector3.one * 0.5f; target.localScale = iniValue; var endValue = Vector3.one * 2f; Tween.Scale(target, endValue, 0.5f, cycles: -1, cycleMode: CycleMode.Yoyo).Complete(); Assert.AreEqual(endValue, target.localScale); { var t = Tween.Scale(target, iniValue, 0.5f, cycles: -1, cycleMode: CycleMode.Yoyo); t.SetRemainingCycles(true); t.Complete(); Assert.AreEqual(iniValue, target.localScale); } { var t = Tween.Scale(target, endValue, 0.5f, cycles: -1, cycleMode: CycleMode.Yoyo); t.SetRemainingCycles(false); t.Complete(); Assert.AreEqual(iniValue, target.localScale); } Object.DestroyImmediate(target.gameObject); LogAssert.NoUnexpectedReceived(); } [UnityTest] public IEnumerator WarnEndValueEqualsCurrent2() { Tween.StopAll(); yield return null; { transform.position = Vector3.one; expectWarning(); var t = Tween.Position(transform, Vector3.one, 0.01f); yield return null; t.Stop(); } { transform.position = Vector3.one; PrimeTweenConfig.warnEndValueEqualsCurrent = false; var t = Tween.Position(transform, Vector3.one, 0.01f); PrimeTweenConfig.warnEndValueEqualsCurrent = true; yield return null; t.Stop(); } LogAssert.NoUnexpectedReceived(); static void expectWarning() => LogAssert.Expect(LogType.Warning, new Regex("Tween's 'endValue' equals to the current animated value")); } [UnityTest] public IEnumerator SetPropertiesOfInfiniteTween() { var t = Tween.Custom(0, 1, 1, _ => { // print(_); }, cycles: -1, cycleMode: CycleMode.Yoyo, ease: Ease.Linear); t.elapsedTime = 0.5f; yield return null; t.progress = 0.75f; yield return null; t.elapsedTimeTotal = 2.1f; yield return null; t.elapsedTime = 0.5f; yield return null; t.Stop(); } [Test] public void NegativeIniCyclesBugReport() { // https://github.com/KyryloKuzyk/PrimeTween/issues/63 Tween.StopAll(); var startValue = Vector3.one * 10; var endValue = Vector3.one * 20; transform.position = startValue; var posTween = Tween.Position(transform, startValue, endValue, new TweenSettings(1f, cycles: 2, cycleMode: CycleMode.Rewind)); var seq = Sequence.Create() .ChainDelay(1f) .Chain(posTween); seq.elapsedTime = 1.1f; Assert.AreNotEqual(0f, posTween.interpolationFactor); Assert.AreNotEqual(startValue, transform.position); // Assert.AreEqual(-1, posTween.tween.cyclesDone); seq.elapsedTime = 0.5f; Assert.AreEqual(0f, posTween.interpolationFactor); Assert.AreEqual(startValue, transform.position); seq.Stop(); } [Test] public void UpdateTypes() { var iniUpdateType = PrimeTweenConfig.defaultUpdateType; { Tween.StopAll(); var t = Tween.Position(transform, default, new TweenSettings(1f, updateType: default)).tween; Assert.AreEqual(_UpdateType.Update, t.settings._updateType); Assert.AreEqual(t, PrimeTweenManager.Instance.tweens.Single()); } { Tween.StopAll(); var t = Tween.Position(transform, default, new TweenSettings(1f, updateType: UpdateType.Update)).tween; Assert.AreEqual(_UpdateType.Update, t.settings._updateType); Assert.AreEqual(t, PrimeTweenManager.Instance.tweens.Single()); } { Tween.StopAll(); var t = Tween.Position(transform, default, new TweenSettings(1f, updateType: UpdateType.LateUpdate)).tween; Assert.AreEqual(_UpdateType.LateUpdate, t.settings._updateType); Assert.AreEqual(t, PrimeTweenManager.Instance.lateUpdateTweens.Single()); } { Tween.StopAll(); var t = Tween.Position(transform, default, new TweenSettings(1f, updateType: UpdateType.FixedUpdate)).tween; Assert.AreEqual(_UpdateType.FixedUpdate, t.settings._updateType); Assert.AreEqual(t, PrimeTweenManager.Instance.fixedUpdateTweens.Single()); } { Tween.StopAll(); UpdateType updateType = default; updateType.enumValue = (_UpdateType)100; Tween.Position(transform, default, new TweenSettings(1f, updateType: updateType)); LogAssert.Expect(LogType.Error, "Invalid update type: 100"); } { Tween.StopAll(); var t = Tween.ShakeLocalPosition(transform, new ShakeSettings(Vector3.one, updateType: default)).tween; Assert.AreEqual(_UpdateType.Update, t.settings._updateType); Assert.AreEqual(t, PrimeTweenManager.Instance.tweens.Single()); } { Tween.StopAll(); var t = Tween.ShakeLocalPosition(transform, new ShakeSettings(Vector3.one, updateType: UpdateType.Update)).tween; Assert.AreEqual(_UpdateType.Update, t.settings._updateType); Assert.AreEqual(t, PrimeTweenManager.Instance.tweens.Single()); } { Tween.StopAll(); var t = Tween.ShakeLocalPosition(transform, new ShakeSettings(Vector3.one, updateType: UpdateType.LateUpdate)).tween; Assert.AreEqual(_UpdateType.LateUpdate, t.settings._updateType); Assert.AreEqual(t, PrimeTweenManager.Instance.lateUpdateTweens.Single()); } { Tween.StopAll(); var t = Tween.ShakeLocalPosition(transform, new ShakeSettings(Vector3.one, updateType: UpdateType.FixedUpdate)).tween; Assert.AreEqual(_UpdateType.FixedUpdate, t.settings._updateType); Assert.AreEqual(t, PrimeTweenManager.Instance.fixedUpdateTweens.Single()); } { Tween.StopAll(); PrimeTweenConfig.defaultUpdateType = UpdateType.FixedUpdate; var t = Tween.Position(transform, default, new TweenSettings(1f, updateType: default)).tween; Assert.AreEqual(_UpdateType.FixedUpdate, t.settings._updateType); Assert.AreEqual(t, PrimeTweenManager.Instance.fixedUpdateTweens.Single()); } { Tween.StopAll(); PrimeTweenConfig.defaultUpdateType = UpdateType.LateUpdate; var t = Tween.Position(transform, default, new TweenSettings(1f, updateType: default)).tween; Assert.AreEqual(_UpdateType.LateUpdate, t.settings._updateType); Assert.AreEqual(t, PrimeTweenManager.Instance.lateUpdateTweens.Single()); } Tween.StopAll(); #pragma warning disable CS0618 // Type or member is obsolete Assert.AreEqual(false, new TweenSettings(1f, updateType: default).useFixedUpdate); Assert.AreEqual(false, new TweenSettings(1f, updateType: UpdateType.Update).useFixedUpdate); Assert.AreEqual(false, new TweenSettings(1f, updateType: UpdateType.LateUpdate).useFixedUpdate); Assert.AreEqual(true, new TweenSettings(1f, updateType: UpdateType.FixedUpdate).useFixedUpdate); #pragma warning restore CS0618 // Type or member is obsolete Assert.AreEqual(UpdateType.Default, new TweenSettings(1f, updateType: default).updateType); PrimeTweenConfig.defaultUpdateType = UpdateType.Update; Assert.AreEqual(UpdateType.Default, new TweenSettings(1f, updateType: default).updateType); PrimeTweenConfig.defaultUpdateType = UpdateType.LateUpdate; Assert.AreEqual(UpdateType.Update, new TweenSettings(1f, updateType: UpdateType.Update).updateType); Assert.AreEqual(UpdateType.FixedUpdate, new TweenSettings(1f, updateType: UpdateType.FixedUpdate).updateType); PrimeTweenConfig.defaultUpdateType = iniUpdateType; } } #endif