infinite-scroll.test.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. // @ts-nocheck
  2. import { nextTick, ref } from 'vue'
  3. import { mount } from '@vue/test-utils'
  4. import {
  5. afterAll,
  6. afterEach,
  7. beforeAll,
  8. describe,
  9. expect,
  10. test,
  11. vi,
  12. } from 'vitest'
  13. import defineGetter from '@element-plus/test-utils/define-getter'
  14. import makeScroll from '@element-plus/test-utils/make-scroll'
  15. import tick from '@element-plus/test-utils/tick'
  16. import InfiniteScroll, { DEFAULT_DELAY, SCOPE } from '../src'
  17. vi.mock('lodash-unified', () => {
  18. return {
  19. throttle: vi.fn((fn) => {
  20. fn.cancel = vi.fn()
  21. fn.flush = vi.fn()
  22. return fn
  23. }),
  24. }
  25. })
  26. const CONTAINER_HEIGHT = 200
  27. const ITEM_HEIGHT = 100
  28. const CONTAINER_STYLE = `overflow-y: auto;`
  29. const LIST_ITEM_CLASS = 'list-item'
  30. const LIST_ITEM_STYLE = `height: ${ITEM_HEIGHT}px;`
  31. const INITIAL_VALUE = 3
  32. // INITIAL_TICK = INITIAL_VALUE * MOUNT_ONE_NEED_TICKS + INITIAL_TICK
  33. const INITIAL_TICK = INITIAL_VALUE * 2 + 1
  34. const CUSTOM_DELAY = 0
  35. const CUSTOM_DISTANCE = 10
  36. let clientHeightRestore = null
  37. let scrollHeightRestore = null
  38. const _mount = (options: Record<string, unknown>) =>
  39. mount(
  40. {
  41. ...options,
  42. template: `
  43. <ul v-infinite-scroll="load" ${options.extraAttrs} ref="ulRef">
  44. <li
  45. v-for="i in count"
  46. :key="i"
  47. class="${LIST_ITEM_CLASS}"
  48. style="${LIST_ITEM_STYLE}"
  49. >{{ i }}</li>
  50. </ul>
  51. `,
  52. directives: {
  53. InfiniteScroll,
  54. },
  55. },
  56. { attachTo: document.body }
  57. )
  58. const setup = function () {
  59. const count = ref(0)
  60. const load = () => {
  61. count.value += 1
  62. }
  63. return { count, load }
  64. }
  65. const countListItem = (wrapper: any) =>
  66. wrapper.findAll(`.${LIST_ITEM_CLASS}`).length
  67. beforeAll(() => {
  68. clientHeightRestore = defineGetter(
  69. window.HTMLElement.prototype,
  70. 'clientHeight',
  71. CONTAINER_HEIGHT,
  72. 0
  73. )
  74. scrollHeightRestore = defineGetter(
  75. window.HTMLElement.prototype,
  76. 'scrollHeight',
  77. function () {
  78. return (
  79. // eslint-disable-next-line unicorn/prefer-query-selector
  80. Array.from(this.getElementsByClassName(LIST_ITEM_CLASS)).length *
  81. ITEM_HEIGHT
  82. )
  83. },
  84. 0
  85. )
  86. })
  87. afterAll(() => {
  88. clientHeightRestore()
  89. scrollHeightRestore()
  90. })
  91. afterEach(() => {
  92. const app = document.querySelector('[data-v-app]')
  93. document.body.removeChild(app)
  94. })
  95. describe('InfiniteScroll', () => {
  96. test('scrollable container is the element to which the directive is bound', async () => {
  97. const wrapper = _mount({
  98. extraAttrs: `style="${CONTAINER_STYLE}"`,
  99. setup,
  100. })
  101. const el = wrapper.element
  102. // wait to ensure initial full check has finished
  103. await tick(INITIAL_TICK)
  104. expect(el[SCOPE].container).toEqual(el)
  105. expect(el[SCOPE].containerEl).toEqual(el)
  106. expect(el[SCOPE].delay).toEqual(DEFAULT_DELAY)
  107. expect(countListItem(wrapper)).toBe(INITIAL_VALUE)
  108. // ensure observer has been destroyed, otherwise will cause memory leak
  109. expect(el[SCOPE].observer).toBeUndefined()
  110. // won't trigger load when not reach the bottom distance
  111. await makeScroll(el, 'scrollTop', ITEM_HEIGHT - 1)
  112. expect(countListItem(wrapper)).toBe(INITIAL_VALUE)
  113. await makeScroll(el, 'scrollTop', ITEM_HEIGHT)
  114. expect(countListItem(wrapper)).toBe(INITIAL_VALUE + 1)
  115. // won't trigger load when scroll back
  116. await makeScroll(el, 'scrollTop', 0)
  117. expect(countListItem(wrapper)).toBe(INITIAL_VALUE + 1)
  118. })
  119. test('custom scroll delay', async () => {
  120. const wrapper = _mount({
  121. extraAttrs: `infinite-scroll-delay="${CUSTOM_DELAY}" style="${CONTAINER_STYLE}"`,
  122. setup,
  123. })
  124. const el = wrapper.element
  125. await nextTick()
  126. expect(el[SCOPE].delay).toBe(CUSTOM_DELAY)
  127. })
  128. test('custom scroll distance', async () => {
  129. const wrapper = _mount({
  130. extraAttrs: `infinite-scroll-distance="${CUSTOM_DISTANCE}" style="${CONTAINER_STYLE}"`,
  131. setup,
  132. })
  133. const el = wrapper.element
  134. // wait to ensure initial full check has finished
  135. await tick(INITIAL_TICK)
  136. await makeScroll(el, 'scrollTop', ITEM_HEIGHT - CUSTOM_DISTANCE)
  137. expect(countListItem(wrapper)).toBe(INITIAL_VALUE + 1)
  138. })
  139. test('turn off immediate check', async () => {
  140. const wrapper = _mount({
  141. extraAttrs: `infinite-scroll-immediate="false" style="${CONTAINER_STYLE}"`,
  142. setup,
  143. })
  144. await tick(INITIAL_TICK)
  145. expect(countListItem(wrapper)).toBe(0)
  146. })
  147. test('limited scroll with `disabled` option', async () => {
  148. const wrapper = _mount({
  149. extraAttrs: `infinite-scroll-disabled="disabled" style="${CONTAINER_STYLE}"`,
  150. setup() {
  151. const count = ref(0)
  152. const disabled = ref(false)
  153. const load = () => {
  154. count.value += 1
  155. disabled.value = count.value >= INITIAL_VALUE + 1
  156. }
  157. return { count, load, disabled }
  158. },
  159. })
  160. const el = wrapper.element
  161. // wait to ensure initial full check has finished
  162. await tick(INITIAL_TICK)
  163. expect(countListItem(wrapper)).toBe(INITIAL_VALUE)
  164. await makeScroll(el, 'scrollTop', ITEM_HEIGHT)
  165. expect(countListItem(wrapper)).toBe(INITIAL_VALUE + 1)
  166. // no more items are loaded since `disabled = true`
  167. await makeScroll(el, 'scrollTop', ITEM_HEIGHT + 1)
  168. expect(countListItem(wrapper)).toBe(INITIAL_VALUE + 1)
  169. })
  170. test('scrollable container is document.documentElement', async () => {
  171. const wrapper = _mount({
  172. setup,
  173. })
  174. const el = wrapper.element
  175. const { documentElement } = document
  176. // wait to ensure initial full check has finished
  177. await tick(INITIAL_TICK)
  178. expect(el[SCOPE].container).toEqual(window)
  179. expect(el[SCOPE].containerEl).toEqual(documentElement)
  180. expect(countListItem(wrapper)).toBe(INITIAL_VALUE)
  181. // won't trigger load when not reach the bottom distance
  182. await makeScroll(documentElement, 'scrollTop', ITEM_HEIGHT - 1)
  183. expect(countListItem(wrapper)).toBe(INITIAL_VALUE)
  184. await makeScroll(documentElement, 'scrollTop', ITEM_HEIGHT)
  185. expect(countListItem(wrapper)).toBe(INITIAL_VALUE + 1)
  186. // won't trigger load when scroll back
  187. await makeScroll(documentElement, 'scrollTop', 0)
  188. expect(countListItem(wrapper)).toBe(INITIAL_VALUE + 1)
  189. })
  190. test('callback will not be triggered infinitely', async () => {
  191. const restoreClientHeight = defineGetter(
  192. window.HTMLElement.prototype,
  193. 'clientHeight',
  194. 0,
  195. CONTAINER_HEIGHT
  196. )
  197. const restoreScrollHeight = defineGetter(
  198. window.HTMLElement.prototype,
  199. 'scrollHeight',
  200. 0,
  201. function () {
  202. return (
  203. // eslint-disable-next-line unicorn/prefer-query-selector
  204. Array.from(this.getElementsByClassName(LIST_ITEM_CLASS)).length *
  205. ITEM_HEIGHT
  206. )
  207. }
  208. )
  209. const wrapper = _mount({
  210. extraAttrs: `style="${CONTAINER_STYLE}"`,
  211. setup,
  212. })
  213. await tick(INITIAL_TICK)
  214. expect(countListItem(wrapper)).toBe(0)
  215. restoreClientHeight()
  216. restoreScrollHeight()
  217. wrapper.vm.$refs.ulRef.ElInfiniteScroll.instance.count++
  218. await nextTick()
  219. expect(countListItem(wrapper)).toBe(INITIAL_VALUE)
  220. })
  221. })