perf(sidebar): 保留兜底定位并限制全树查询频率
- 缓存节点连接期间按 1.5s safety-net 周期完整复核 - html style 变化时立即绕过 fast path 重新定位 - 覆盖流式热路径、定时复核、HMR 信号与节点替换行为
This commit is contained in:
committed by
Menghuan1918
parent
a6c7cf63c1
commit
f1da0fe345
@@ -1,53 +1,103 @@
|
||||
// @vitest-environment jsdom
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { resolveCenterColumn } from '../src/client/center-column.ts'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
CENTER_COLUMN_REVALIDATE_MS,
|
||||
resolveCenterColumn,
|
||||
} from '../src/client/center-column.ts'
|
||||
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = ''
|
||||
})
|
||||
interface Fixture {
|
||||
doc: Document
|
||||
col: HTMLDivElement
|
||||
slot: HTMLDivElement
|
||||
}
|
||||
|
||||
function connectedColumn(): { col: HTMLDivElement; slot: HTMLDivElement } {
|
||||
const col = document.createElement('div')
|
||||
const slot = document.createElement('div')
|
||||
function connectedColumn(doc = document.implementation.createHTMLDocument()): Fixture {
|
||||
const col = doc.createElement('div')
|
||||
const slot = doc.createElement('div')
|
||||
slot.dataset.slot = 'conversation'
|
||||
col.append(slot)
|
||||
document.body.append(col)
|
||||
return { col, slot }
|
||||
doc.body.append(col)
|
||||
return { doc, col, slot }
|
||||
}
|
||||
|
||||
describe('center-column locator (issue #403)', () => {
|
||||
it('reuses a connected cached column without querying the app tree', () => {
|
||||
const { col } = connectedColumn()
|
||||
const query = vi.fn<() => Element | null>(() => null)
|
||||
it('reuses a connected cached column without querying during the hot-path window', () => {
|
||||
const { doc, col, slot } = connectedColumn()
|
||||
let now = 0
|
||||
const query = vi.fn<() => Element | null>(() => slot)
|
||||
const options = { document: doc, query, now: () => now }
|
||||
|
||||
expect(resolveCenterColumn(col, query)).toBe(col)
|
||||
expect(resolveCenterColumn(col, options)).toBe(col)
|
||||
now = CENTER_COLUMN_REVALIDATE_MS - 1
|
||||
expect(resolveCenterColumn(col, options)).toBe(col)
|
||||
expect(query).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('queries and adopts the replacement after the cached column detaches', () => {
|
||||
const stale = connectedColumn().col
|
||||
it('fully revalidates a connected cache at the 1.5s safety-net boundary', () => {
|
||||
const { doc, col, slot } = connectedColumn()
|
||||
let now = 0
|
||||
const query = vi.fn<() => Element | null>(() => slot)
|
||||
const options = { document: doc, query, now: () => now }
|
||||
|
||||
expect(resolveCenterColumn(col, options)).toBe(col)
|
||||
expect(query).not.toHaveBeenCalled()
|
||||
|
||||
now = CENTER_COLUMN_REVALIDATE_MS
|
||||
expect(resolveCenterColumn(col, options)).toBe(col)
|
||||
expect(query).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('revalidates immediately when the html style signal changes', () => {
|
||||
const { doc, col, slot } = connectedColumn()
|
||||
let now = 0
|
||||
const query = vi.fn<() => Element | null>(() => slot)
|
||||
const options = { document: doc, query, now: () => now }
|
||||
|
||||
expect(resolveCenterColumn(col, options)).toBe(col)
|
||||
expect(query).not.toHaveBeenCalled()
|
||||
|
||||
doc.documentElement.style.setProperty('--dsh-sidebar-width', '320px')
|
||||
now = 1
|
||||
expect(resolveCenterColumn(col, options)).toBe(col)
|
||||
expect(query).toHaveBeenCalledTimes(1)
|
||||
|
||||
// The same fingerprint is cheap again; one style change causes one full
|
||||
// validation instead of turning later streaming mutations into queries.
|
||||
now = 2
|
||||
expect(resolveCenterColumn(col, options)).toBe(col)
|
||||
expect(query).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('queries and adopts the replacement as soon as the cached column detaches', () => {
|
||||
const first = connectedColumn()
|
||||
const stale = first.col
|
||||
stale.remove()
|
||||
const { col: replacement, slot } = connectedColumn()
|
||||
const { col: replacement, slot } = connectedColumn(first.doc)
|
||||
const query = vi.fn<() => Element | null>(() => slot)
|
||||
|
||||
expect(resolveCenterColumn(stale, query)).toBe(replacement)
|
||||
expect(resolveCenterColumn(stale, { document: first.doc, query, now: () => 1 })).toBe(replacement)
|
||||
expect(query).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('queries when no column has been cached yet', () => {
|
||||
const { col, slot } = connectedColumn()
|
||||
const { doc, col, slot } = connectedColumn()
|
||||
const query = vi.fn<() => Element | null>(() => slot)
|
||||
|
||||
expect(resolveCenterColumn(null, query)).toBe(col)
|
||||
expect(resolveCenterColumn(null, { document: doc, query, now: () => 0 })).toBe(col)
|
||||
expect(query).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('rejects a missing or detached query result', () => {
|
||||
expect(resolveCenterColumn(null, () => null)).toBeUndefined()
|
||||
const doc = document.implementation.createHTMLDocument()
|
||||
expect(resolveCenterColumn(null, { document: doc, query: () => null, now: () => 0 })).toBeUndefined()
|
||||
|
||||
const detachedCol = document.createElement('div')
|
||||
const detachedSlot = document.createElement('div')
|
||||
const detachedCol = doc.createElement('div')
|
||||
const detachedSlot = doc.createElement('div')
|
||||
detachedCol.append(detachedSlot)
|
||||
expect(resolveCenterColumn(null, () => detachedSlot)).toBeUndefined()
|
||||
expect(resolveCenterColumn(null, {
|
||||
document: doc,
|
||||
query: () => detachedSlot,
|
||||
now: () => 1,
|
||||
})).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user