2020-04-02 06:34:16 -05:00
|
|
|
// Copyright (c) 2017 Uber Technologies, Inc.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2022-11-22 16:19:35 -06:00
|
|
|
import Tween, { TweenState } from './Tween';
|
2020-04-02 06:34:16 -05:00
|
|
|
|
|
|
|
describe('Tween', () => {
|
|
|
|
const oldNow = Date.now;
|
|
|
|
const nowFn = jest.fn();
|
|
|
|
const oldSetTimeout = window.setTimeout;
|
|
|
|
const setTimeoutFn = jest.fn();
|
|
|
|
const oldRaf = window.requestAnimationFrame;
|
|
|
|
const rafFn = jest.fn();
|
|
|
|
|
|
|
|
const baseOptions = { duration: 10, from: 0, to: 1 };
|
|
|
|
|
2022-11-22 16:19:35 -06:00
|
|
|
jest.useFakeTimers();
|
|
|
|
jest.spyOn(global, 'setTimeout');
|
|
|
|
|
2020-04-02 06:34:16 -05:00
|
|
|
Date.now = nowFn;
|
|
|
|
window.requestAnimationFrame = rafFn;
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
nowFn.mockReset();
|
|
|
|
nowFn.mockReturnValue(0);
|
|
|
|
setTimeoutFn.mockReset();
|
|
|
|
rafFn.mockReset();
|
|
|
|
});
|
|
|
|
|
|
|
|
afterAll(() => {
|
|
|
|
Date.now = oldNow;
|
|
|
|
window.setTimeout = oldSetTimeout;
|
|
|
|
window.requestAnimationFrame = oldRaf;
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('ctor', () => {
|
|
|
|
it('set startTime to the current time', () => {
|
|
|
|
const n = Math.random();
|
|
|
|
nowFn.mockReturnValue(n);
|
|
|
|
const tween = new Tween(baseOptions);
|
|
|
|
expect(tween.startTime).toBe(n);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('adds delay to the startTime', () => {
|
|
|
|
const n = Math.random();
|
|
|
|
nowFn.mockReturnValue(n);
|
|
|
|
const tween = new Tween({ ...baseOptions, delay: 10 });
|
|
|
|
expect(tween.startTime).toBe(n + 10);
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('with callbacks', () => {
|
|
|
|
it('schedules setTimeout if there is a delay', () => {
|
|
|
|
const delay = 10;
|
|
|
|
const tween = new Tween({ ...baseOptions, delay, onUpdate: jest.fn() });
|
2022-11-22 16:19:35 -06:00
|
|
|
expect(setTimeout).lastCalledWith(tween._frameCallback, delay);
|
2020-04-02 06:34:16 -05:00
|
|
|
});
|
|
|
|
|
2020-10-07 05:29:30 -05:00
|
|
|
it('schedules animation frame if there is not a delay', () => {
|
2020-04-02 06:34:16 -05:00
|
|
|
const tween = new Tween({ ...baseOptions, onUpdate: jest.fn() });
|
|
|
|
expect(rafFn).lastCalledWith(tween._frameCallback);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('getCurrent()', () => {
|
|
|
|
it('returns `{done: false, value: from}` when time is before the delay is finished', () => {
|
|
|
|
const tween = new Tween({ ...baseOptions, delay: 1 });
|
|
|
|
const current = tween.getCurrent();
|
|
|
|
expect(current).toEqual({ done: false, value: baseOptions.from });
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('in progress tweens', () => {
|
|
|
|
it('returns `{done: false...`}', () => {
|
|
|
|
const tween = new Tween(baseOptions);
|
|
|
|
nowFn.mockReturnValue(1);
|
|
|
|
const current = tween.getCurrent();
|
|
|
|
expect(current.done).toBe(false);
|
|
|
|
expect(nowFn()).toBeLessThan(tween.startTime + tween.duration);
|
|
|
|
expect(nowFn()).toBeGreaterThan(tween.startTime);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('progresses `{..., value} as time progresses', () => {
|
|
|
|
const tween = new Tween(baseOptions);
|
|
|
|
let lastValue = tween.getCurrent().value;
|
|
|
|
for (let i = 1; i < baseOptions.duration; i++) {
|
|
|
|
nowFn.mockReturnValue(i);
|
|
|
|
const { done, value } = tween.getCurrent();
|
|
|
|
expect(done).toBe(false);
|
|
|
|
expect(value).toBeGreaterThan(lastValue);
|
|
|
|
lastValue = value;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('returns `{done: true, value: to}` when the time is past the duration', () => {
|
|
|
|
const tween = new Tween(baseOptions);
|
|
|
|
nowFn.mockReturnValue(baseOptions.duration);
|
|
|
|
const current = tween.getCurrent();
|
|
|
|
expect(current).toEqual({ done: true, value: baseOptions.to });
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('_frameCallback', () => {
|
|
|
|
it('freezes the callback argument', () => {
|
2022-11-22 16:19:35 -06:00
|
|
|
let current: TweenState | undefined;
|
2021-01-20 00:59:48 -06:00
|
|
|
const fn = jest.fn((_current) => {
|
2020-04-02 06:34:16 -05:00
|
|
|
current = _current;
|
|
|
|
});
|
|
|
|
const tween = new Tween({ ...baseOptions, onUpdate: fn });
|
|
|
|
tween._frameCallback();
|
|
|
|
expect(current).toBeDefined();
|
|
|
|
const copy = { ...current };
|
|
|
|
try {
|
2022-11-22 16:19:35 -06:00
|
|
|
current!.done = !current!.done;
|
2020-04-02 06:34:16 -05:00
|
|
|
// eslint-disable-next-line no-empty
|
|
|
|
} catch (_) {}
|
|
|
|
expect(current).toEqual(copy);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('calls onUpdate if there is an onUpdate callback', () => {
|
|
|
|
const fn = jest.fn();
|
|
|
|
const tween = new Tween({ ...baseOptions, onUpdate: fn });
|
|
|
|
tween._frameCallback();
|
|
|
|
const current = tween.getCurrent();
|
|
|
|
expect(current).toBeDefined();
|
|
|
|
expect(fn).lastCalledWith(current);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not call onComplete if there is an onComplete callback and the tween is not complete', () => {
|
|
|
|
const fn = jest.fn();
|
|
|
|
const tween = new Tween({ ...baseOptions, onComplete: fn });
|
|
|
|
tween._frameCallback();
|
|
|
|
expect(fn.mock.calls.length).toBe(0);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('calls onComplete if there is an onComplete callback and the tween is complete', () => {
|
|
|
|
const fn = jest.fn();
|
|
|
|
const tween = new Tween({ ...baseOptions, onComplete: fn });
|
|
|
|
nowFn.mockReturnValue(nowFn() + baseOptions.duration);
|
|
|
|
tween._frameCallback();
|
|
|
|
const current = tween.getCurrent();
|
|
|
|
expect(fn.mock.calls).toEqual([[current]]);
|
|
|
|
expect(current.done).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('schedules an animatinon frame if the tween is not complete', () => {
|
|
|
|
expect(rafFn.mock.calls.length).toBe(0);
|
|
|
|
const tween = new Tween({ ...baseOptions, onUpdate: () => {} });
|
|
|
|
nowFn.mockReturnValue(nowFn() + 0.5 * baseOptions.duration);
|
|
|
|
rafFn.mockReset();
|
|
|
|
tween._frameCallback();
|
|
|
|
expect(rafFn.mock.calls).toEqual([[tween._frameCallback]]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('cancel()', () => {
|
|
|
|
it('cancels scheduled timeouts or animation frames', () => {
|
|
|
|
const oldClearTimeout = window.clearTimeout;
|
|
|
|
const oldCancelRaf = window.cancelAnimationFrame;
|
|
|
|
const clearFn = jest.fn();
|
|
|
|
window.clearTimeout = clearFn;
|
|
|
|
const cancelFn = jest.fn();
|
|
|
|
window.cancelAnimationFrame = cancelFn;
|
|
|
|
|
|
|
|
const tween = new Tween(baseOptions);
|
|
|
|
const id = 1;
|
|
|
|
tween.timeoutID = id;
|
|
|
|
tween.requestID = id;
|
|
|
|
tween.cancel();
|
|
|
|
expect(clearFn.mock.calls).toEqual([[id]]);
|
|
|
|
expect(cancelFn.mock.calls).toEqual([[id]]);
|
|
|
|
expect(tween.timeoutID).toBe(undefined);
|
|
|
|
expect(tween.requestID).toBe(undefined);
|
|
|
|
|
|
|
|
window.clearTimeout = oldClearTimeout;
|
|
|
|
window.cancelAnimationFrame = oldCancelRaf;
|
|
|
|
});
|
|
|
|
|
|
|
|
it('releases references to callbacks', () => {
|
|
|
|
const tween = new Tween({ ...baseOptions, onComplete: () => {}, onUpdate: () => {} });
|
|
|
|
tween.cancel();
|
2022-12-13 04:17:28 -06:00
|
|
|
expect(tween.onComplete).toBe(undefined);
|
|
|
|
expect(tween.onUpdate).toBe(undefined);
|
2020-04-02 06:34:16 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|