Skip to content

Commit 4388803

Browse files
committed
feat(core): enhance batchUpdater tests with first render optimizations and class rendering tracking
1 parent d736e50 commit 4388803

File tree

4 files changed

+188
-6
lines changed

4 files changed

+188
-6
lines changed

packages/core/__tests__/batchUpdater.test.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ describe('batchUpdater', () => {
7272
})
7373

7474
it('should batch multiple updates together', () => {
75+
// 标记这些类为已渲染,这样它们就不会被立即执行
76+
batchUpdater.markAsRendered('class1')
77+
batchUpdater.markAsRendered('class2')
78+
batchUpdater.markAsRendered('class3')
79+
7580
batchUpdater.scheduleUpdate('class1', '.class1 { color: red; }')
7681
batchUpdater.scheduleUpdate('class2', '.class2 { color: blue; }')
7782
batchUpdater.scheduleUpdate('class3', '.class3 { color: green; }')
@@ -87,6 +92,10 @@ describe('batchUpdater', () => {
8792
})
8893

8994
it('should replace duplicate class updates', () => {
95+
// 标记类为已渲染,这样更新会被批量处理
96+
batchUpdater.markAsRendered('test-class')
97+
batchUpdater.markAsRendered('other-class')
98+
9099
batchUpdater.scheduleUpdate('test-class', '.test { color: red; }')
91100
batchUpdater.scheduleUpdate('test-class', '.test { color: blue; }') // 应该替换前一个
92101
batchUpdater.scheduleUpdate('other-class', '.other { color: green; }')
@@ -102,6 +111,11 @@ describe('batchUpdater', () => {
102111
})
103112

104113
it('should handle priority ordering', () => {
114+
// 标记类为已渲染,这样更新会被批量处理
115+
batchUpdater.markAsRendered('low-priority')
116+
batchUpdater.markAsRendered('high-priority')
117+
batchUpdater.markAsRendered('medium-priority')
118+
105119
batchUpdater.scheduleUpdate('low-priority', '.low { color: red; }', 1)
106120
batchUpdater.scheduleUpdate('high-priority', '.high { color: blue; }', 10)
107121
batchUpdater.scheduleUpdate('medium-priority', '.medium { color: green; }', 5)
@@ -281,6 +295,10 @@ describe('batchUpdater', () => {
281295
vi.useFakeTimers()
282296
configureStyleProcessing({ enableBatchUpdates: true })
283297

298+
// 标记类为已渲染,这样更新会被批量处理
299+
batchUpdater.markAsRendered('low')
300+
batchUpdater.markAsRendered('high')
301+
284302
batchUpdater.scheduleUpdate('low', '.low { color: red; }', -1)
285303
batchUpdater.scheduleUpdate('high', '.high { color: blue; }', 1)
286304

@@ -319,4 +337,112 @@ describe('batchUpdater', () => {
319337
expect(mockInsertFunction).not.toHaveBeenCalled()
320338
})
321339
})
340+
341+
describe('first render optimization', () => {
342+
beforeEach(() => {
343+
vi.useFakeTimers()
344+
configureStyleProcessing({ enableBatchUpdates: true, batchDelay: 16 })
345+
batchUpdater.resetFirstRenderState()
346+
})
347+
348+
it('should execute first render styles immediately even with batch updates enabled', () => {
349+
batchUpdater.scheduleUpdate('first-render-class', '.first { color: red; }')
350+
351+
// 首次渲染应该立即执行,不需要等待
352+
expect(mockInsertFunction).toHaveBeenCalledWith('first-render-class', '.first { color: red; }')
353+
expect(batchUpdater.getPendingCount()).toBe(0)
354+
})
355+
356+
it('should batch subsequent updates for the same class', () => {
357+
// 首次渲染
358+
batchUpdater.scheduleUpdate('test-class', '.test { color: red; }')
359+
expect(mockInsertFunction).toHaveBeenCalledTimes(1)
360+
361+
// 后续更新应该被批量处理
362+
batchUpdater.scheduleUpdate('test-class', '.test { color: blue; }')
363+
expect(mockInsertFunction).toHaveBeenCalledTimes(1) // 仍然是1次
364+
expect(batchUpdater.getPendingCount()).toBe(1)
365+
366+
// 等待批量更新执行
367+
vi.advanceTimersByTime(16)
368+
vi.runAllTimers()
369+
370+
expect(mockInsertFunction).toHaveBeenCalledTimes(2)
371+
expect(mockInsertFunction).toHaveBeenLastCalledWith('test-class', '.test { color: blue; }')
372+
})
373+
374+
it('should prioritize first render styles in batch updates', () => {
375+
// 先添加一个已渲染的类的更新
376+
batchUpdater.markAsRendered('already-rendered')
377+
batchUpdater.scheduleUpdate('already-rendered', '.already { color: green; }')
378+
379+
// 再添加一个首次渲染的类
380+
batchUpdater.resetFirstRenderState()
381+
batchUpdater.scheduleUpdate('first-render', '.first { color: red; }')
382+
383+
// 首次渲染应该立即执行
384+
expect(mockInsertFunction).toHaveBeenCalledWith('first-render', '.first { color: red; }')
385+
386+
// 等待批量更新执行已渲染的类
387+
vi.advanceTimersByTime(16)
388+
vi.runAllTimers()
389+
390+
expect(mockInsertFunction).toHaveBeenCalledWith('already-rendered', '.already { color: green; }')
391+
})
392+
393+
it('should track rendered classes correctly', () => {
394+
expect(batchUpdater.isFirstRender('new-class')).toBe(true)
395+
396+
batchUpdater.scheduleUpdate('new-class', '.new { color: red; }')
397+
398+
expect(batchUpdater.isFirstRender('new-class')).toBe(false)
399+
})
400+
401+
it('should allow manual marking of rendered classes', () => {
402+
expect(batchUpdater.isFirstRender('manual-class')).toBe(true)
403+
404+
batchUpdater.markAsRendered('manual-class')
405+
406+
expect(batchUpdater.isFirstRender('manual-class')).toBe(false)
407+
408+
// 现在这个类的更新应该被批量处理
409+
batchUpdater.scheduleUpdate('manual-class', '.manual { color: red; }')
410+
expect(mockInsertFunction).not.toHaveBeenCalled()
411+
expect(batchUpdater.getPendingCount()).toBe(1)
412+
})
413+
414+
it('should reset first render state correctly', () => {
415+
batchUpdater.scheduleUpdate('test-class', '.test { color: red; }')
416+
expect(batchUpdater.isFirstRender('test-class')).toBe(false)
417+
418+
batchUpdater.resetFirstRenderState()
419+
expect(batchUpdater.isFirstRender('test-class')).toBe(true)
420+
421+
// 重置后,相同类名的更新应该被视为首次渲染
422+
batchUpdater.scheduleUpdate('test-class', '.test { color: blue; }')
423+
expect(mockInsertFunction).toHaveBeenCalledWith('test-class', '.test { color: blue; }')
424+
})
425+
426+
it('should handle multiple first render classes simultaneously', () => {
427+
batchUpdater.scheduleUpdate('class1', '.class1 { color: red; }')
428+
batchUpdater.scheduleUpdate('class2', '.class2 { color: blue; }')
429+
batchUpdater.scheduleUpdate('class3', '.class3 { color: green; }')
430+
431+
// 所有首次渲染的类都应该立即执行
432+
expect(mockInsertFunction).toHaveBeenCalledTimes(3)
433+
expect(mockInsertFunction).toHaveBeenNthCalledWith(1, 'class1', '.class1 { color: red; }')
434+
expect(mockInsertFunction).toHaveBeenNthCalledWith(2, 'class2', '.class2 { color: blue; }')
435+
expect(mockInsertFunction).toHaveBeenNthCalledWith(3, 'class3', '.class3 { color: green; }')
436+
expect(batchUpdater.getPendingCount()).toBe(0)
437+
})
438+
439+
it('should respect batch updates disabled setting for first render', () => {
440+
configureStyleProcessing({ enableBatchUpdates: false })
441+
442+
batchUpdater.scheduleUpdate('test-class', '.test { color: red; }')
443+
444+
expect(mockInsertFunction).toHaveBeenCalledWith('test-class', '.test { color: red; }')
445+
expect(batchUpdater.getPendingCount()).toBe(0)
446+
})
447+
})
322448
})

packages/core/__tests__/performance.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ describe('performance Optimization', () => {
1414
resetStyleConfig()
1515
styleCache.clear()
1616
batchUpdater.clear()
17+
batchUpdater.resetFirstRenderState()
1718
performanceMonitor.reset()
1819
})
1920

@@ -110,6 +111,9 @@ describe('performance Optimization', () => {
110111
const mockInsert = vi.fn()
111112
batchUpdater.setInsertFunction(mockInsert)
112113

114+
// 标记为已渲染,避免首次渲染立即执行
115+
batchUpdater.markAsRendered('test-class')
116+
113117
batchUpdater.scheduleUpdate('test-class', '.test { color: red; }')
114118

115119
expect(batchUpdater.getPendingCount()).toBe(1)
@@ -133,6 +137,9 @@ describe('performance Optimization', () => {
133137
const mockInsert = vi.fn()
134138
batchUpdater.setInsertFunction(mockInsert)
135139

140+
// 标记为已渲染,确保进入队列而不是立即执行
141+
batchUpdater.markAsRendered('test-class')
142+
136143
batchUpdater.scheduleUpdate('test-class', '.test { color: red; }')
137144
batchUpdater.flushSync()
138145

@@ -143,6 +150,9 @@ describe('performance Optimization', () => {
143150
it('should replace duplicate class updates', () => {
144151
configureStyleProcessing({ enableBatchUpdates: true })
145152

153+
// 标记为已渲染,确保进入队列
154+
batchUpdater.markAsRendered('test-class')
155+
146156
batchUpdater.scheduleUpdate('test-class', '.test { color: red; }')
147157
batchUpdater.scheduleUpdate('test-class', '.test { color: blue; }')
148158

@@ -231,6 +241,10 @@ describe('performance Optimization', () => {
231241
const mockInsert = vi.fn()
232242
batchUpdater.setInsertFunction(mockInsert)
233243

244+
// 标记类名为已渲染,避免首次渲染立即执行的影响
245+
batchUpdater.markAsRendered('test-class')
246+
batchUpdater.markAsRendered('test-class-2')
247+
234248
// 第一次调用应该计算并缓存
235249
const expressions = ['color: red;']
236250
const context = { theme: {} }

packages/core/__tests__/styleManagement.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ describe('styleManagement', () => {
2727
resetStyleConfig()
2828
styleCache.clear()
2929
batchUpdater.clear()
30+
batchUpdater.resetFirstRenderState()
3031
performanceMonitor.reset()
3132
vi.clearAllMocks()
3233
})
@@ -35,6 +36,7 @@ describe('styleManagement', () => {
3536
resetStyleConfig()
3637
styleCache.clear()
3738
batchUpdater.clear()
39+
batchUpdater.resetFirstRenderState()
3840
performanceMonitor.reset()
3941
vi.restoreAllMocks()
4042
})
@@ -121,9 +123,11 @@ describe('styleManagement', () => {
121123
})
122124

123125
it('should insert style directly when batch updates are disabled', () => {
124-
configureStyleProcessing({ enableBatchUpdates: false })
126+
configureStyleProcessing({
127+
enableBatchUpdates: false,
128+
})
125129

126-
const className = 'test-class'
130+
const className = 'test-class-direct-insert'
127131
const cssWithExpression = ['color: red;']
128132
const context = { theme: {} }
129133

packages/core/src/utils/batchUpdater.ts

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ interface UpdateTask {
55
className: string
66
cssString: string
77
priority: number
8+
isFirstRender?: boolean
89
}
910

1011
class BatchUpdater {
1112
private pendingUpdates = new Map<string, UpdateTask>()
1213
private isUpdateScheduled = false
1314
private updateId = 0
15+
private renderedClasses = new Set<string>()
16+
private firstRenderThreshold = 100 // 100ms内认为是首次渲染
1417

1518
/**
1619
* 调度样式更新
@@ -22,9 +25,13 @@ class BatchUpdater {
2225
): void {
2326
const config = getStyleConfig()
2427

25-
if (!config.enableBatchUpdates) {
26-
// 如果未启用批量更新,立即执行
28+
// 检测是否为首次渲染
29+
const isFirstRender = !this.renderedClasses.has(className)
30+
31+
// 如果未启用批量更新或是首次渲染,立即执行
32+
if (!config.enableBatchUpdates || isFirstRender) {
2733
this.executeUpdate(className, cssString)
34+
this.renderedClasses.add(className)
2835
return
2936
}
3037

@@ -43,6 +50,7 @@ class BatchUpdater {
4350
className,
4451
cssString,
4552
priority,
53+
isFirstRender,
4654
})
4755

4856
if (!this.isUpdateScheduled) {
@@ -85,13 +93,22 @@ class BatchUpdater {
8593
startTime = performance.now()
8694
}
8795

88-
// 按优先级排序更新任务
96+
// 按优先级排序更新任务,首次渲染优先
8997
const tasks = Array.from(this.pendingUpdates.values())
90-
.sort((a, b) => b.priority - a.priority)
98+
.sort((a, b) => {
99+
// 首次渲染的样式优先级最高
100+
if (a.isFirstRender && !b.isFirstRender)
101+
return -1
102+
if (!a.isFirstRender && b.isFirstRender)
103+
return 1
104+
// 其次按优先级排序
105+
return b.priority - a.priority
106+
})
91107

92108
// 执行所有更新
93109
for (const task of tasks) {
94110
this.executeUpdate(task.className, task.cssString)
111+
this.renderedClasses.add(task.className)
95112
}
96113

97114
if (config.enablePerformanceMonitoring && startTime !== undefined) {
@@ -148,6 +165,27 @@ class BatchUpdater {
148165
this.pendingUpdates.clear()
149166
this.isUpdateScheduled = false
150167
}
168+
169+
/**
170+
* 重置首次渲染状态(用于测试或页面刷新)
171+
*/
172+
resetFirstRenderState(): void {
173+
this.renderedClasses.clear()
174+
}
175+
176+
/**
177+
* 标记类名为已渲染(用于手动控制)
178+
*/
179+
markAsRendered(className: string): void {
180+
this.renderedClasses.add(className)
181+
}
182+
183+
/**
184+
* 检查是否为首次渲染
185+
*/
186+
isFirstRender(className: string): boolean {
187+
return !this.renderedClasses.has(className)
188+
}
151189
}
152190

153191
export const batchUpdater = new BatchUpdater()

0 commit comments

Comments
 (0)