-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
50fa810
commit 6b35af3
Showing
3 changed files
with
305 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
import parse from '../src/util/animationParser' | ||
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', | ||
duration: '1s', | ||
timingFunction: 'cubic-bezier(0, 0, 0.2, 1)', | ||
iterationCount: 'infinite', | ||
}, | ||
], | ||
[ | ||
'pulse 2s cubic-bezier(0.4, 0, 0.6) infinite', | ||
{ | ||
name: 'pulse', | ||
duration: '2s', | ||
timingFunction: 'cubic-bezier(0.4, 0, 0.6)', | ||
iterationCount: 'infinite', | ||
}, | ||
], | ||
['bounce 1s infinite', { name: 'bounce', duration: '1s', iterationCount: 'infinite' }], | ||
])('should be possible to parse: "%s"', (input, expected) => { | ||
expect(parse(input)).toEqual(expected) | ||
}) | ||
}) | ||
|
||
describe('MDN Examples', () => { | ||
it.each([ | ||
[ | ||
'3s ease-in 1s 2 reverse both paused slidein', | ||
{ | ||
delay: '1s', | ||
direction: 'reverse', | ||
duration: '3s', | ||
fillMode: 'both', | ||
iterationCount: '2', | ||
name: 'slidein', | ||
playState: 'paused', | ||
timingFunction: 'ease-in', | ||
}, | ||
], | ||
[ | ||
'slidein 3s linear 1s', | ||
{ delay: '1s', duration: '3s', name: 'slidein', timingFunction: 'linear' }, | ||
], | ||
['slidein 3s', { duration: '3s', name: 'slidein' }], | ||
])('should be possible to parse: "%s"', (input, expected) => { | ||
expect(parse(input)).toEqual(expected) | ||
}) | ||
}) | ||
|
||
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 = parse(input) | ||
expect(parsed.duration).toEqual(duration) | ||
expect(parsed.delay).toEqual(delay) | ||
}) | ||
}) | ||
|
||
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'], | ||
|
||
// 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(parse(input).iterationCount).toEqual(iterationCount) | ||
} | ||
) | ||
}) | ||
|
||
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(parse(input).iterationCount).toEqual(iterationCount) | ||
} | ||
) | ||
}) | ||
|
||
describe('multiple animations', () => { | ||
it('should be possible to parse multiple applications at once', () => { | ||
const input = [ | ||
'spin 1s linear infinite', | ||
'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite', | ||
'pulse 2s cubic-bezier(0.4, 0, 0.6) infinite', | ||
].join(',') | ||
|
||
const parsed = parse(input) | ||
expect(parsed).toHaveLength(3) | ||
expect(parsed).toEqual([ | ||
{ name: 'spin', duration: '1s', timingFunction: 'linear', iterationCount: 'infinite' }, | ||
{ | ||
name: 'ping', | ||
duration: '1s', | ||
timingFunction: 'cubic-bezier(0, 0, 0.2, 1)', | ||
iterationCount: 'infinite', | ||
}, | ||
{ | ||
name: 'pulse', | ||
duration: '2s', | ||
timingFunction: 'cubic-bezier(0.4, 0, 0.6)', | ||
iterationCount: 'infinite', | ||
}, | ||
]) | ||
}) | ||
}) | ||
|
||
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(parse(input)).toEqual(output) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// Full credit goes to: https://github.com/purplestone/exhaust | ||
// However, it is modified so that it is a bit more modern | ||
export function produce(blueprint) { | ||
let groups = [] | ||
|
||
// Call the blueprint once so that we can collect all the possible values we | ||
// spit out in the callback function. | ||
blueprint((...args) => { | ||
if (args.length <= 0) throw new Error('Blueprint callback must have at least a single value') | ||
groups.push(args) | ||
}) | ||
|
||
// Calculate how many combinations there are | ||
let iterations = groups.reduce((total, current) => total * current.length, 1) | ||
|
||
// Calculate all the combinations possible | ||
let zippedGroups = [] | ||
let currentIteration = iterations | ||
groups.forEach((a) => { | ||
let n = a.length | ||
currentIteration = currentIteration / n | ||
let iS = -1 | ||
let aS = [] | ||
|
||
for (let i = 0; i < iterations; i++) { | ||
if (!(i % currentIteration)) iS++ | ||
aS.push(a[iS % n]) | ||
} | ||
zippedGroups.push(aS) | ||
}) | ||
|
||
// Transpose the matrix, so that we can get the correct rows/columns structure | ||
// again. | ||
zippedGroups = zippedGroups[0].map((_, i) => zippedGroups.map((o) => o[i])) | ||
|
||
// Call the blueprint again, but now give the inner function a single value | ||
// every time so that we can build up the final result with single values. | ||
return zippedGroups.map((group) => { | ||
let i = 0 | ||
return blueprint(() => group[i++]) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
const DIRECTIONS = new Set(['normal', 'reverse', 'alternate', 'alternate-reverse']) | ||
const PLAY_STATES = new Set(['running', 'paused']) | ||
const FILL_MODES = new Set(['none', 'forwards', 'backwards', 'both']) | ||
const ITERATION_COUNTS = new Set(['infinite']) | ||
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 TIME = /^(-?[\d.]+m?s)$/ | ||
const DIGIT = /^(\d+)$/ | ||
|
||
export default function parse(input) { | ||
const animations = input.split(COMMA) | ||
const result = animations.map((animation) => { | ||
const result = {} | ||
const parts = animation.split(SPACE) | ||
|
||
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 | ||
} | ||
|
||
return result | ||
}) | ||
|
||
return animations.length > 1 ? result : result[0] | ||
} |