@enact/ui
Version:
A collection of simplified unstyled cross-platform UI components for Enact
232 lines (223 loc) • 7.84 kB
JavaScript
;
require("@testing-library/jest-dom");
var _Scroller = require("../Scroller");
var mockPlatform = {};
jest.mock('@enact/core/platform', function () {
return {
get platform() {
return mockPlatform;
}
};
});
describe('ScrollBasic', function () {
var scrollContentRef;
beforeEach(function () {
jest.createMockFromModule('@enact/core/platform');
scrollContentRef = {
current: {
scrollLeft: 0,
scrollTop: 0,
scrollBy: jest.fn(),
scrollTo: jest.fn()
}
};
});
afterEach(function () {
jest.clearAllMocks();
});
test('should call scrollTo on scrollToPosition', function () {
var instance = new _Scroller.ScrollerBasic({
scrollContentRef: scrollContentRef,
direction: 'both'
});
instance.scrollToPosition(100, 200, 'smooth');
expect(scrollContentRef.current.scrollTo).toHaveBeenCalledWith({
left: 100,
top: 200,
behavior: 'smooth'
});
instance.scrollToPosition(100, 200, 'instant');
expect(scrollContentRef.current.scrollTo).toHaveBeenCalledWith({
left: 100,
top: 200,
behavior: 'instant'
});
});
test('should cancel pending animation before calling scrollTo on scrollToPosition', function () {
var instance = new _Scroller.ScrollerBasic({
scrollContentRef: scrollContentRef,
direction: 'both'
});
var mockAnimationId = 42;
instance.scrollAnimationId = mockAnimationId;
window.cancelAnimationFrame = jest.fn();
instance.scrollToPosition(100, 200, 'instant');
expect(window.cancelAnimationFrame).toHaveBeenCalledWith(mockAnimationId);
expect(instance.scrollAnimationId).toBeNull();
expect(scrollContentRef.current.scrollTo).toHaveBeenCalledWith({
left: 100,
top: 200,
behavior: 'instant'
});
});
test('should not call cancelAnimationFrame when scrollAnimationId is null on scrollToPosition', function () {
var instance = new _Scroller.ScrollerBasic({
scrollContentRef: scrollContentRef,
direction: 'both'
});
instance.scrollAnimationId = null;
window.cancelAnimationFrame = jest.fn();
instance.scrollToPosition(100, 200, 'instant');
expect(window.cancelAnimationFrame).not.toHaveBeenCalled();
});
test('should call animateScroll instead of scrollTo when platform is Chrome, behavior is smooth, and repeat is true', function () {
mockPlatform.chrome = 132;
var instance = new _Scroller.ScrollerBasic({
scrollContentRef: scrollContentRef,
direction: 'both'
});
instance.animateScroll = jest.fn();
instance.scrollToPosition(100, 200, 'smooth', true);
expect(instance.animateScroll).toHaveBeenCalledWith(100, 200, scrollContentRef.current);
expect(scrollContentRef.current.scrollTo).not.toHaveBeenCalled();
delete mockPlatform.chrome;
});
describe('animateScroll', function () {
var rafCallbacks;
var rafIdCounter;
var nowValue;
beforeEach(function () {
rafCallbacks = [];
rafIdCounter = 1;
nowValue = 0;
window.requestAnimationFrame = jest.fn(function (cb) {
var id = rafIdCounter++;
rafCallbacks.push({
id: id,
cb: cb
});
return id;
});
window.cancelAnimationFrame = jest.fn(function (id) {
rafCallbacks = rafCallbacks.filter(function (entry) {
return entry.id !== id;
});
});
jest.spyOn(performance, 'now').mockImplementation(function () {
return nowValue;
});
});
afterEach(function () {
jest.restoreAllMocks();
});
function flushRaf(time) {
nowValue = time;
var callbacks = rafCallbacks.splice(0);
callbacks.forEach(function (_ref) {
var cb = _ref.cb;
return cb(time);
});
}
test('should call scrollBy with a dynamic step (10% of remaining distance) during animateScroll', function () {
scrollContentRef.current.scrollLeft = 0;
scrollContentRef.current.scrollTop = 0;
var instance = new _Scroller.ScrollerBasic({
scrollContentRef: scrollContentRef,
direction: 'both'
});
var node = scrollContentRef.current;
instance.animateScroll(200, 100, node);
// First frame at t=0
flushRaf(0);
expect(node.scrollBy).toHaveBeenCalledWith(expect.objectContaining({
left: expect.closeTo(20, 1),
// 10% of 200
top: expect.closeTo(10, 1),
// 10% of 100
behavior: 'instant'
}));
});
test('should use minimum step of 8px when remaining distance is less than 80px', function () {
scrollContentRef.current.scrollLeft = 0;
scrollContentRef.current.scrollTop = 0;
var instance = new _Scroller.ScrollerBasic({
scrollContentRef: scrollContentRef,
direction: 'both'
});
var node = scrollContentRef.current;
// 50px remaining → 10% = 5px < 8px minimum
instance.animateScroll(50, 50, node);
flushRaf(0);
expect(node.scrollBy).toHaveBeenCalledWith(expect.objectContaining({
left: 8,
top: 8,
behavior: 'instant'
}));
});
test('should stop animating and not call scrollBy when target is already reached', function () {
scrollContentRef.current.scrollLeft = 100;
scrollContentRef.current.scrollTop = 200;
var instance = new _Scroller.ScrollerBasic({
scrollContentRef: scrollContentRef,
direction: 'both'
});
var node = scrollContentRef.current;
// Target equals current position → already reached
instance.animateScroll(100, 200, node);
flushRaf(0);
expect(node.scrollBy).not.toHaveBeenCalled();
expect(node.scrollTo).not.toHaveBeenCalled();
});
test('should stop animation and call scrollTo with smooth behavior when 1s timeout elapses before reaching target', function () {
scrollContentRef.current.scrollLeft = 0;
scrollContentRef.current.scrollTop = 0;
var instance = new _Scroller.ScrollerBasic({
scrollContentRef: scrollContentRef,
direction: 'both'
});
var node = scrollContentRef.current;
instance.animateScroll(500, 500, node);
// Advance past 1s timeout without updating scroll position
flushRaf(1100);
expect(node.scrollTo).toHaveBeenCalledWith({
top: 500,
left: 500,
behavior: 'smooth'
});
});
test('should not call scrollTo fallback when target is reached exactly at timeout boundary', function () {
scrollContentRef.current.scrollLeft = 500;
scrollContentRef.current.scrollTop = 500;
var instance = new _Scroller.ScrollerBasic({
scrollContentRef: scrollContentRef,
direction: 'both'
});
var node = scrollContentRef.current;
// Target already reached
instance.animateScroll(500, 500, node);
flushRaf(1100);
expect(node.scrollTo).not.toHaveBeenCalled();
});
test('should continue requesting animation frames until target is reached', function () {
scrollContentRef.current.scrollLeft = 0;
scrollContentRef.current.scrollTop = 0;
var instance = new _Scroller.ScrollerBasic({
scrollContentRef: scrollContentRef,
direction: 'both'
});
var node = scrollContentRef.current;
instance.animateScroll(100, 0, node);
// First frame: scrollBy called, new rAF registered
flushRaf(0);
expect(node.scrollBy).toHaveBeenCalledTimes(1);
// Simulate scroll progress
node.scrollLeft = 50;
flushRaf(100);
expect(node.scrollBy).toHaveBeenCalledTimes(2);
// Simulate reaching target
node.scrollLeft = 100;
flushRaf(200);
expect(node.scrollBy).toHaveBeenCalledTimes(2);
});
});
});