import * as TKUnit from '../../tk-unit'; import * as helper from '../../ui-helper'; import * as viewModule from '@nativescript/core/ui/core/view'; import { Label } from '@nativescript/core/ui/label'; import { StackLayout } from '@nativescript/core/ui/layouts/stack-layout'; import * as colorModule from '@nativescript/core/color'; import { Enums } from '@nativescript/core'; import { AnimationPromise } from '@nativescript/core/ui/animation'; // >> animation-require import * as animation from '@nativescript/core/ui/animation'; import { PercentLengthType, PercentLength } from '@nativescript/core'; // << animation-require function prepareTest(parentHeight?: number, parentWidth?: number): Label { let mainPage = helper.getCurrentPage(); let label = new Label(); label.text = 'label'; let stackLayout = new StackLayout(); // optionally size the parent extent to make assertions about percentage values if (parentHeight !== undefined) { stackLayout.height = PercentLength.parse(parentHeight + ''); } if (parentWidth !== undefined) { stackLayout.width = PercentLength.parse(parentWidth + ''); } stackLayout.addChild(label); mainPage.content = stackLayout; TKUnit.waitUntilReady(() => label.isLoaded); return label; } export function test_AnimatingProperties(done) { let label = prepareTest(); // >> animation-properties label .animate({ opacity: 0.75, backgroundColor: new colorModule.Color('Red'), translate: { x: 100, y: 100 }, scale: { x: 2, y: 2 }, rotate: 180, duration: 5, delay: 10, iterations: 3, curve: Enums.AnimationCurve.easeIn, }) .then(() => { //console.log("Animation finished."); // >> (hide) assertIOSNativeTransformIsCorrect(label); done(); // << (hide) }) .catch((e) => { console.log(e.message); // >> (hide) done(e); // << (hide) }); // << animation-properties } export function test_PlayRejectsWhenAlreadyPlayingAnimation(done) { let label = prepareTest(); var animation = label.createAnimation({ translate: { x: 100, y: 100 }, duration: 5 }); animation.play(); animation.play().then( () => { // should never get here throw new Error('Already playing.'); }, (e) => { TKUnit.assert(animation.isPlaying === true, "animation.isPlaying should be true since it's currently playing."); if (e === 'Animation is already playing.') { done(); } } ); } export function test_CancelIgnoredWhenNotPlayingAnimation() { let label = prepareTest(); var animation = label.createAnimation({ translate: { x: 100, y: 100 }, duration: 5 }); animation.cancel(); // should not throw TKUnit.assert(!animation.isPlaying, 'animation.isPlaying should be falsey since it was never played.'); } export function test_CancellingAnimation(done) { let label = prepareTest(); // >> animation-cancel var animation1 = label.createAnimation({ translate: { x: 100, y: 100 }, duration: 5 }); animation1 .play() .then(() => { //console.log("Animation finished"); // >> (hide) throw new Error('Cancelling Animation - Should not be in the Promise Then()'); // << (hide) }) .catch((e) => { //console.log("Animation cancelled"); // >> (hide) if (!e) { done(new Error('Cancel path did not have proper error')); } else if (e.toString() === 'Error: Animation cancelled.') { done(); } else { done(e); } // << (hide) }); animation1.cancel(); // << animation-cancel } export function test_CancellingAnimate(done) { let label = prepareTest(); // >> animation-cancel2 var animation1 = label .animate({ translate: { x: 100, y: 100 }, duration: 5 }) .then(() => { //console.log("Animation finished"); // >> (hide) throw new Error('Cancelling Animate - Should not be in Promise Then()'); // << (hide) }) .catch((e) => { //console.log("Animation cancelled"); // >> (hide) if (!e) { done(new Error('Cancel path did not have proper error')); } else if (e.toString() === 'Error: Animation cancelled.') { done(); } else { done(e); } // << (hide) }); (animation1).cancel(); // << animation-cancel2 } export function test_ChainingAnimations(done) { let label = prepareTest(); // >> animation-chaining let duration = 300; // >> (hide) duration = 5; // << (hide) label .animate({ opacity: 0, duration: duration }) .then(() => label.animate({ opacity: 1, duration: duration })) .then(() => label.animate({ translate: { x: 200, y: 200 }, duration: duration })) .then(() => label.animate({ translate: { x: 0, y: 0 }, duration: duration })) .then(() => label.animate({ scale: { x: 5, y: 5 }, duration: duration })) .then(() => label.animate({ scale: { x: 1, y: 1 }, duration: duration })) .then(() => label.animate({ rotate: 180, duration: duration })) .then(() => label.animate({ rotate: 0, duration: duration })) .then(() => { //console.log("Animation finished"); // >> (hide) assertIOSNativeTransformIsCorrect(label); done(); // << (hide) }) .catch((e) => { console.log(e.message); // >> (hide) done(e); // << (hide) }); // << animation-chaining } export function test_ReusingAnimations(done) { let label = prepareTest(); // >> animation-reusing var animation1 = label.createAnimation({ translate: { x: 100, y: 100 }, duration: 5 }); var animation2 = label.createAnimation({ translate: { x: 0, y: 0 }, duration: 5 }); animation1 .play() .then(() => animation2.play()) .then(() => animation1.play()) .then(() => animation2.play()) .then(() => animation1.play()) .then(() => animation2.play()) .then(() => { //console.log("Animation finished"); // >> (hide) assertIOSNativeTransformIsCorrect(label); done(); // << (hide) }) .catch((e) => { console.log(e.message); // >> (hide) done(e); // << (hide) }); // << animation-reusing } export function test_AnimatingMultipleViews(done) { let mainPage = helper.getCurrentPage(); let label1 = new Label(); label1.text = 'label1'; let label2 = new Label(); label2.text = 'label2'; let label3 = new Label(); label3.text = 'label3'; let stackLayout = new StackLayout(); stackLayout.addChild(label1); stackLayout.addChild(label2); stackLayout.addChild(label3); mainPage.content = stackLayout; TKUnit.waitUntilReady(() => label3.isLoaded); // >> animation-multiple-views var animations: Array = [ { target: label1, translate: { x: 200, y: 200 }, duration: 5, delay: 0 }, { target: label2, translate: { x: 200, y: 200 }, duration: 5, delay: 2 }, { target: label3, translate: { x: 200, y: 200 }, duration: 5, delay: 4 }, ]; var a = new animation.Animation(animations); a.play() .then(() => { //console.log("Animations finished"); // >> (hide) assertIOSNativeTransformIsCorrect(label1); assertIOSNativeTransformIsCorrect(label2); assertIOSNativeTransformIsCorrect(label3); done(); // << (hide) }) .catch((e) => { console.log(e.message); // >> (hide) done(e); // << (hide) }); // << animation-multiple-views } export function test_AnimateOpacity(done) { let label = prepareTest(); label .animate({ opacity: 0.75, duration: 5 }) .then(() => { TKUnit.assertEqual(label.opacity, 0.75, 'label.opacity'); done(); }) .catch((e) => { done(e); }); } export function test_AnimateOpacity_ShouldThrow_IfNotNumber() { var label = new Label(); helper.buildUIAndRunTest(label, (views: Array) => { TKUnit.assertThrows(() => { label.animate({ opacity: '0.75' }); }, 'Setting opacity to a non number should throw.'); }); } export function test_AnimateDelay_ShouldThrow_IfNotNumber() { var label = new Label(); helper.buildUIAndRunTest(label, (views: Array) => { TKUnit.assertThrows(() => { label.animate({ delay: '1' }); }, 'Setting delay to a non number should throw.'); }); } export function test_AnimateDuration_ShouldThrow_IfNotNumber() { var label = new Label(); helper.buildUIAndRunTest(label, (views: Array) => { TKUnit.assertThrows(() => { label.animate({ duration: '1' }); }, 'Setting duration to a non number should throw.'); }); } export function test_AnimateIterations_ShouldThrow_IfNotNumber() { var label = new Label(); helper.buildUIAndRunTest(label, (views: Array) => { TKUnit.assertThrows(() => { label.animate({ iterations: '1' }); }, 'Setting iterations to a non number should throw.'); }); } export function test_AnimateRotate_ShouldThrow_IfNotNumber() { var label = new Label(); helper.buildUIAndRunTest(label, (views: Array) => { TKUnit.assertThrows(() => { label.animate({ rotate: '1' }); }, 'Setting rotate to a non number should throw.'); }); } export function test_AnimateScale_ShouldThrow_IfNotPair() { var label = new Label(); helper.buildUIAndRunTest(label, (views: Array) => { TKUnit.assertThrows(() => { label.animate({ scale: '1' }); }, 'Setting scale to a non Pair should throw.'); }); } export function test_AnimateTranslate_ShouldThrow_IfNotPair() { var label = new Label(); helper.buildUIAndRunTest(label, (views: Array) => { TKUnit.assertThrows(() => { label.animate({ translate: '1' }); }, 'Setting translate to a non Pair should throw.'); }); } export function test_AnimateBackgroundColor_ShouldThrow_IfNotValidColorStringOrColor() { var label = new Label(); helper.buildUIAndRunTest(label, (views: Array) => { TKUnit.assertThrows(() => { label.animate({ backgroundColor: 'test' }); }, 'Setting backgroundColor to a not valid color string or Color should throw.'); }); } export function test_AnimateBackgroundColor(done) { let label = prepareTest(); var red = new colorModule.Color('Red'); label .animate({ backgroundColor: red, duration: 5 }) .then(() => { TKUnit.assert((label.backgroundColor).equals(red)); done(); }) .catch((e) => { done(e); }); } export function test_AnimateBackgroundColor_FromString(done) { let label = prepareTest(); var expected = 'Red'; var clr = new colorModule.Color(expected); label .animate({ backgroundColor: expected, duration: 5 }) .then(() => { TKUnit.assert((label.backgroundColor).equals(clr)); done(); }) .catch((e) => { done(e); }); } export function test_AnimateTranslate(done) { let label = prepareTest(); label .animate({ translate: { x: 100, y: 200 }, duration: 5 }) .then(() => { TKUnit.assertEqual(label.translateX, 100, 'label.translateX'); TKUnit.assertEqual(label.translateY, 200, 'label.translateY'); assertIOSNativeTransformIsCorrect(label); done(); }) .catch((e) => { done(e); }); } export function test_AnimateScale(done) { let label = prepareTest(); label .animate({ scale: { x: 2, y: 3 }, duration: 5 }) .then(() => { TKUnit.assertEqual(label.scaleX, 2, 'label.scaleX'); TKUnit.assertEqual(label.scaleY, 3, 'label.scaleY'); assertIOSNativeTransformIsCorrect(label); done(); }) .catch((e) => { done(e); }); } export function test_AnimateRotate(done) { let label = prepareTest(); label .animate({ rotate: 123, duration: 5 }) .then(() => { TKUnit.assertEqual(label.rotate, 123, 'label.rotate'); assertIOSNativeTransformIsCorrect(label); done(); }) .catch((e) => { done(e); }); } function animateExtentAndAssertExpected(along: 'height' | 'width', value: PercentLengthType, pixelExpected: PercentLengthType): Promise { function pretty(val) { return JSON.stringify(val, null, 2); } const parentExtent = 200; const height = along === 'height' ? parentExtent : undefined; const width = along === 'height' ? undefined : parentExtent; const label = prepareTest(height, width); const props = { duration: 5, [along]: value, }; return label.animate(props).then(() => { const observedString: string = PercentLength.convertToString(label[along]); const inputString: string = PercentLength.convertToString(value); TKUnit.assertEqual(observedString, inputString, `PercentLength.convertToString(${pretty(value)}) should be "${inputString}" but is "${observedString}"`); // assert that the animated view"s calculated pixel extent matches the expected pixel value const observedNumber: number = PercentLength.toDevicePixels(label[along], parentExtent, parentExtent); const expectedNumber: number = PercentLength.toDevicePixels(pixelExpected, parentExtent, parentExtent); TKUnit.assertEqual(observedNumber, expectedNumber, `PercentLength.toDevicePixels(${inputString}) should be "${expectedNumber}" but is "${observedNumber}"`); assertIOSNativeTransformIsCorrect(label); }); } export function test_AnimateExtent_Should_ResolvePercentageStrings(done) { let promise: Promise = Promise.resolve(); const pairs: [string, string][] = [ ['100%', '200px'], ['50%', '100px'], ['25%', '50px'], ['-25%', '-50px'], ['-50%', '-100px'], ['-100%', '-200px'], ]; pairs.forEach((pair) => { const input = PercentLength.parse(pair[0]); const expected = PercentLength.parse(pair[1]); promise = promise.then(() => { return animateExtentAndAssertExpected('height', input, expected); }); promise = promise.then(() => { return animateExtentAndAssertExpected('width', input, expected); }); }); promise.then(() => done()).catch(done); } export function test_AnimateExtent_Should_AcceptStringPixelValues(done) { let promise: Promise = Promise.resolve(); const pairs: [string, number][] = [ ['100px', 100], ['50px', 50], ]; pairs.forEach((pair) => { const input = PercentLength.parse(pair[0]); const expected = { unit: 'px', value: pair[1], } as PercentLengthType; promise = promise.then(() => { return animateExtentAndAssertExpected('height', input, expected); }); promise = promise.then(() => { return animateExtentAndAssertExpected('width', input, expected); }); }); promise.then(() => done()).catch(done); } export function test_AnimateExtent_Should_AcceptNumberValuesAsDip(done) { let promise: Promise = Promise.resolve(); const inputs: any[] = [200, 150, 100, 50, 0]; inputs.forEach((value) => { const parsed = PercentLength.parse(value); promise = promise.then(() => { return animateExtentAndAssertExpected('height', parsed, parsed); }); promise = promise.then(() => { return animateExtentAndAssertExpected('width', parsed, parsed); }); }); promise.then(() => done()).catch(done); } export function test_AnimateExtent_Should_ThrowIfCannotParsePercentLength() { const label = new Label(); helper.buildUIAndRunTest(label, (views: Array) => { TKUnit.assertThrows(() => { label.animate({ width: '-frog%' }); }, 'Invalid percent string should throw'); TKUnit.assertThrows(() => { label.animate({ height: '-frog%' }); }, 'Invalid percent string should throw'); }); } export function test_AnimateTranslateScaleAndRotateSimultaneously(done) { let label = prepareTest(); label .animate({ translate: { x: 100, y: 200 }, scale: { x: 2, y: 3 }, rotate: 123, duration: 5, }) .then(() => { TKUnit.assertEqual(label.translateX, 100, 'label.translateX'); TKUnit.assertEqual(label.translateY, 200, 'label.translateY'); TKUnit.assertEqual(label.scaleX, 2, 'label.scaleX'); TKUnit.assertEqual(label.scaleY, 3, 'label.scaleY'); TKUnit.assertEqual(label.rotate, 123, 'label.rotate'); assertIOSNativeTransformIsCorrect(label); done(); }) .catch((e) => { done(e); }); } export function test_AnimateTranslateScaleAndRotateSequentially(done) { let label = prepareTest(); label .animate({ translate: { x: 100, y: 200 }, duration: 5 }) .then(() => { TKUnit.assertEqual(label.translateX, 100, 'label.translateX'); TKUnit.assertEqual(label.translateY, 200, 'label.translateY'); assertIOSNativeTransformIsCorrect(label); return label.animate({ scale: { x: 2, y: 3 }, duration: 5 }); }) .then(() => { TKUnit.assertEqual(label.translateX, 100, 'label.translateX'); TKUnit.assertEqual(label.translateY, 200, 'label.translateY'); TKUnit.assertEqual(label.scaleX, 2, 'label.scaleX'); TKUnit.assertEqual(label.scaleY, 3, 'label.scaleY'); assertIOSNativeTransformIsCorrect(label); return label.animate({ rotate: 123, duration: 5 }); }) .then(() => { TKUnit.assertEqual(label.translateX, 100, 'label.translateX'); TKUnit.assertEqual(label.translateY, 200, 'label.translateY'); TKUnit.assertEqual(label.scaleX, 2, 'label.scaleX'); TKUnit.assertEqual(label.scaleY, 3, 'label.scaleY'); TKUnit.assertEqual(label.rotate, 123, 'label.rotate'); assertIOSNativeTransformIsCorrect(label); done(); }) .catch((e) => { done(e); }); } export function test_AnimationsAreAlwaysPlayed(done) { let label = prepareTest(); var animation1 = label.createAnimation({ opacity: 0, duration: 5 }); var animation2 = label.createAnimation({ opacity: 1, duration: 5 }); animation1 .play() .then(() => { TKUnit.assert(label.opacity === 0, `Label opacity should be 0 after first animation, actual value is ${label.opacity}.`); return animation2.play(); }) .then(() => { TKUnit.assert(label.opacity === 1, `Label opacity should be 1 after second animation, actual value is ${label.opacity}.`); done(); }) .catch((e) => { console.log(e.message); done(e); }); } export function test_PlayPromiseIsResolvedWhenAnimationFinishes(done) { let label = prepareTest(); var animation = label.createAnimation({ opacity: 0, duration: 5 }); animation.play().then( function onResolved() { TKUnit.assert(animation.isPlaying === false, 'Animation.isPlaying should be false when animation play promise is resolved.'); done(); }, function onRejected(e) { TKUnit.assert(false, 'Animation play promise should be resolved, not rejected.'); done(e); } ); } export function test_PlayPromiseIsRejectedWhenAnimationIsCancelled(done) { let label = prepareTest(); var animation = label.createAnimation({ opacity: 0, duration: 5 }); animation.play().then( function onResolved() { TKUnit.assert(false, 'Animation play promise should be rejected, not resolved.'); done(); }, function onRejected(e) { TKUnit.assert(animation.isPlaying === false, 'Animation.isPlaying should be false when animation play promise is rejected.'); done(); } ); animation.cancel(); } function assertIOSNativeTransformIsCorrect(view: viewModule.View) { if (view.ios) { var errorMessage = (animation)._getTransformMismatchErrorMessage(view); if (errorMessage) { TKUnit.assert(false, errorMessage); } } }