UNPKG

@enact/ui

Version:

A collection of simplified unstyled cross-platform UI components for Enact

384 lines (382 loc) 16 kB
"use strict"; require("@testing-library/jest-dom"); var _react = require("@testing-library/react"); var _react2 = require("react"); var _VirtualList = _interopRequireWildcard(require("../VirtualList")); var _jsxRuntime = require("react/jsx-runtime"); var _excluded = ["index"], _excluded2 = ["index"], _excluded3 = ["direction"], _excluded4 = ["index"]; function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; } function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; } var mockPlatform = {}; jest.mock('@enact/core/platform', function () { return { get platform() { return mockPlatform; } }; }); describe('VirtualList with native scrollMode', function () { var clientSize, dataSize, items, itemSize, renderItem; beforeEach(function () { clientSize = { clientWidth: 1280, clientHeight: 720 }; dataSize = 100; items = []; itemSize = 30; renderItem = function renderItem(_ref) { var index = _ref.index, rest = _objectWithoutProperties(_ref, _excluded); // eslint-disable-line enact/display-name return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", _objectSpread(_objectSpread({}, rest), {}, { "data-index": index, id: 'item' + index, children: items[index].name })); }; for (var i = 0; i < dataSize; i++) { items.push({ name: 'Account ' + i }); } }); afterEach(function () { clientSize = null; dataSize = null; items = null; itemSize = null; renderItem = null; }); test('should render a list of \'items\'', function () { (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_VirtualList["default"], { clientSize: clientSize, dataSize: dataSize, itemRenderer: renderItem, itemSize: itemSize, scrollMode: "native" })); var expected = 'Account 0'; var actual = _react.screen.getByRole('list').children.item(0).textContent; expect(actual).toBe(expected); }); test('should render a list of \'items\' with horizontal direction', function () { (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_VirtualList["default"], { clientSize: clientSize, dataSize: dataSize, direction: "horizontal", itemRenderer: renderItem, itemSize: itemSize, scrollMode: "native" })); var expected = 'Account 0'; var actual = _react.screen.getByRole('list').children.item(0).textContent; expect(actual).toBe(expected); }); test('should render overhang items when clientSize is not given', function () { (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_VirtualList["default"], { dataSize: dataSize, itemRenderer: renderItem, itemSize: itemSize, scrollMode: "native" })); var expected = 3; var actual = _react.screen.getByRole('list').children.length; expect(actual).toBe(expected); }); test('should render (clientHeight / itemHeight + overhang) items', function () { (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_VirtualList["default"], { clientSize: clientSize, dataSize: dataSize, itemRenderer: renderItem, itemSize: itemSize, scrollMode: "native" })); var expected = 27; // 720 / 30 + 3 var actual = _react.screen.getByRole('list').children.length; expect(actual).toBe(expected); }); test('should re-render clientHeight / itemHeight + overhang) items after changing client size', function () { var _render = (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_VirtualList["default"], { clientSize: clientSize, dataSize: dataSize, itemRenderer: renderItem, itemSize: itemSize, scrollMode: "native" })), rerender = _render.rerender; var newClientSize = { clientWidth: 1280, clientHeight: 360 }; rerender(/*#__PURE__*/(0, _jsxRuntime.jsx)(_VirtualList["default"], { clientSize: newClientSize, dataSize: dataSize, itemRenderer: renderItem, itemSize: itemSize, scrollMode: "native" })); var expected = 15; // 360 / 30 + 3 var actual = _react.screen.getByRole('list').children.length; expect(actual).toBe(expected); }); describe('Adding an item', function () { test('should render an added item named \'Password 0\' as the first item', function (done) { var itemArray = [{ name: 'A' }, { name: 'B' }, { name: 'C' }]; var renderItemArray = function renderItemArray(_ref2) { var index = _ref2.index, rest = _objectWithoutProperties(_ref2, _excluded2); return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", _objectSpread(_objectSpread({}, rest), {}, { id: 'item' + index, children: itemArray[index].name })); }; var _render2 = (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_VirtualList["default"], { clientSize: clientSize, dataSize: itemArray.length, itemRenderer: renderItemArray, itemSize: itemSize, scrollMode: "native" })), rerender = _render2.rerender; itemArray.unshift({ name: 'Password 0' }); rerender(/*#__PURE__*/(0, _jsxRuntime.jsx)(_VirtualList["default"], { clientSize: clientSize, dataSize: itemArray.length, itemRenderer: renderItemArray, itemSize: itemSize, scrollMode: "native" })); jest.useFakeTimers(); (0, _react.act)(function () { return jest.advanceTimersByTime(0); }); var expected = itemArray[0].name; var actual = _react.screen.getByRole('list').children.item(0).textContent; expect(actual).toBe(expected); done(); jest.useRealTimers(); }); }); describe('Animating scroll', function () { var instance, node, scrollContentRef; beforeEach(function () { node = { scrollTo: jest.fn(), scrollBy: jest.fn(), scrollLeft: 0, scrollTop: 0 }; scrollContentRef = { current: node }; instance = new _VirtualList.VirtualListBasic({ scrollContentRef: scrollContentRef, clientSize: clientSize, itemSize: itemSize, itemRenderer: renderItem, scrollMode: 'native' }); jest.spyOn(window, 'requestAnimationFrame').mockImplementation(function (cb) { instance.rafCallback = cb; return 123; }); jest.spyOn(window, 'cancelAnimationFrame').mockImplementation(jest.fn()); jest.spyOn(performance, 'now').mockReturnValue(0); }); afterEach(function () { delete mockPlatform.chrome; }); test('should call native scrollTo when behavior is instant', function () { instance.scrollToPosition(0, 300, 'instant'); expect(node.scrollTo).toHaveBeenCalledWith({ left: 0, top: 300, behavior: 'instant' }); }); test('should call native scrollTo when NOT on Chrome and behavior is smooth', function () { mockPlatform.chrome = false; instance.scrollToPosition(0, 300, 'smooth'); expect(node.scrollTo).toHaveBeenCalledWith({ left: 0, top: 300, behavior: 'smooth' }); }); test('should call animateScroll when on Chrome and behavior is smooth', function () { mockPlatform.chrome = 88; instance.animateScroll = jest.fn(); instance.scrollToPosition(0, 300, 'smooth'); expect(instance.animateScroll).toHaveBeenCalledWith(0, 300, node); }); test('should cancel animation when content is scrolled by pageKey', function () { mockPlatform.chrome = 88; instance.scrollToPosition(0, 600, 'smooth'); node.lastInputType = 'pageKey'; instance.scrollToPosition(0, 200, 'smooth'); expect(window.cancelAnimationFrame).toHaveBeenCalled(); }); test('should initiate animation loop using requestAnimationFrame', function () { instance.animateScroll(0, 300, node); expect(window.requestAnimationFrame).toHaveBeenCalled(); }); test('should scroll incrementally on each frame', function () { instance.animateScroll(0, 300, node); instance.rafCallback(); expect(node.scrollBy).toHaveBeenCalledWith(expect.objectContaining({ behavior: 'instant' })); expect(instance.scrolling).toBe(true); }); test('should cancel animation and stop when target is reached', function () { node.scrollTop = 300; instance.animateScroll(0, 310, node); instance.rafCallback(600); expect(window.cancelAnimationFrame).toHaveBeenCalled(); expect(instance.scrolling).toBe(false); }); test('should fallback to smooth scroll if animation exceeds duration (timeout)', function () { instance.animateScroll(0, 300, node); instance.rafCallback(600); node.scrollTop = 250; expect(node.scrollTo).toHaveBeenCalledWith({ left: 0, top: 300, behavior: 'smooth' }); expect(window.cancelAnimationFrame).toHaveBeenCalled(); }); }); }); describe('VirtualListBasic scrollBounds consistency after item margin detection', function () { var originalGetComputedStyle; var nop = function nop() {}; function renderVirtualListBasic() { var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, _ref3$direction = _ref3.direction, direction = _ref3$direction === void 0 ? 'vertical' : _ref3$direction, rest = _objectWithoutProperties(_ref3, _excluded3); var scrollContentRef = /*#__PURE__*/(0, _react2.createRef)(); var itemRefs = /*#__PURE__*/(0, _react2.createRef)(); itemRefs.current = []; var ref = /*#__PURE__*/(0, _react2.createRef)(); (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_VirtualList.VirtualListBasic, _objectSpread({ clientSize: { clientWidth: 1280, clientHeight: 720 }, dataSize: 100, direction: direction, getAffordance: function getAffordance() { return 0; }, itemRefs: itemRefs, itemRenderer: function itemRenderer(_ref4) { var index = _ref4.index, rest2 = _objectWithoutProperties(_ref4, _excluded4); return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", _objectSpread(_objectSpread({}, rest2), {}, { "data-index": index })); }, itemSize: 30, overhang: 3, placeholderRenderer: nop, role: "list", scrollContentRef: scrollContentRef, scrollMode: "native", spacing: 0, cbScrollTo: nop, ref: ref }, rest))); return { ref: ref }; } beforeEach(function () { originalGetComputedStyle = window.getComputedStyle; }); afterEach(function () { window.getComputedStyle = originalGetComputedStyle; }); test('should increment scrollHeight and maxTop by the same margin sum when vertical item margins are detected, and not re-apply on subsequent updates', function () { var _renderVirtualListBas = renderVirtualListBasic(), ref = _renderVirtualListBas.ref; var instance = ref.current; var marginTop = 10; var marginBottom = 5; window.getComputedStyle = function (el) { if (el === instance.props.itemRefs.current[0]) { return { getPropertyValue: function getPropertyValue(prop) { if (prop === 'margin-top') return "".concat(marginTop, "px"); if (prop === 'margin-bottom') return "".concat(marginBottom, "px"); if (prop === 'margin-left') return '0px'; if (prop === 'margin-right') return '0px'; return '0px'; } }; } return originalGetComputedStyle(el); }; instance.itemMarginTop = null; (0, _react.act)(function () { instance.forceUpdate(); }); var scrollHeightAfterFirst = instance.scrollBounds.scrollHeight; var maxTopAfterFirst = instance.scrollBounds.maxTop; expect(scrollHeightAfterFirst - maxTopAfterFirst).toBe(instance.props.clientSize.clientHeight); expect(instance.scrollBounds.maxTop).toBe(instance.scrollBounds.scrollHeight - instance.props.clientSize.clientHeight); // guard prevents re-applying on subsequent updates (0, _react.act)(function () { instance.forceUpdate(); }); expect(instance.scrollBounds.scrollHeight).toBe(scrollHeightAfterFirst); expect(instance.scrollBounds.maxTop).toBe(maxTopAfterFirst); }); test('should increment scrollWidth and maxLeft by the same margin sum when horizontal item margins are detected', function () { var _renderVirtualListBas2 = renderVirtualListBasic({ direction: 'horizontal' }), ref = _renderVirtualListBas2.ref; var instance = ref.current; var marginLeft = 8; var marginRight = 8; window.getComputedStyle = function (el) { if (el === instance.props.itemRefs.current[0]) { return { getPropertyValue: function getPropertyValue(prop) { if (prop === 'margin-top') return '0px'; if (prop === 'margin-bottom') return '0px'; if (prop === 'margin-left') return "".concat(marginLeft, "px"); if (prop === 'margin-right') return "".concat(marginRight, "px"); return '0px'; } }; } return originalGetComputedStyle(el); }; instance.itemMarginTop = null; (0, _react.act)(function () { instance.forceUpdate(); }); expect(instance.scrollBounds.scrollWidth - instance.scrollBounds.maxLeft).toBe(instance.props.clientSize.clientWidth); }); });