// @ts-nocheck import { nextTick, ref } from 'vue' import { mount } from '@vue/test-utils' import { afterAll, afterEach, beforeAll, describe, expect, test, vi, } from 'vitest' import defineGetter from '@element-plus/test-utils/define-getter' import makeScroll from '@element-plus/test-utils/make-scroll' import tick from '@element-plus/test-utils/tick' import InfiniteScroll, { DEFAULT_DELAY, SCOPE } from '../src' vi.mock('lodash-unified', () => { return { throttle: vi.fn((fn) => { fn.cancel = vi.fn() fn.flush = vi.fn() return fn }), } }) const CONTAINER_HEIGHT = 200 const ITEM_HEIGHT = 100 const CONTAINER_STYLE = `overflow-y: auto;` const LIST_ITEM_CLASS = 'list-item' const LIST_ITEM_STYLE = `height: ${ITEM_HEIGHT}px;` const INITIAL_VALUE = 3 // INITIAL_TICK = INITIAL_VALUE * MOUNT_ONE_NEED_TICKS + INITIAL_TICK const INITIAL_TICK = INITIAL_VALUE * 2 + 1 const CUSTOM_DELAY = 0 const CUSTOM_DISTANCE = 10 let clientHeightRestore = null let scrollHeightRestore = null const _mount = (options: Record) => mount( { ...options, template: ` `, directives: { InfiniteScroll, }, }, { attachTo: document.body } ) const setup = function () { const count = ref(0) const load = () => { count.value += 1 } return { count, load } } const countListItem = (wrapper: any) => wrapper.findAll(`.${LIST_ITEM_CLASS}`).length beforeAll(() => { clientHeightRestore = defineGetter( window.HTMLElement.prototype, 'clientHeight', CONTAINER_HEIGHT, 0 ) scrollHeightRestore = defineGetter( window.HTMLElement.prototype, 'scrollHeight', function () { return ( // eslint-disable-next-line unicorn/prefer-query-selector Array.from(this.getElementsByClassName(LIST_ITEM_CLASS)).length * ITEM_HEIGHT ) }, 0 ) }) afterAll(() => { clientHeightRestore() scrollHeightRestore() }) afterEach(() => { const app = document.querySelector('[data-v-app]') document.body.removeChild(app) }) describe('InfiniteScroll', () => { test('scrollable container is the element to which the directive is bound', async () => { const wrapper = _mount({ extraAttrs: `style="${CONTAINER_STYLE}"`, setup, }) const el = wrapper.element // wait to ensure initial full check has finished await tick(INITIAL_TICK) expect(el[SCOPE].container).toEqual(el) expect(el[SCOPE].containerEl).toEqual(el) expect(el[SCOPE].delay).toEqual(DEFAULT_DELAY) expect(countListItem(wrapper)).toBe(INITIAL_VALUE) // ensure observer has been destroyed, otherwise will cause memory leak expect(el[SCOPE].observer).toBeUndefined() // won't trigger load when not reach the bottom distance await makeScroll(el, 'scrollTop', ITEM_HEIGHT - 1) expect(countListItem(wrapper)).toBe(INITIAL_VALUE) await makeScroll(el, 'scrollTop', ITEM_HEIGHT) expect(countListItem(wrapper)).toBe(INITIAL_VALUE + 1) // won't trigger load when scroll back await makeScroll(el, 'scrollTop', 0) expect(countListItem(wrapper)).toBe(INITIAL_VALUE + 1) }) test('custom scroll delay', async () => { const wrapper = _mount({ extraAttrs: `infinite-scroll-delay="${CUSTOM_DELAY}" style="${CONTAINER_STYLE}"`, setup, }) const el = wrapper.element await nextTick() expect(el[SCOPE].delay).toBe(CUSTOM_DELAY) }) test('custom scroll distance', async () => { const wrapper = _mount({ extraAttrs: `infinite-scroll-distance="${CUSTOM_DISTANCE}" style="${CONTAINER_STYLE}"`, setup, }) const el = wrapper.element // wait to ensure initial full check has finished await tick(INITIAL_TICK) await makeScroll(el, 'scrollTop', ITEM_HEIGHT - CUSTOM_DISTANCE) expect(countListItem(wrapper)).toBe(INITIAL_VALUE + 1) }) test('turn off immediate check', async () => { const wrapper = _mount({ extraAttrs: `infinite-scroll-immediate="false" style="${CONTAINER_STYLE}"`, setup, }) await tick(INITIAL_TICK) expect(countListItem(wrapper)).toBe(0) }) test('limited scroll with `disabled` option', async () => { const wrapper = _mount({ extraAttrs: `infinite-scroll-disabled="disabled" style="${CONTAINER_STYLE}"`, setup() { const count = ref(0) const disabled = ref(false) const load = () => { count.value += 1 disabled.value = count.value >= INITIAL_VALUE + 1 } return { count, load, disabled } }, }) const el = wrapper.element // wait to ensure initial full check has finished await tick(INITIAL_TICK) expect(countListItem(wrapper)).toBe(INITIAL_VALUE) await makeScroll(el, 'scrollTop', ITEM_HEIGHT) expect(countListItem(wrapper)).toBe(INITIAL_VALUE + 1) // no more items are loaded since `disabled = true` await makeScroll(el, 'scrollTop', ITEM_HEIGHT + 1) expect(countListItem(wrapper)).toBe(INITIAL_VALUE + 1) }) test('scrollable container is document.documentElement', async () => { const wrapper = _mount({ setup, }) const el = wrapper.element const { documentElement } = document // wait to ensure initial full check has finished await tick(INITIAL_TICK) expect(el[SCOPE].container).toEqual(window) expect(el[SCOPE].containerEl).toEqual(documentElement) expect(countListItem(wrapper)).toBe(INITIAL_VALUE) // won't trigger load when not reach the bottom distance await makeScroll(documentElement, 'scrollTop', ITEM_HEIGHT - 1) expect(countListItem(wrapper)).toBe(INITIAL_VALUE) await makeScroll(documentElement, 'scrollTop', ITEM_HEIGHT) expect(countListItem(wrapper)).toBe(INITIAL_VALUE + 1) // won't trigger load when scroll back await makeScroll(documentElement, 'scrollTop', 0) expect(countListItem(wrapper)).toBe(INITIAL_VALUE + 1) }) test('callback will not be triggered infinitely', async () => { const restoreClientHeight = defineGetter( window.HTMLElement.prototype, 'clientHeight', 0, CONTAINER_HEIGHT ) const restoreScrollHeight = defineGetter( window.HTMLElement.prototype, 'scrollHeight', 0, function () { return ( // eslint-disable-next-line unicorn/prefer-query-selector Array.from(this.getElementsByClassName(LIST_ITEM_CLASS)).length * ITEM_HEIGHT ) } ) const wrapper = _mount({ extraAttrs: `style="${CONTAINER_STYLE}"`, setup, }) await tick(INITIAL_TICK) expect(countListItem(wrapper)).toBe(0) restoreClientHeight() restoreScrollHeight() wrapper.vm.$refs.ulRef.ElInfiniteScroll.instance.count++ await nextTick() expect(countListItem(wrapper)).toBe(INITIAL_VALUE) }) })