mirror of
				https://github.com/NativeScript/NativeScript.git
				synced 2025-11-04 21:06:45 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			528 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			528 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { CoreTypes } from '../../core-types';
 | 
						|
import type { KeyframeAnimationInfo, KeyframeInfo } from '../animation';
 | 
						|
import { CssAnimationParser, keyframeAnimationsFromCSSProperty } from './css-animation-parser';
 | 
						|
import { cssTreeParse } from '../../css/css-tree-parser';
 | 
						|
 | 
						|
function getCurves() {
 | 
						|
	return {
 | 
						|
		ease: CoreTypes.AnimationCurve.ease,
 | 
						|
		linear: CoreTypes.AnimationCurve.linear,
 | 
						|
		'ease-in': CoreTypes.AnimationCurve.easeIn,
 | 
						|
		'ease-out': CoreTypes.AnimationCurve.easeOut,
 | 
						|
		'ease-in-out': CoreTypes.AnimationCurve.easeInOut,
 | 
						|
		spring: CoreTypes.AnimationCurve.spring,
 | 
						|
		'cubic-bezier(0.1, 1.0, 0.5, 0.5)': CoreTypes.AnimationCurve.cubicBezier(0.1, 1.0, 0.5, 0.5),
 | 
						|
		'cubic-bezier(0.42, 0.0, 1.0, 1.0);': CoreTypes.AnimationCurve.cubicBezier(0.42, 0.0, 1.0, 1.0),
 | 
						|
	};
 | 
						|
}
 | 
						|
 | 
						|
