⚡ perf(vscode): 代码阅读预览按卡片订阅状态:中文名、调用树棵数、单击高亮、问答一条消息只重画受影响的卡片和文字,新文档里内容没变的块沿用原来的对象不重画,没展开又没说过的块不画问答区
This commit is contained in:
1 parent
6e2c541558
commit
0ad4748cc6
27 files changed
+1359
-620
No files matched your search
@@ -9,6 +9,58 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { App } from './App'
|
||||
import { createViewStateStore } from './viewState'
|
||||
|
||||
/**
|
||||
* 一次次提交里哪些组件真的画了(新挂上的不算):赶在 react-dom 加载之前挂上 React 开发者工具的钩子,每次提交顺着 fiber 树数。
|
||||
* 跳过重画的整棵子树(fiber 和上一次的共用子节点)不往下数。
|
||||
*/
|
||||
const renders = vi.hoisted(() => {
|
||||
interface Fiber { type: unknown, elementType: unknown, alternate: Fiber | null, flags: number, child: Fiber | null, sibling: Fiber | null }
|
||||
const counts = new Map<string, number>()
|
||||
let counting = false
|
||||
/** 组件叫什么:memo 包着的看它的 displayName,别的看函数名 */
|
||||
const nameOf = (fiber: Fiber): string | undefined => {
|
||||
const memoName = (fiber.elementType as { displayName?: string } | null)?.displayName
|
||||
return memoName ?? (typeof fiber.type === 'function' ? fiber.type.name : undefined)
|
||||
}
|
||||
const walk = (root: { current: Fiber }): void => {
|
||||
const stack = [root.current]
|
||||
while (stack.length > 0) {
|
||||
const fiber = stack.pop()!
|
||||
const name = nameOf(fiber)
|
||||
// flags 的第 1 位(PerformedWork):这次真的调用了组件函数
|
||||
if (name && fiber.alternate && (fiber.flags & 1))
|
||||
counts.set(name, (counts.get(name) ?? 0) + 1)
|
||||
if (fiber.alternate && fiber.child === fiber.alternate.child)
|
||||
continue
|
||||
for (let child = fiber.child; child; child = child.sibling)
|
||||
stack.push(child)
|
||||
}
|
||||
}
|
||||
Object.assign(globalThis, {
|
||||
__REACT_DEVTOOLS_GLOBAL_HOOK__: {
|
||||
supportsFiber: true,
|
||||
isDisabled: false,
|
||||
renderers: new Map(),
|
||||
inject: () => 1,
|
||||
checkDCE: () => {},
|
||||
onScheduleFiberRoot: () => {},
|
||||
onCommitFiberRoot: (_id: number, root: { current: Fiber }) => counting && walk(root),
|
||||
onCommitFiberUnmount: () => {},
|
||||
onPostCommitFiberRoot: () => {},
|
||||
},
|
||||
})
|
||||
return {
|
||||
start: () => {
|
||||
counts.clear()
|
||||
counting = true
|
||||
},
|
||||
stop: (): Record<string, number> => {
|
||||
counting = false
|
||||
return Object.fromEntries(counts)
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
/** 扩展端那头:页面发出去的消息、页面挂着的收消息回调,以及 webview 自己存的状态(页面重新加载时还在) */
|
||||
const host = vi.hoisted(() => ({
|
||||
posted: [] as unknown[],
|
||||
@@ -171,3 +223,70 @@ describe('代码配色跟着当前颜色主题', () => {
|
||||
expect(style.getPropertyValue('--cr-kind-loop')).toBe('#AF00DB')
|
||||
})
|
||||
})
|
||||
|
||||
describe('一条消息只重画受影响的卡片', () => {
|
||||
/** 三张卡片,译文里各有一个带下标的名字(下标 10、20、30) */
|
||||
function named(id: string, name: string, offset: number, text = '调用'): BlockView {
|
||||
return { ...block(id, name), translation: [{ text, role: 'keyword' }, { text: name, role: 'function', offset }] }
|
||||
}
|
||||
const DOC = documentOf(3, [named('0', 'load', 10), named('1', 'save', 20), named('2', 'drop', 30)])
|
||||
/** 扩展端每次发来的都是新对象:深拷一份 */
|
||||
const copy = <T,>(value: T): T => JSON.parse(JSON.stringify(value)) as T
|
||||
|
||||
async function rendered(msg: ExtensionToWebview): Promise<Record<string, number>> {
|
||||
renders.start()
|
||||
await deliver(msg)
|
||||
return renders.stop()
|
||||
}
|
||||
|
||||
it('中文名到了:只重画换了字的那段文字,卡片不动', async () => {
|
||||
await load()
|
||||
await deliver(copy(DOC))
|
||||
const counts = await rendered({ type: 'displayNames', version: 3, names: [{ offset: 20, length: 4, name: '保存' }] })
|
||||
expect(counts).toEqual({ SegmentText: 1 })
|
||||
expect(container.querySelector('[data-offset="20"]')?.textContent).toBe('保存')
|
||||
})
|
||||
|
||||
it('调用树棵数到了:只重画棵数变了的那几块', async () => {
|
||||
await load()
|
||||
await deliver(copy(DOC))
|
||||
const counts = await rendered({ type: 'callTreeCounts', version: 3, counts: { save: 2 } })
|
||||
expect(counts.BlockItem).toBe(1)
|
||||
expect(counts.SegmentText).toBeUndefined()
|
||||
expect(container.querySelector('[data-call-tree="save"]')?.textContent).toBe('2')
|
||||
})
|
||||
|
||||
it('同一版文档重发、只改了一块的新版本:内容没变的卡片不重画', async () => {
|
||||
await load()
|
||||
await deliver(copy(DOC))
|
||||
expect(await rendered(copy(DOC))).not.toHaveProperty('BlockItem')
|
||||
const edited = documentOf(4, [named('0', 'load', 10), named('1', 'save', 20, '调用了'), named('2', 'drop', 30)])
|
||||
const counts = await rendered(copy(edited))
|
||||
expect(counts.BlockItem).toBe(1)
|
||||
expect(container.querySelector('[data-id="1"]')?.textContent).toContain('调用了')
|
||||
})
|
||||
|
||||
it('单击名字高亮:只重画高亮落到的那段文字', async () => {
|
||||
await load()
|
||||
await deliver(copy(DOC))
|
||||
await act(async () => container.querySelector<HTMLElement>('[data-offset="30"]')!.click())
|
||||
const asked = [...host.posted].reverse().find((msg): msg is Extract<WebviewToExtension, { type: 'findOccurrences' }> => (msg as WebviewToExtension).type === 'findOccurrences')!
|
||||
const counts = await rendered({ type: 'occurrencesFound', id: asked.id, occurrences: [{ start: 30, end: 34, kind: 'read' }] })
|
||||
expect(counts.SegmentText).toBe(1)
|
||||
expect(counts.BlockItem).toBeUndefined()
|
||||
expect(container.querySelector('[data-offset="30"]')?.getAttribute('data-occurrence')).toBe('normal')
|
||||
})
|
||||
|
||||
it('没展开、没说过的块下面不画问答区;展开一块只重画那一块', async () => {
|
||||
await load()
|
||||
await deliver({ type: 'qa', available: true, threads: [], path: [], subject: 'file', current: [], rewrites: [], rewriteCurrent: [] })
|
||||
await deliver(copy(DOC))
|
||||
// 顶部那条一直摊开
|
||||
expect(container.querySelectorAll('.cr-qa')).toHaveLength(1)
|
||||
renders.start()
|
||||
await act(async () => container.querySelector<HTMLButtonElement>('[data-id="1"] [aria-label="问 AI"]')!.click())
|
||||
const counts = renders.stop()
|
||||
expect(counts.BlockItem).toBe(1)
|
||||
expect(container.querySelectorAll('.cr-qa')).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user