From 946e74849f94f6104dd357669d8632da0ecca391 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 5 May 2021 17:09:24 +0200 Subject: [PATCH] simplify animation tests and improve stability This will make sure that we tackle a few additional edge cases: - When the `name` is the same as a reserved keyword, then it will be used as a `name` as well. E.g.: 1s ease ease infinite; Will result in a name of `ease` as well. - We take care of trimming and multiple spaces. - We don't generate 8k tests anymore, which means that these specific tests only take a second instead of 10 seconds. --- src/util/parseAnimationValue.js | 48 +++++++---- tests/parseAnimationValue.test.js | 134 ++++++++---------------------- 2 files changed, 69 insertions(+), 113 deletions(-) diff --git a/src/util/parseAnimationValue.js b/src/util/parseAnimationValue.js index 8e883d8b6bac..64bab84111d0 100644 --- a/src/util/parseAnimationValue.js +++ b/src/util/parseAnimationValue.js @@ -6,26 +6,46 @@ const TIMINGS = new Set(['linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out'] const TIMING_FNS = ['cubic-bezier', 'steps'] const COMMA = /\,(?![^(]*\))/g // Comma separator that is not located between brackets. E.g.: `cubiz-bezier(a, b, c)` these don't count. -const SPACE = /\ (?![^(]*\))/g // Similar to the one above, but with spaces instead. +const SPACE = /\ +(?![^(]*\))/g // Similar to the one above, but with spaces instead. const TIME = /^(-?[\d.]+m?s)$/ const DIGIT = /^(\d+)$/ export default function parseAnimationValue(input) { - const animations = input.split(COMMA) - const result = animations.map((animation) => { - const result = {} - const parts = animation.split(SPACE) + let animations = input.split(COMMA) + let result = animations.map((animation) => { + let result = {} + let parts = animation.trim().split(SPACE) + let seen = new Set() for (let part of parts) { - if (DIRECTIONS.has(part)) result.direction = part - else if (PLAY_STATES.has(part)) result.playState = part - else if (FILL_MODES.has(part)) result.fillMode = part - else if (ITERATION_COUNTS.has(part)) result.iterationCount = part - else if (TIMINGS.has(part)) result.timingFunction = part - else if (TIMING_FNS.some((f) => part.startsWith(`${f}(`))) result.timingFunction = part - else if (TIME.test(part)) result[result.duration === undefined ? 'duration' : 'delay'] = part - else if (DIGIT.test(part)) result.iterationCount = part - else result.name = part + if (!seen.has('DIRECTIONS') && DIRECTIONS.has(part)) { + result.direction = part + seen.add('DIRECTIONS') + } else if (!seen.has('PLAY_STATES') && PLAY_STATES.has(part)) { + result.playState = part + seen.add('PLAY_STATES') + } else if (!seen.has('FILL_MODES') && FILL_MODES.has(part)) { + result.fillMode = part + seen.add('FILL_MODES') + } else if ( + !seen.has('ITERATION_COUNTS') && + (ITERATION_COUNTS.has(part) || DIGIT.test(part)) + ) { + result.iterationCount = part + seen.add('ITERATION_COUNTS') + } else if (!seen.has('TIMING_FUNCTION') && TIMINGS.has(part)) { + result.timingFunction = part + seen.add('TIMING_FUNCTION') + } else if (!seen.has('TIMING_FUNCTION') && TIMING_FNS.some((f) => part.startsWith(`${f}(`))) { + result.timingFunction = part + seen.add('TIMING_FUNCTION') + } else if (!seen.has('DURATION') && TIME.test(part)) { + result.duration = part + seen.add('DURATION') + } else if (!seen.has('DELAY') && TIME.test(part)) { + result.delay = part + seen.add('DELAY') + } else result.name = part } return result diff --git a/tests/parseAnimationValue.test.js b/tests/parseAnimationValue.test.js index 200a77323cdb..38e360e7f07f 100644 --- a/tests/parseAnimationValue.test.js +++ b/tests/parseAnimationValue.test.js @@ -1,27 +1,22 @@ import parseAnimationValue from '../src/util/parseAnimationValue' -import { produce } from './util/produce' describe('Tailwind Defaults', () => { it.each([ [ 'spin 1s linear infinite', - { name: 'spin', duration: '1s', timingFunction: 'linear', iterationCount: 'infinite' }, - ], - [ - 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite', { - name: 'ping', + name: 'spin', duration: '1s', - timingFunction: 'cubic-bezier(0, 0, 0.2, 1)', + timingFunction: 'linear', iterationCount: 'infinite', }, ], [ - 'pulse 2s cubic-bezier(0.4, 0, 0.6) infinite', + 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite', { - name: 'pulse', - duration: '2s', - timingFunction: 'cubic-bezier(0.4, 0, 0.6)', + name: 'ping', + duration: '1s', + timingFunction: 'cubic-bezier(0, 0, 0.2, 1)', iterationCount: 'infinite', }, ], @@ -48,7 +43,12 @@ describe('MDN Examples', () => { ], [ 'slidein 3s linear 1s', - { delay: '1s', duration: '3s', name: 'slidein', timingFunction: 'linear' }, + { + delay: '1s', + duration: '3s', + name: 'slidein', + timingFunction: 'linear', + }, ], ['slidein 3s', { duration: '3s', name: 'slidein' }], ])('should be possible to parse: "%s"', (input, expected) => { @@ -59,44 +59,28 @@ describe('MDN Examples', () => { describe('duration & delay', () => { it.each([ // Positive seconds (integer) - ['spin 1s 1s linear', { duration: '1s', delay: '1s' }], ['spin 2s 1s linear', { duration: '2s', delay: '1s' }], - ['spin 1s 2s linear', { duration: '1s', delay: '2s' }], // Negative seconds (integer) - ['spin -1s -1s linear', { duration: '-1s', delay: '-1s' }], ['spin -2s -1s linear', { duration: '-2s', delay: '-1s' }], - ['spin -1s -2s linear', { duration: '-1s', delay: '-2s' }], // Positive seconds (float) - ['spin 1.321s 1.321s linear', { duration: '1.321s', delay: '1.321s' }], ['spin 2.321s 1.321s linear', { duration: '2.321s', delay: '1.321s' }], - ['spin 1.321s 2.321s linear', { duration: '1.321s', delay: '2.321s' }], // Negative seconds (float) - ['spin -1.321s -1.321s linear', { duration: '-1.321s', delay: '-1.321s' }], ['spin -2.321s -1.321s linear', { duration: '-2.321s', delay: '-1.321s' }], - ['spin -1.321s -2.321s linear', { duration: '-1.321s', delay: '-2.321s' }], // Positive milliseconds (integer) - ['spin 100ms 100ms linear', { duration: '100ms', delay: '100ms' }], ['spin 200ms 100ms linear', { duration: '200ms', delay: '100ms' }], - ['spin 100ms 200ms linear', { duration: '100ms', delay: '200ms' }], // Negative milliseconds (integer) - ['spin -100ms -100ms linear', { duration: '-100ms', delay: '-100ms' }], ['spin -200ms -100ms linear', { duration: '-200ms', delay: '-100ms' }], - ['spin -100ms -200ms linear', { duration: '-100ms', delay: '-200ms' }], // Positive milliseconds (float) - ['spin 100.321ms 100.321ms linear', { duration: '100.321ms', delay: '100.321ms' }], ['spin 200.321ms 100.321ms linear', { duration: '200.321ms', delay: '100.321ms' }], - ['spin 100.321ms 200.321ms linear', { duration: '100.321ms', delay: '200.321ms' }], // Negative milliseconds (float) - ['spin -100.321ms -100.321ms linear', { duration: '-100.321ms', delay: '-100.321ms' }], ['spin -200.321ms -100.321ms linear', { duration: '-200.321ms', delay: '-100.321ms' }], - ['spin -100.321ms -200.321ms linear', { duration: '-100.321ms', delay: '-200.321ms' }], ])('should be possible to parse "%s" into %o', (input, { duration, delay }) => { const parsed = parseAnimationValue(input) expect(parsed.duration).toEqual(duration) @@ -127,34 +111,6 @@ describe('iteration count', () => { ) }) -describe('iteration count', () => { - it.each([ - // Number - ['1 spin 200s 100s linear', '1'], - ['spin 2 200s 100s linear', '2'], - ['spin 200s 3 100s linear', '3'], - ['spin 200s 100s 4 linear', '4'], - ['spin 200s 100s linear 5', '5'], - ['100 spin 200s 100s linear', '100'], - ['spin 200 200s 100s linear', '200'], - ['spin 200s 300 100s linear', '300'], - ['spin 200s 100s 400 linear', '400'], - ['spin 200s 100s linear 500', '500'], - - // Infinite - ['infinite spin 200s 100s linear', 'infinite'], - ['spin infinite 200s 100s linear', 'infinite'], - ['spin 200s infinite 100s linear', 'infinite'], - ['spin 200s 100s infinite linear', 'infinite'], - ['spin 200s 100s linear infinite', 'infinite'], - ])( - 'should be possible to parse "%s" with an iteraction count of "%s"', - (input, iterationCount) => { - expect(parseAnimationValue(input).iterationCount).toEqual(iterationCount) - } - ) -}) - describe('multiple animations', () => { it('should be possible to parse multiple applications at once', () => { const input = [ @@ -166,7 +122,12 @@ describe('multiple animations', () => { const parsed = parseAnimationValue(input) expect(parsed).toHaveLength(3) expect(parsed).toEqual([ - { name: 'spin', duration: '1s', timingFunction: 'linear', iterationCount: 'infinite' }, + { + name: 'spin', + duration: '1s', + timingFunction: 'linear', + iterationCount: 'infinite', + }, { name: 'ping', duration: '1s', @@ -183,46 +144,21 @@ describe('multiple animations', () => { }) }) -describe('randomized crazy big examples', () => { - function reOrder(input, offset = 0) { - return [...input.slice(offset), ...input.slice(0, offset)] - } - - it.each( - produce((choose) => { - const direction = choose('normal', 'reverse', 'alternate', 'alternate-reverse') - const playState = choose('running', 'paused') - const fillMode = choose('none', 'forwards', 'backwards', 'both') - const iterationCount = choose('infinite', '1', '100') - const timingFunction = choose( - 'linear', - 'ease', - 'ease-in', - 'ease-out', - 'ease-in-out', - 'cubic-bezier(0, 0, 0.2, 1)', - 'steps(4, end)' - ) - const name = choose('animation-name-a', 'animation-name-b') - const inputArgs = [direction, playState, fillMode, iterationCount, timingFunction, name] - const orderOffset = choose(...Array(inputArgs.length).keys()) - - return [ - // Input - reOrder(inputArgs, orderOffset).join(' '), - - // Output - { - direction, - playState, - fillMode, - iterationCount, - timingFunction, - name, - }, - ] - }) - )('should be possible to parse "%s"', (input, output) => { - expect(parseAnimationValue(input)).toEqual(output) - }) +it.each` + input | direction | playState | fillMode | iterationCount | timingFunction | duration | delay | name + ${'1s spin 1s infinite'} | ${undefined} | ${undefined} | ${undefined} | ${'infinite'} | ${undefined} | ${'1s'} | ${'1s'} | ${'spin'} + ${'infinite infinite 1s 1s'} | ${undefined} | ${undefined} | ${undefined} | ${'infinite'} | ${undefined} | ${'1s'} | ${'1s'} | ${'infinite'} + ${'ease 1s ease 1s'} | ${undefined} | ${undefined} | ${undefined} | ${undefined} | ${'ease'} | ${'1s'} | ${'1s'} | ${'ease'} + ${'normal paused backwards infinite ease-in 1s 2s name'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'} + ${'paused backwards infinite ease-in 1s 2s name normal'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'} + ${'backwards infinite ease-in 1s 2s name normal paused'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'} + ${'infinite ease-in 1s 2s name normal paused backwards'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'} + ${'ease-in 1s 2s name normal paused backwards infinite'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'} + ${'1s 2s name normal paused backwards infinite ease-in'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'} + ${'2s name normal paused backwards infinite ease-in 1s'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'2s'} | ${'1s'} | ${'name'} + ${'name normal paused backwards infinite ease-in 1s 2s'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'} + ${' name normal paused backwards infinite ease-in 1s 2s '} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'} +`('should parse "$input" correctly', ({ input, ...expected }) => { + let parsed = parseAnimationValue(input) + expect(parsed).toEqual(expected) })