hookehuyr

feat(checkin): 添加滚动恢复功能以改善用户体验

新增 useScrollRestoration 组合式函数,用于在打卡列表页面实现滚动位置恢复
当用户从打卡详情页返回时,自动恢复到之前的滚动位置,支持锚点定位和日历高度补偿
添加对应的单元测试,覆盖等待条件、超时处理和条件恢复等场景
<!--
* @Date: 2025-01-25 15:34:17
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2026-01-23 09:33:40
* @LastEditTime: 2026-01-23 10:38:40
* @FilePath: /mlaj/src/components/calendar/CollapsibleCalendar.vue
* @Description: 可折叠日历组件
-->
......
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
import { useScrollRestoration } from '../useScrollRestoration'
const create_session_storage = () => {
const store = {}
return {
store,
setItem: vi.fn((key, value) => {
store[key] = String(value)
}),
getItem: vi.fn((key) => {
if (Object.prototype.hasOwnProperty.call(store, key)) return store[key]
return null
}),
removeItem: vi.fn((key) => {
delete store[key]
}),
clear: vi.fn(() => {
Object.keys(store).forEach((key) => delete store[key])
}),
}
}
describe('useScrollRestoration', () => {
const original_window = globalThis.window
const original_session_storage = globalThis.sessionStorage
beforeEach(() => {
const session_storage = create_session_storage()
vi.stubGlobal('sessionStorage', session_storage)
const window_stub = {
scrollY: 0,
scrollTo: vi.fn(({ top }) => {
window_stub.scrollY = top
}),
}
vi.stubGlobal('window', window_stub)
})
afterEach(() => {
vi.unstubAllGlobals()
if (original_window !== undefined) vi.stubGlobal('window', original_window)
if (original_session_storage !== undefined) vi.stubGlobal('sessionStorage', original_session_storage)
vi.clearAllMocks()
})
it('restore_state 会等待 wait_for 达成后再执行滚动', async () => {
const { save_state, restore_state } = useScrollRestoration({
get_key: () => 'scroll_key',
get_scroll_el: () => window,
})
window.scrollY = 200
save_state({ extra: 1 })
window.scrollY = 0
let calls = 0
await restore_state({
wait_for: () => {
calls += 1
return calls >= 3
},
wait_for_timeout_ms: 200,
wait_for_interval_ms: 1,
settle_frames: 0,
get_scroll_top: () => 123,
})
expect(calls).toBeGreaterThanOrEqual(3)
expect(window.scrollTo).toHaveBeenCalled()
expect(window.scrollY).toBe(123)
})
it('wait_for 超时后仍会继续尝试恢复滚动', async () => {
const { save_state, restore_state } = useScrollRestoration({
get_key: () => 'scroll_key_timeout',
get_scroll_el: () => window,
})
window.scrollY = 80
save_state()
window.scrollY = 0
await restore_state({
wait_for: () => false,
wait_for_timeout_ms: 20,
wait_for_interval_ms: 5,
settle_frames: 0,
get_scroll_top: () => 50,
})
expect(window.scrollTo).toHaveBeenCalled()
expect(window.scrollY).toBe(50)
})
it('should_restore 返回 false 时不滚动且会清理状态', async () => {
const { save_state, restore_state, read_state } = useScrollRestoration({
get_key: () => 'scroll_key_should_restore',
get_scroll_el: () => window,
})
window.scrollY = 10
save_state()
await restore_state({
should_restore: () => false,
})
expect(window.scrollTo).not.toHaveBeenCalled()
expect(read_state()).toBeNull()
})
})
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.