Page MenuHomePhorge

No OneTemporary

Size
11 KB
Referenced Files
None
Subscribers
None
diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
index d87176300e..fe210bd6c6 100644
--- a/src/components/conversation/conversation.js
+++ b/src/components/conversation/conversation.js
@@ -164,7 +164,10 @@ export default {
const resizeObserver = ref(new ResizeObserver(updateVirtualHeight))
watch(rootElement, () => resizeObserver.value.observe(rootElement.value))
watch(suspendable, (value) =>
- emit('suspendableStateChange', { suspendable: value, id: statusId.value }),
+ emit('suspendableStateChange', {
+ suspendable: value,
+ id: statusId.value,
+ }),
)
onUnmounted(() => resizeObserver.value.disconnect())
diff --git a/src/composables/useVirtualScrolling.js b/src/composables/useVirtualScrolling.js
index 1b8362194d..a761876b1f 100644
--- a/src/composables/useVirtualScrolling.js
+++ b/src/composables/useVirtualScrolling.js
@@ -86,7 +86,7 @@ export function useVirtualScrolling({
const { top } = body.value.getBoundingClientRect()
- const distanceItemTopToWindowTop = 0 - top + offset.value
+ const distanceItemTopToWindowTop = 0 - top + (toValue(offset) ?? 0)
const distanceItemTopToWindowBottom = windowHeight.value - top
// Technically, bottom scroll boundary should be distance
@@ -160,11 +160,10 @@ export function useVirtualScrolling({
const chart = enabled.value ? heightChartVisibility : heightChart
// Group invisible items into spacers
return chart.value.reduce((acc, heightChartItem) => {
- const { suspendable, visible, height, top, bottom, id } = heightChartItem
- // Bottom value isn't really used otherwise for debugging
+ const { suspendable, visible, height, top, id } = heightChartItem
const present = visible || !suspendable
if (present) {
- return [...acc, { type: 'item', height, top, bottom, id }]
+ return [...acc, { type: 'item', height, top, id }]
} else {
// Reusing previous item if possible
const previousItem = acc[acc.length - 1]
@@ -176,7 +175,6 @@ export function useVirtualScrolling({
: {
type: 'spacer',
top: Number.POSITIVE_INFINITY,
- bottom: Number.POSITIVE_INFINITY,
height: 0,
ids: new Set(),
}
@@ -186,7 +184,6 @@ export function useVirtualScrolling({
spacer.height += height
if (top < spacer.top) spacer.top = top
- if (bottom < spacer.bottom) spacer.bottom = bottom
// If we used previous item there is no need to push it to array
if (usingPreviousItem) {
@@ -281,7 +278,7 @@ export function useVirtualScrolling({
)
// Misc
- const reset = async () => {
+ const reset = () => {
unsuspendibleIds.value = new Set()
heights.value = new Map()
}
diff --git a/test/unit/specs/composables/useVirtualScrolling.spec.js b/test/unit/specs/composables/useVirtualScrolling.spec.js
new file mode 100644
index 0000000000..8a69714b5c
--- /dev/null
+++ b/test/unit/specs/composables/useVirtualScrolling.spec.js
@@ -0,0 +1,336 @@
+import { createTestingPinia } from '@pinia/testing'
+import { setActivePinia } from 'pinia'
+import { ref } from 'vue'
+
+import { useVirtualScrolling } from 'src/composables/useVirtualScrolling.js'
+
+const windowSize = {
+ width: ref(1024),
+ height: ref(768),
+}
+
+const scrollPositionInstance = {
+ x: ref(0),
+ y: ref(0),
+ scrollBy: vi.fn(),
+}
+
+vi.mock(import('src/composables/useWindowSize.js'), () => ({
+ useWindowSize: () => windowSize,
+}))
+
+describe('useVirtualScrolling', () => {
+ beforeEach(() => {
+ setActivePinia(createTestingPinia({ stubActions: false }))
+ vi.useFakeTimers()
+ })
+
+ afterEach(() => {
+ vi.useRealTimers()
+ vi.resetAllMocks()
+ scrollPositionInstance.x.value = 0
+ scrollPositionInstance.y.value = 0
+ windowSize.width.value = 1024
+ windowSize.height.value = 768
+ })
+
+ it('should init boundaries and compute a height chart', () => {
+ const body = ref({
+ getBoundingClientRect: () => ({
+ top: 0,
+ }),
+ })
+ const list = [...new Array(20)].map((i, index) => ({
+ id: `${index}i`,
+ }))
+ const result = useVirtualScrolling({
+ name: 'Test',
+ enabled: ref(true),
+ list: ref(list),
+ body: ref(body),
+ offset: 0,
+ getPlaceholderHeight: () => ref(100),
+ scrollPositionInstance,
+ })
+
+ result.updateBoundaries()
+ const items = [...new Array(11)].map((i, index) => ({
+ id: `${index}i`,
+ top: index * 100,
+ height: 100,
+ type: 'item',
+ }))
+ const spacer = {
+ type: 'spacer',
+ top: 1100,
+ height: 900,
+ ids: expect.any(Set),
+ id: expect.stringContaining('19i'),
+ }
+ expect(result.heightChart.value).toEqual([...items, spacer])
+ })
+
+ it('should update boundaries and compute a height chart on scroll', () => {
+ let topPosition = 0
+ const body = ref({
+ getBoundingClientRect: () => ({
+ top: topPosition,
+ }),
+ })
+ const list = [...new Array(20)].map((i, index) => ({
+ id: `${index}i`,
+ }))
+ const result = useVirtualScrolling({
+ name: 'Test',
+ enabled: ref(true),
+ list: ref(list),
+ body: ref(body),
+ offset: 0,
+ getPlaceholderHeight: () => ref(100),
+ scrollPositionInstance,
+ })
+ result.updateBoundaries()
+
+ const amount = 500
+ topPosition -= amount
+ result.updateBoundaries()
+
+ const spacer1 = {
+ type: 'spacer',
+ top: 0,
+ height: 200,
+ ids: expect.any(Set),
+ id: expect.stringContaining('0i'),
+ }
+ const items = [...new Array(14)].map((i, index) => ({
+ id: `${index + 2}i`,
+ top: (index + 2) * 100,
+ height: 100,
+ type: 'item',
+ }))
+ const spacer2 = {
+ type: 'spacer',
+ top: 1600,
+ height: 400,
+ ids: expect.any(Set),
+ id: expect.stringContaining('19i'),
+ }
+ expect(result.heightChart.value).toEqual([spacer1, ...items, spacer2])
+ })
+
+ it('should compensate for scroll when new items added on top', async () => {
+ let topPosition = 0
+ const body = ref({
+ getBoundingClientRect: () => ({
+ top: topPosition,
+ }),
+ })
+ const listOld = [...new Array(10)].map((i, index) => ({
+ id: `${index}ia`,
+ }))
+ const listNew = [...new Array(10)].map((i, index) => ({
+ id: `${index}ib`,
+ }))
+ const list = ref(listOld)
+ const result = useVirtualScrolling({
+ name: 'Test',
+ enabled: ref(true),
+ list,
+ body: ref(body),
+ offset: 0,
+ getPlaceholderHeight: () => ref(100),
+ scrollCompensation: true,
+ scrollPositionInstance,
+ })
+ listOld.forEach((i) => {
+ result.updateVirtualHeight({ id: i.id, height: 200 })
+ })
+ result.updateBoundaries()
+ await vi.advanceTimersToNextTimerAsync()
+
+ list.value = [...listNew, ...listOld]
+ result.updateBoundaries()
+ await vi.advanceTimersToNextTimerAsync()
+ const amount = 1000
+ expect(scrollPositionInstance.scrollBy).to.have.been.calledOnceWith(
+ 0,
+ amount,
+ )
+ topPosition -= amount
+ result.updateBoundaries()
+
+ const spacer1 = {
+ type: 'spacer',
+ top: 0,
+ height: 700,
+ ids: expect.any(Set),
+ id: expect.stringContaining('0ib'),
+ }
+ const itemsNew = [...new Array(3)].map((i, index) => ({
+ id: `${index + 7}ib`,
+ top: (index + 7) * 100,
+ height: 100,
+ type: 'item',
+ }))
+ const itemsOld = [...new Array(6)].map((i, index) => ({
+ id: `${index}ia`,
+ top: index * 200 + 1000,
+ height: 200,
+ type: 'item',
+ }))
+ const spacer2 = {
+ type: 'spacer',
+ top: 2200,
+ height: 800,
+ ids: expect.any(Set),
+ id: expect.stringContaining('9ia'),
+ }
+ expect(result.heightChart.value).toEqual([
+ spacer1,
+ ...itemsNew,
+ ...itemsOld,
+ spacer2,
+ ])
+ })
+
+ it('should compensate for scroll when items are removed from top', async () => {
+ let topPosition = 0
+ const body = ref({
+ getBoundingClientRect: () => ({
+ top: topPosition,
+ }),
+ })
+ const listA = [...new Array(10)].map((i, index) => ({
+ id: `${index}ia`,
+ }))
+ const listB = [...new Array(10)].map((i, index) => ({
+ id: `${index}ib`,
+ }))
+ const listC = [...new Array(10)].map((i, index) => ({
+ id: `${index}ic`,
+ }))
+ const list = ref([...listA, ...listB, ...listC])
+ const result = useVirtualScrolling({
+ name: 'Test',
+ enabled: ref(true),
+ list,
+ body: ref(body),
+ offset: 0,
+ getPlaceholderHeight: () => ref(100),
+ scrollCompensation: true,
+ scrollPositionInstance,
+ })
+ list.value.forEach((i) => {
+ result.updateVirtualHeight({ id: i.id, height: 200 })
+ })
+ result.updateBoundaries()
+ await vi.advanceTimersToNextTimerAsync()
+
+ list.value = listB
+ result.updateBoundaries()
+ await vi.advanceTimersToNextTimerAsync()
+ const amount = -2000
+ expect(scrollPositionInstance.scrollBy).to.have.been.calledOnceWith(
+ 0,
+ amount,
+ )
+ // topPosition -= amount // not doing this because it would overscroll
+ result.updateBoundaries()
+ await vi.advanceTimersToNextTimerAsync()
+
+ const items = [...new Array(6)].map((i, index) => ({
+ id: `${index}ib`,
+ top: index * 200,
+ height: 200,
+ type: 'item',
+ }))
+ const spacer = {
+ type: 'spacer',
+ top: 1200,
+ height: 800,
+ ids: expect.any(Set),
+ id: expect.stringContaining('9ib'),
+ }
+ expect(result.heightChart.value).toEqual([...items, spacer])
+ })
+
+ it('should exclude unsuspendable items from virtualization', async () => {
+ let topPosition = 0
+ const body = ref({
+ getBoundingClientRect: () => ({
+ top: topPosition,
+ }),
+ })
+ const list = ref(
+ [...new Array(20)].map((i, index) => ({
+ id: `${index}i`,
+ })),
+ )
+ const result = useVirtualScrolling({
+ name: 'Test',
+ enabled: ref(true),
+ list,
+ body: ref(body),
+ offset: 0,
+ getPlaceholderHeight: () => ref(100),
+ scrollPositionInstance,
+ })
+ list.value.forEach((i) => {
+ result.updateVirtualHeight({ id: i.id, height: 200 })
+ })
+ result.changeSuspendState({ id: '2i', suspendable: false })
+ result.changeSuspendState({ id: '3i', suspendable: true })
+ result.updateBoundaries()
+ await vi.advanceTimersToNextTimerAsync()
+
+ result.updateBoundaries()
+ await vi.advanceTimersToNextTimerAsync()
+ const amount = 2000
+ topPosition -= amount
+ result.updateBoundaries()
+ await vi.advanceTimersToNextTimerAsync()
+
+ const spacer1 = {
+ type: 'spacer',
+ top: 0,
+ height: 400,
+ ids: expect.any(Set),
+ id: expect.stringContaining('0i'),
+ }
+ const items1 = [
+ {
+ id: `2i`,
+ top: 400,
+ height: 200,
+ type: 'item',
+ },
+ ]
+ const spacer2 = {
+ type: 'spacer',
+ top: 600,
+ height: 1000,
+ ids: expect.any(Set),
+ id: expect.stringContaining('3i'),
+ }
+ const items2 = [...new Array(8)].map((i, index) => ({
+ id: `${index + 8}i`,
+ top: (index + 8) * 200,
+ height: 200,
+ type: 'item',
+ }))
+ const spacer3 = {
+ type: 'spacer',
+ top: 3200,
+ height: 800,
+ ids: expect.any(Set),
+ id: expect.stringContaining('19i'),
+ }
+ expect(result.heightChart.value).toEqual([
+ spacer1,
+ ...items1,
+ spacer2,
+ ...items2,
+ spacer3,
+ ])
+ })
+})

File Metadata

Mime Type
text/x-diff
Expires
Sat, Sep 19, 7:21 AM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1769103
Default Alt Text
(11 KB)

Event Timeline