describe('css-animation-parser', () => {
 | 
						|
	describe('shorthand-property-parser', () => {
 | 
						|
		// helper functions
 | 
						|
		function testSingleAnimation(css: string): KeyframeAnimationInfo {
 | 
						|
			const animations: KeyframeAnimationInfo[] = [];
 | 
						|
			keyframeAnimationsFromCSSProperty(css, animations);
 | 
						|
 | 
						|
			return animations[0];
 | 
						|
		}
 | 
						|
 | 
						|
		function testMultipleAnimations(css: string): KeyframeAnimationInfo[] {
 | 
						|
			const animations: KeyframeAnimationInfo[] = [];
 | 
						|
			keyframeAnimationsFromCSSProperty(css, animations);
 | 
						|
 | 
						|
			return animations;
 | 
						|
		}
 | 
						|
 | 
						|
		it('empty', () => {
 | 
						|
			const animation = testSingleAnimation('');
 | 
						|
			expect(animation).toBeUndefined();
 | 
						|
		});
 | 
						|
 | 
						|
		// times to test for
 | 
						|
		const times = {
 | 
						|
			'0s': 0,
 | 
						|
			'0ms': 0,
 | 
						|
			'250ms': 250,
 | 
						|
			'0.5s': 500,
 | 
						|
			'1500ms': 1500,
 | 
						|
			'1s': 1000,
 | 
						|
			'3s': 3000,
 | 
						|
		};
 | 
						|
 | 
						|
		it('parses duration', () => {
 | 
						|
			Object.entries(times).forEach(([timeString, ms]) => {
 | 
						|
				expect(testSingleAnimation(`${timeString}`).duration).toBe(ms);
 | 
						|
			});
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses delay', () => {
 | 
						|
			Object.entries(times).forEach(([timeString, ms]) => {
 | 
						|
				const animation = testSingleAnimation(`0s ${timeString}`);
 | 
						|
				expect(animation.duration).toBe(0);
 | 
						|
				expect(animation.delay).toBe(ms);
 | 
						|
			});
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses duration and delay', () => {
 | 
						|
			Object.entries(times).forEach(([timeString, ms]) => {
 | 
						|
				const animation = testSingleAnimation(`${timeString} ${timeString}`);
 | 
						|
				expect(animation.duration).toBe(ms);
 | 
						|
				expect(animation.delay).toBe(ms);
 | 
						|
			});
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses curve', () => {
 | 
						|
			const curves = getCurves();
 | 
						|
			Object.entries(curves).forEach(([curveString, curve]) => {
 | 
						|
				const animation = testSingleAnimation(`${curveString}`);
 | 
						|
				expect(animation.curve).toEqual(curve);
 | 
						|
			});
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses duration, curve and delay', () => {
 | 
						|
			const curves = getCurves();
 | 
						|
			Object.entries(curves).forEach(([curveString, curve]) => {
 | 
						|
				const animation1 = testSingleAnimation(`225ms 300ms ${curveString}`);
 | 
						|
				expect(animation1.duration).toBe(225);
 | 
						|
				expect(animation1.delay).toBe(300);
 | 
						|
				expect(animation1.curve).toEqual(curve);
 | 
						|
 | 
						|
				// curve and delay can be swapped
 | 
						|
				const animation2 = testSingleAnimation(`225ms ${curveString} 300ms`);
 | 
						|
				expect(animation2.duration).toBe(225);
 | 
						|
				expect(animation2.delay).toBe(300);
 | 
						|
				expect(animation2.curve).toEqual(curve);
 | 
						|
			});
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses iteration count', () => {
 | 
						|
			expect(testSingleAnimation(`0s 0s ease 2`).iterations).toBe(2);
 | 
						|
			expect(testSingleAnimation(`0s 0s ease 2.5`).iterations).toBe(2.5);
 | 
						|
			expect(testSingleAnimation(`0s 0s ease infinite`).iterations).toBe(Infinity);
 | 
						|
			expect(testSingleAnimation(`2`).iterations).toBe(2);
 | 
						|
			expect(testSingleAnimation(`2.5`).iterations).toBe(2.5);
 | 
						|
			expect(testSingleAnimation(`infinite`).iterations).toBe(Infinity);
 | 
						|
			expect(testSingleAnimation(`1s 2`).iterations).toBe(2);
 | 
						|
			expect(testSingleAnimation(`1s 2.5`).iterations).toBe(2.5);
 | 
						|
			expect(testSingleAnimation(`1s infinite`).iterations).toBe(Infinity);
 | 
						|
			expect(testSingleAnimation(`ease 2`).iterations).toBe(2);
 | 
						|
			expect(testSingleAnimation(`ease 2.5`).iterations).toBe(2.5);
 | 
						|
			expect(testSingleAnimation(`ease infinite`).iterations).toBe(Infinity);
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses direction', () => {
 | 
						|
			expect(testSingleAnimation(`1s`).isReverse).toBe(false);
 | 
						|
			expect(testSingleAnimation(`1s normal`).isReverse).toBe(false);
 | 
						|
			expect(testSingleAnimation(`1s reverse`).isReverse).toBe(true);
 | 
						|
			expect(testSingleAnimation(`1s 1s reverse`).isReverse).toBe(true);
 | 
						|
			expect(testSingleAnimation(`1s 1s ease reverse`).isReverse).toBe(true);
 | 
						|
			expect(testSingleAnimation(`1s 1s ease 2 reverse`).isReverse).toBe(true);
 | 
						|
			expect(testSingleAnimation(`1s 1s ease infinite reverse`).isReverse).toBe(true);
 | 
						|
			expect(testSingleAnimation(`1s ease reverse`).isReverse).toBe(true);
 | 
						|
			expect(testSingleAnimation(`1s ease 1s reverse`).isReverse).toBe(true);
 | 
						|
			expect(testSingleAnimation(`1s ease 1s 2 reverse`).isReverse).toBe(true);
 | 
						|
 | 
						|
			// unsupported values should still work
 | 
						|
			expect(testSingleAnimation(`1s alternate`).isReverse).toBe(false);
 | 
						|
			expect(testSingleAnimation(`1s alternate-reverse`).isReverse).toBe(false);
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses fill-mode', () => {
 | 
						|
			expect(testSingleAnimation(`1s`).isForwards).toBe(false);
 | 
						|
			expect(testSingleAnimation(`1s none`).isForwards).toBe(false);
 | 
						|
			expect(testSingleAnimation(`1s backwards`).isForwards).toBe(false);
 | 
						|
 | 
						|
			expect(testSingleAnimation(`1s both`).isForwards).toBe(true);
 | 
						|
			expect(testSingleAnimation(`1s forwards`).isForwards).toBe(true);
 | 
						|
			expect(testSingleAnimation(`1s 1s forwards`).isForwards).toBe(true);
 | 
						|
			expect(testSingleAnimation(`1s 1s ease forwards`).isForwards).toBe(true);
 | 
						|
			expect(testSingleAnimation(`1s 1s ease 2 forwards`).isForwards).toBe(true);
 | 
						|
			expect(testSingleAnimation(`1s 1s ease infinite forwards`).isForwards).toBe(true);
 | 
						|
			expect(testSingleAnimation(`1s ease forwards`).isForwards).toBe(true);
 | 
						|
			expect(testSingleAnimation(`1s ease 1s forwards`).isForwards).toBe(true);
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses play-state', () => {
 | 
						|
			// TODO: implement play-state?
 | 
						|
 | 
						|
			expect(testSingleAnimation(`1s`)).not.toBeUndefined();
 | 
						|
			expect(testSingleAnimation(`1s running`)).not.toBeUndefined();
 | 
						|
			expect(testSingleAnimation(`1s paused`)).not.toBeUndefined();
 | 
						|
			expect(testSingleAnimation(`1s 1s paused`)).not.toBeUndefined();
 | 
						|
			expect(testSingleAnimation(`1s 1s ease paused`)).not.toBeUndefined();
 | 
						|
			expect(testSingleAnimation(`1s 1s ease 2 paused`)).not.toBeUndefined();
 | 
						|
			expect(testSingleAnimation(`1s 1s ease infinite paused`)).not.toBeUndefined();
 | 
						|
			expect(testSingleAnimation(`1s ease paused`)).not.toBeUndefined();
 | 
						|
			expect(testSingleAnimation(`1s ease 1s paused`)).not.toBeUndefined();
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses animation name', () => {
 | 
						|
			expect(testSingleAnimation(`1s`).name).toBe('');
 | 
						|
			expect(testSingleAnimation(`1s fade`).name).toBe('fade');
 | 
						|
			expect(testSingleAnimation(`1s 'fade'`).name).toBe('fade');
 | 
						|
			expect(testSingleAnimation(`1s "fade"`).name).toBe('fade');
 | 
						|
 | 
						|
			expect(testSingleAnimation(`1s fade-in`).name).toBe('fade-in');
 | 
						|
			expect(testSingleAnimation(`1s 'fade-in'`).name).toBe('fade-in');
 | 
						|
			expect(testSingleAnimation(`1s "fade-in"`).name).toBe('fade-in');
 | 
						|
 | 
						|
			expect(testSingleAnimation(`1s fade_in`).name).toBe('fade_in');
 | 
						|
			expect(testSingleAnimation(`1s 'fade_in'`).name).toBe('fade_in');
 | 
						|
			expect(testSingleAnimation(`1s "fade_in"`).name).toBe('fade_in');
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses MDN example: 3s ease-in 1s 2 reverse both paused slidein', () => {
 | 
						|
			const animation = testSingleAnimation(`3s ease-in 1s 2 reverse both paused slidein`);
 | 
						|
			expect(animation.duration).toBe(3000);
 | 
						|
			expect(animation.delay).toBe(1000);
 | 
						|
			expect(animation.curve).toBe(CoreTypes.AnimationCurve.easeIn);
 | 
						|
			expect(animation.iterations).toBe(2);
 | 
						|
			expect(animation.isReverse).toBe(true);
 | 
						|
			expect(animation.isForwards).toBe(true);
 | 
						|
			expect(animation.name).toBe('slidein');
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses MDN example: 3s linear 1s slidein', () => {
 | 
						|
			const animation = testSingleAnimation(`3s linear 1s slidein`);
 | 
						|
			expect(animation.duration).toBe(3000);
 | 
						|
			expect(animation.delay).toBe(1000);
 | 
						|
			expect(animation.curve).toBe(CoreTypes.AnimationCurve.linear);
 | 
						|
			expect(animation.name).toBe('slidein');
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses MDN example: 3s linear slidein, 3s ease-out 5s slideout', () => {
 | 
						|
			const [animation1, animation2] = testMultipleAnimations(`3s linear slidein, 3s ease-out 5s slideout`);
 | 
						|
 | 
						|
			expect(animation1.duration).toBe(3000);
 | 
						|
			expect(animation1.curve).toBe(CoreTypes.AnimationCurve.linear);
 | 
						|
			expect(animation1.name).toBe('slidein');
 | 
						|
 | 
						|
			expect(animation2.duration).toBe(3000);
 | 
						|
			expect(animation2.delay).toBe(5000);
 | 
						|
			expect(animation2.curve).toBe(CoreTypes.AnimationCurve.easeOut);
 | 
						|
			expect(animation2.name).toBe('slideout');
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses SPEC example: 3s none backwards', () => {
 | 
						|
			const animation = testSingleAnimation(`3s none backwards`);
 | 
						|
			expect(animation.duration).toBe(3000);
 | 
						|
			expect(animation.isForwards).toBe(false);
 | 
						|
			expect(animation.name).toBe('backwards');
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses SPEC example: 3s backwards', () => {
 | 
						|
			const animation = testSingleAnimation(`3s backwards`);
 | 
						|
			expect(animation.duration).toBe(3000);
 | 
						|
			expect(animation.isForwards).toBe(false);
 | 
						|
			expect(animation.name).toBe('');
 | 
						|
		});
 | 
						|
 | 
						|
		it('does not throw on invalid values', () => {
 | 
						|
			// prettier-ignore
 | 
						|
			const invalidValues = [
 | 
						|
				'asd',
 | 
						|
				'$#-1401;lk',
 | 
						|
				'1 1 1 1 1 1 1 1 1 1',
 | 
						|
				'1s 1s 1s 1s',
 | 
						|
				',,,,',
 | 
						|
				'$,1s-_1:s>',
 | 
						|
				Infinity.toString(),
 | 
						|
				NaN.toString(),
 | 
						|
				null,
 | 
						|
				undefined
 | 
						|
			];
 | 
						|
 | 
						|
			invalidValues.forEach((value) => {
 | 
						|
				expect(() => testSingleAnimation(value)).not.toThrow();
 | 
						|
			});
 | 
						|
		});
 | 
						|
	});
 | 
						|
 | 
						|
	describe('keyframe-parser', () => {
 | 
						|
		// helper function
 | 
						|
		function testKeyframesArrayFromCSS(css: string, expectedName?: string): KeyframeInfo[] {
 | 
						|
			const ast = cssTreeParse(css, 'test.css');
 | 
						|
			const rules = ast.stylesheet.rules;
 | 
						|
			const firstRule = rules[0];
 | 
						|
 | 
						|
			expect(rules.length).toBe(1);
 | 
						|
			expect(firstRule.type).toBe('keyframes');
 | 
						|
 | 
						|
			const name = firstRule.name;
 | 
						|
			const keyframes = firstRule.keyframes;
 | 
						|
 | 
						|
			if (expectedName) {
 | 
						|
				expect(name).toBe(expectedName);
 | 
						|
			}
 | 
						|
 | 
						|
			return CssAnimationParser.keyframesArrayFromCSS(keyframes);
 | 
						|
		}
 | 
						|
 | 
						|
		it('parses "from" keyframes', () => {
 | 
						|
			const res = testKeyframesArrayFromCSS(
 | 
						|
				`@keyframes fade {
 | 
						|
					from { opacity: 0; }
 | 
						|
				}`,
 | 
						|
				'fade',
 | 
						|
			);
 | 
						|
 | 
						|
			expect(res.length).toBe(1);
 | 
						|
 | 
						|
			const [from] = res;
 | 
						|
			expect(from.duration).toBe(0);
 | 
						|
			expect(from.declarations.length).toBe(1);
 | 
						|
			expect(from.declarations[0].property).toBe('opacity');
 | 
						|
			expect(from.declarations[0].value).toBe(0);
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses "to" keyframes', () => {
 | 
						|
			const res = testKeyframesArrayFromCSS(
 | 
						|
				`@keyframes fade {
 | 
						|
					to { opacity: 1; }
 | 
						|
				}`,
 | 
						|
				'fade',
 | 
						|
			);
 | 
						|
 | 
						|
			expect(res.length).toBe(1);
 | 
						|
 | 
						|
			const [to] = res;
 | 
						|
			expect(to.duration).toBe(1);
 | 
						|
			expect(to.declarations.length).toBe(1);
 | 
						|
			expect(to.declarations[0].property).toBe('opacity');
 | 
						|
			expect(to.declarations[0].value).toBe(1);
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses "from/to" keyframes', () => {
 | 
						|
			const res = testKeyframesArrayFromCSS(
 | 
						|
				`@keyframes fade {
 | 
						|
					from { opacity: 0; }
 | 
						|
					to { opacity: 1; }
 | 
						|
				}`,
 | 
						|
				'fade',
 | 
						|
			);
 | 
						|
 | 
						|
			expect(res.length).toBe(2);
 | 
						|
 | 
						|
			const [from, to] = res;
 | 
						|
			expect(from.duration).toBe(0);
 | 
						|
			expect(from.declarations.length).toBe(1);
 | 
						|
			expect(from.declarations[0].property).toBe('opacity');
 | 
						|
			expect(from.declarations[0].value).toBe(0);
 | 
						|
 | 
						|
			expect(to.duration).toBe(1);
 | 
						|
			expect(to.declarations.length).toBe(1);
 | 
						|
			expect(to.declarations[0].property).toBe('opacity');
 | 
						|
			expect(to.declarations[0].value).toBe(1);
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses "0%" keyframes', () => {
 | 
						|
			const res = testKeyframesArrayFromCSS(
 | 
						|
				`@keyframes fade {
 | 
						|
					0% { opacity: 0; }
 | 
						|
				}`,
 | 
						|
				'fade',
 | 
						|
			);
 | 
						|
 | 
						|
			expect(res.length).toBe(1);
 | 
						|
 | 
						|
			const [from] = res;
 | 
						|
			expect(from.duration).toBe(0);
 | 
						|
			expect(from.declarations.length).toBe(1);
 | 
						|
			expect(from.declarations[0].property).toBe('opacity');
 | 
						|
			expect(from.declarations[0].value).toBe(0);
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses "100%" keyframes', () => {
 | 
						|
			const res = testKeyframesArrayFromCSS(
 | 
						|
				`@keyframes fade {
 | 
						|
					100% { opacity: 1; }
 | 
						|
				}`,
 | 
						|
				'fade',
 | 
						|
			);
 | 
						|
 | 
						|
			expect(res.length).toBe(1);
 | 
						|
 | 
						|
			const [to] = res;
 | 
						|
			expect(to.duration).toBe(1);
 | 
						|
			expect(to.declarations.length).toBe(1);
 | 
						|
			expect(to.declarations[0].property).toBe('opacity');
 | 
						|
			expect(to.declarations[0].value).toBe(1);
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses "0%/100%" keyframes', () => {
 | 
						|
			const res = testKeyframesArrayFromCSS(
 | 
						|
				`@keyframes fade {
 | 
						|
					0% { opacity: 0; }
 | 
						|
					100% { opacity: 1; }
 | 
						|
				}`,
 | 
						|
				'fade',
 | 
						|
			);
 | 
						|
 | 
						|
			expect(res.length).toBe(2);
 | 
						|
 | 
						|
			const [from, to] = res;
 | 
						|
			expect(from.duration).toBe(0);
 | 
						|
			expect(from.declarations.length).toBe(1);
 | 
						|
			expect(from.declarations[0].property).toBe('opacity');
 | 
						|
			expect(from.declarations[0].value).toBe(0);
 | 
						|
 | 
						|
			expect(to.duration).toBe(1);
 | 
						|
			expect(to.declarations.length).toBe(1);
 | 
						|
			expect(to.declarations[0].property).toBe('opacity');
 | 
						|
			expect(to.declarations[0].value).toBe(1);
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses "via" keyframes', () => {
 | 
						|
			const res = testKeyframesArrayFromCSS(
 | 
						|
				`@keyframes fade {
 | 
						|
					50% { opacity: 0.5; }
 | 
						|
				}`,
 | 
						|
				'fade',
 | 
						|
			);
 | 
						|
 | 
						|
			expect(res.length).toBe(1);
 | 
						|
 | 
						|
			const [via] = res;
 | 
						|
			expect(via.duration).toBe(0.5);
 | 
						|
			expect(via.declarations.length).toBe(1);
 | 
						|
			expect(via.declarations[0].property).toBe('opacity');
 | 
						|
			expect(via.declarations[0].value).toBe(0.5);
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses multiple keyframes', () => {
 | 
						|
			const res = testKeyframesArrayFromCSS(
 | 
						|
				`@keyframes fade {
 | 
						|
					0% { opacity: 0; }
 | 
						|
					50% { opacity: 0.5; }
 | 
						|
					100% { opacity: 1; }
 | 
						|
				}`,
 | 
						|
				'fade',
 | 
						|
			);
 | 
						|
 | 
						|
			expect(res.length).toBe(3);
 | 
						|
 | 
						|
			const [from, via, to] = res;
 | 
						|
			expect(from.duration).toBe(0);
 | 
						|
			expect(from.declarations.length).toBe(1);
 | 
						|
			expect(from.declarations[0].property).toBe('opacity');
 | 
						|
			expect(from.declarations[0].value).toBe(0);
 | 
						|
 | 
						|
			expect(via.duration).toBe(0.5);
 | 
						|
			expect(via.declarations.length).toBe(1);
 | 
						|
			expect(via.declarations[0].property).toBe('opacity');
 | 
						|
			expect(via.declarations[0].value).toBe(0.5);
 | 
						|
 | 
						|
			expect(to.duration).toBe(1);
 | 
						|
			expect(to.declarations.length).toBe(1);
 | 
						|
			expect(to.declarations[0].property).toBe('opacity');
 | 
						|
			expect(to.declarations[0].value).toBe(1);
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses multiple keyframes with mixed stops', () => {
 | 
						|
			const res = testKeyframesArrayFromCSS(
 | 
						|
				`@keyframes fade {
 | 
						|
					from { opacity: 0; }
 | 
						|
					50% { opacity: 0.5; }
 | 
						|
					to { opacity: 1; }
 | 
						|
				}`,
 | 
						|
				'fade',
 | 
						|
			);
 | 
						|
 | 
						|
			expect(res.length).toBe(3);
 | 
						|
 | 
						|
			const [from, via, to] = res;
 | 
						|
			expect(from.duration).toBe(0);
 | 
						|
			expect(from.declarations.length).toBe(1);
 | 
						|
			expect(from.declarations[0].property).toBe('opacity');
 | 
						|
			expect(from.declarations[0].value).toBe(0);
 | 
						|
 | 
						|
			expect(via.duration).toBe(0.5);
 | 
						|
			expect(via.declarations.length).toBe(1);
 | 
						|
			expect(via.declarations[0].property).toBe('opacity');
 | 
						|
			expect(via.declarations[0].value).toBe(0.5);
 | 
						|
 | 
						|
			expect(to.duration).toBe(1);
 | 
						|
			expect(to.declarations.length).toBe(1);
 | 
						|
			expect(to.declarations[0].property).toBe('opacity');
 | 
						|
			expect(to.declarations[0].value).toBe(1);
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses duplicate keyframes', () => {
 | 
						|
			const res = testKeyframesArrayFromCSS(
 | 
						|
				`@keyframes fade {
 | 
						|
					0% { opacity: 0; }
 | 
						|
					50% { opacity: 0.5; }
 | 
						|
					50% { translateX: 100; }
 | 
						|
					100% { opacity: 1; }
 | 
						|
				}`,
 | 
						|
				'fade',
 | 
						|
			);
 | 
						|
 | 
						|
			expect(res.length).toBe(3);
 | 
						|
 | 
						|
			const [from, via, to] = res;
 | 
						|
			expect(from.duration).toBe(0);
 | 
						|
			expect(from.declarations.length).toBe(1);
 | 
						|
			expect(from.declarations[0].property).toBe('opacity');
 | 
						|
			expect(from.declarations[0].value).toBe(0);
 | 
						|
 | 
						|
			expect(via.duration).toBe(0.5);
 | 
						|
			expect(via.declarations.length).toBe(2);
 | 
						|
			expect(via.declarations[0].property).toBe('opacity');
 | 
						|
			expect(via.declarations[0].value).toBe(0.5);
 | 
						|
			expect(via.declarations[1].property).toBe('translateX');
 | 
						|
			expect(via.declarations[1].value).toBe(100);
 | 
						|
 | 
						|
			expect(to.duration).toBe(1);
 | 
						|
			expect(to.declarations.length).toBe(1);
 | 
						|
			expect(to.declarations[0].property).toBe('opacity');
 | 
						|
			expect(to.declarations[0].value).toBe(1);
 | 
						|
		});
 | 
						|
 | 
						|
		it('parses timing functions in keyframes', () => {
 | 
						|
			const res = testKeyframesArrayFromCSS(
 | 
						|
				`@keyframes fade {
 | 
						|
					from { opacity: 0; animation-timing-function: ease-in; }
 | 
						|
					to { opacity: 1; }
 | 
						|
				}`,
 | 
						|
				'fade',
 | 
						|
			);
 | 
						|
 | 
						|
			expect(res.length).toBe(2);
 | 
						|
 | 
						|
			const [from, to] = res;
 | 
						|
			expect(from.curve).toBe(CoreTypes.AnimationCurve.easeIn);
 | 
						|
			expect(to.curve).not.toBeDefined();
 | 
						|
		});
 | 
						|
 | 
						|
		it('sorts multiple keyframes with mixed order', () => {
 | 
						|
			const res = testKeyframesArrayFromCSS(
 | 
						|
				`@keyframes fade {
 | 
						|
					100% { opacity: 1; }
 | 
						|
					0% { opacity: 0; }
 | 
						|
					50% { opacity: 0.5; }
 | 
						|
				}`,
 | 
						|
				'fade',
 | 
						|
			);
 | 
						|
 | 
						|
			expect(res.length).toBe(3);
 | 
						|
 | 
						|
			const [from, via, to] = res;
 | 
						|
			expect(from.duration).toBe(0);
 | 
						|
			expect(from.declarations.length).toBe(1);
 | 
						|
			expect(from.declarations[0].property).toBe('opacity');
 | 
						|
			expect(from.declarations[0].value).toBe(0);
 | 
						|
 | 
						|
			expect(via.duration).toBe(0.5);
 | 
						|
			expect(via.declarations.length).toBe(1);
 | 
						|
			expect(via.declarations[0].property).toBe('opacity');
 | 
						|
			expect(via.declarations[0].value).toBe(0.5);
 | 
						|
 | 
						|
			expect(to.duration).toBe(1);
 | 
						|
			expect(to.declarations.length).toBe(1);
 | 
						|
			expect(to.declarations[0].property).toBe('opacity');
 | 
						|
			expect(to.declarations[0].value).toBe(1);
 | 
						|
		});
 | 
						|
	});
 | 
						|
});
 |