initialize Electron desktop app and remove unused files
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
// @vitest-environment jsdom
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { defineComponent, nextTick } from 'vue'
|
||||
|
||||
const dynamicScrollToBottomMock = vi.hoisted(() => vi.fn())
|
||||
const dynamicScrollToPositionMock = vi.hoisted(() => vi.fn())
|
||||
const dynamicScrollToItemMock = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('vue-virtual-scroller', () => ({
|
||||
DynamicScroller: defineComponent({
|
||||
name: 'DynamicScroller',
|
||||
props: {
|
||||
items: { type: Array, default: () => [] },
|
||||
},
|
||||
emits: ['scroll', 'resize', 'visible'],
|
||||
setup(_props, { expose }) {
|
||||
expose({
|
||||
scrollToBottom: dynamicScrollToBottomMock,
|
||||
scrollToPosition: dynamicScrollToPositionMock,
|
||||
scrollToItem: dynamicScrollToItemMock,
|
||||
})
|
||||
},
|
||||
template: `
|
||||
<div class="virtual-message-list" @scroll="$emit('scroll')">
|
||||
<slot name="before" />
|
||||
<slot v-for="(item, index) in items" :item="item" :index="index" :active="true" />
|
||||
<slot name="after" />
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
DynamicScrollerItem: defineComponent({
|
||||
name: 'DynamicScrollerItem',
|
||||
props: {
|
||||
item: { type: Object, required: true },
|
||||
index: { type: Number, required: true },
|
||||
active: { type: Boolean, default: true },
|
||||
},
|
||||
template: '<div class="virtual-row"><slot /></div>',
|
||||
}),
|
||||
}))
|
||||
|
||||
import VirtualMessageList from '@/components/hermes/chat/VirtualMessageList.vue'
|
||||
|
||||
function setScrollerMetrics(el: HTMLElement, metrics: { scrollHeight: number; clientHeight: number; scrollTop: number }) {
|
||||
Object.defineProperty(el, 'scrollHeight', { configurable: true, value: metrics.scrollHeight })
|
||||
Object.defineProperty(el, 'clientHeight', { configurable: true, value: metrics.clientHeight })
|
||||
el.scrollTop = metrics.scrollTop
|
||||
}
|
||||
|
||||
describe('VirtualMessageList scroll behavior', () => {
|
||||
let rafCallbacks: FrameRequestCallback[]
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
rafCallbacks = []
|
||||
vi.stubGlobal('requestAnimationFrame', (callback: FrameRequestCallback) => {
|
||||
rafCallbacks.push(callback)
|
||||
return rafCallbacks.length
|
||||
})
|
||||
vi.stubGlobal('cancelAnimationFrame', (id: number) => {
|
||||
rafCallbacks[id - 1] = () => undefined
|
||||
})
|
||||
})
|
||||
|
||||
it('cancels queued bottom scrolling when the user scrolls away from the bottom', async () => {
|
||||
const wrapper = mount(VirtualMessageList, {
|
||||
props: {
|
||||
messages: [{ id: 'message-1' }],
|
||||
},
|
||||
slots: {
|
||||
item: '<div>message</div>',
|
||||
},
|
||||
})
|
||||
await nextTick()
|
||||
|
||||
const scroller = wrapper.find<HTMLElement>('.virtual-message-list')
|
||||
setScrollerMetrics(scroller.element, {
|
||||
scrollHeight: 1000,
|
||||
clientHeight: 400,
|
||||
scrollTop: 600,
|
||||
})
|
||||
|
||||
;(wrapper.vm as any).scrollToBottom({ frames: 5, keepAliveMs: 700 })
|
||||
await nextTick()
|
||||
expect(rafCallbacks.length).toBeGreaterThan(0)
|
||||
|
||||
scroller.element.scrollTop = 120
|
||||
await scroller.trigger('scroll')
|
||||
rafCallbacks.splice(0).forEach(callback => callback(performance.now()))
|
||||
|
||||
expect(dynamicScrollToBottomMock).not.toHaveBeenCalled()
|
||||
expect(scroller.element.scrollTop).toBe(120)
|
||||
})
|
||||
|
||||
it('locks auto-follow as soon as the user scrolls upward during streaming', async () => {
|
||||
const wrapper = mount(VirtualMessageList, {
|
||||
props: {
|
||||
messages: [{ id: 'message-1' }],
|
||||
},
|
||||
slots: {
|
||||
item: '<div>message</div>',
|
||||
},
|
||||
})
|
||||
await nextTick()
|
||||
|
||||
const scroller = wrapper.find<HTMLElement>('.virtual-message-list')
|
||||
setScrollerMetrics(scroller.element, {
|
||||
scrollHeight: 1000,
|
||||
clientHeight: 400,
|
||||
scrollTop: 600,
|
||||
})
|
||||
await scroller.trigger('scroll')
|
||||
|
||||
scroller.element.scrollTop = 580
|
||||
await scroller.trigger('scroll')
|
||||
|
||||
expect((wrapper.vm as any).isNearBottom(100)).toBe(true)
|
||||
expect((wrapper.vm as any).shouldAutoFollowBottom(100)).toBe(false)
|
||||
|
||||
scroller.element.scrollTop = 600
|
||||
await scroller.trigger('scroll')
|
||||
|
||||
expect((wrapper.vm as any).shouldAutoFollowBottom(100)).toBe(true)
|
||||
})
|
||||
|
||||
it('does not keep scrolling every animation frame for the whole keep-alive window', async () => {
|
||||
const wrapper = mount(VirtualMessageList, {
|
||||
props: {
|
||||
messages: [{ id: 'message-1' }],
|
||||
},
|
||||
slots: {
|
||||
item: '<div>message</div>',
|
||||
},
|
||||
})
|
||||
await nextTick()
|
||||
|
||||
const scroller = wrapper.find<HTMLElement>('.virtual-message-list')
|
||||
setScrollerMetrics(scroller.element, {
|
||||
scrollHeight: 1000,
|
||||
clientHeight: 400,
|
||||
scrollTop: 600,
|
||||
})
|
||||
|
||||
;(wrapper.vm as any).scrollToBottom({ frames: 2, keepAliveMs: 1200 })
|
||||
await nextTick()
|
||||
|
||||
for (let i = 0; i < 2; i += 1) {
|
||||
const callback = rafCallbacks.shift()
|
||||
expect(callback).toBeTypeOf('function')
|
||||
callback?.(performance.now())
|
||||
}
|
||||
|
||||
expect(dynamicScrollToBottomMock).toHaveBeenCalledTimes(2)
|
||||
expect(rafCallbacks).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('cancels bottom scrolling when the user scrolls upward during a programmatic scroll window', async () => {
|
||||
const wrapper = mount(VirtualMessageList, {
|
||||
props: {
|
||||
messages: [{ id: 'message-1' }],
|
||||
},
|
||||
slots: {
|
||||
item: '<div>message</div>',
|
||||
},
|
||||
})
|
||||
await nextTick()
|
||||
|
||||
const scroller = wrapper.find<HTMLElement>('.virtual-message-list')
|
||||
setScrollerMetrics(scroller.element, {
|
||||
scrollHeight: 1000,
|
||||
clientHeight: 400,
|
||||
scrollTop: 600,
|
||||
})
|
||||
|
||||
;(wrapper.vm as any).scrollToBottom({ frames: 5, keepAliveMs: 400 })
|
||||
await nextTick()
|
||||
|
||||
const firstFrame = rafCallbacks.shift()
|
||||
firstFrame?.(performance.now())
|
||||
expect(dynamicScrollToBottomMock).toHaveBeenCalledTimes(1)
|
||||
|
||||
scroller.element.scrollTop = 560
|
||||
await scroller.trigger('scroll')
|
||||
|
||||
rafCallbacks.splice(0).forEach(callback => callback(performance.now()))
|
||||
expect(dynamicScrollToBottomMock).toHaveBeenCalledTimes(1)
|
||||
expect((wrapper.vm as any).shouldAutoFollowBottom(100)).toBe(false)
|
||||
})
|
||||
|
||||
it('uses native scroll positioning when virtualization is disabled', async () => {
|
||||
const wrapper = mount(VirtualMessageList, {
|
||||
props: {
|
||||
messages: [{ id: 'message-1' }],
|
||||
virtualized: false,
|
||||
},
|
||||
slots: {
|
||||
item: '<div>message</div>',
|
||||
},
|
||||
})
|
||||
await nextTick()
|
||||
|
||||
const scroller = wrapper.find<HTMLElement>('.virtual-message-list')
|
||||
setScrollerMetrics(scroller.element, {
|
||||
scrollHeight: 1200,
|
||||
clientHeight: 400,
|
||||
scrollTop: 0,
|
||||
})
|
||||
|
||||
;(wrapper.vm as any).scrollToBottom({ frames: 1, keepAliveMs: 0 })
|
||||
await nextTick()
|
||||
rafCallbacks.splice(0).forEach(callback => callback(performance.now()))
|
||||
|
||||
expect(dynamicScrollToBottomMock).not.toHaveBeenCalled()
|
||||
expect(scroller.element.scrollTop).toBe(800)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user