Skip to content

Commit 2a71d9a

Browse files
committed
bench(useStep): add perf benchmarks
1 parent 4fcb03e commit 2a71d9a

File tree

1 file changed

+327
-0
lines changed

1 file changed

+327
-0
lines changed
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
// Composables
2+
import { useStep } from './index'
3+
4+
// Utilities
5+
import { describe, it, expect } from 'vitest'
6+
import { run, compare } from '#v0/utilities/benchmark'
7+
8+
describe('useStep benchmarks', () => {
9+
it('should benchmark registration operations', async () => {
10+
const context = useStep('benchmark-test')[2]
11+
12+
const result = await run('register 1000 step items', () => {
13+
for (let i = 0; i < 1000; i++) {
14+
context.register({ value: `item-${i}`, disabled: false })
15+
}
16+
}, 10)
17+
18+
expect(result.name).toBe('register 1000 step items')
19+
expect(result.duration).toBeGreaterThan(0)
20+
expect(result.ops).toBeGreaterThan(0)
21+
22+
console.log(`Step Registration: ${result.ops} ops/sec (${result.duration.toFixed(2)}ms avg)`)
23+
})
24+
25+
it('should benchmark selection operations', async () => {
26+
const context = useStep('benchmark-test')[2]
27+
28+
// Pre-populate with items
29+
const ids: (string | number)[] = []
30+
for (let i = 0; i < 1000; i++) {
31+
const item = context.register({ value: `item-${i}`, disabled: false })
32+
ids.push(item.id)
33+
}
34+
35+
const result = await run('select 1000 items', () => {
36+
for (const id of ids) {
37+
context.select(id)
38+
}
39+
}, 10)
40+
41+
expect(result.name).toBe('select 1000 items')
42+
expect(result.duration).toBeGreaterThan(0)
43+
expect(result.ops).toBeGreaterThan(0)
44+
45+
console.log(`Selection: ${result.ops} ops/sec (${result.duration.toFixed(2)}ms avg)`)
46+
})
47+
48+
it('should benchmark browse operations', async () => {
49+
const context = useStep('benchmark-test')[2]
50+
51+
// Pre-populate with items
52+
for (let i = 0; i < 1000; i++) {
53+
context.register({ value: `item-${i}`, disabled: false })
54+
}
55+
56+
const result = await run('browse 1000 values', () => {
57+
for (let i = 0; i < 1000; i++) {
58+
context.browse(`item-${i}`)
59+
}
60+
}, 10)
61+
62+
expect(result.name).toBe('browse 1000 values')
63+
expect(result.duration).toBeGreaterThan(0)
64+
expect(result.ops).toBeGreaterThan(0)
65+
66+
console.log(`Browse: ${result.ops} ops/sec (${result.duration.toFixed(2)}ms avg)`)
67+
})
68+
69+
it('should benchmark step navigation operations', async () => {
70+
const context = useStep('benchmark-test-step', {})[2]
71+
72+
// Pre-populate with items
73+
for (let i = 0; i < 100; i++) {
74+
context.register({ value: `item-${i}`, disabled: false })
75+
}
76+
77+
const result = await run('step navigation 1000 times', () => {
78+
for (let i = 0; i < 1000; i++) {
79+
if (i % 4 === 0) context.next()
80+
else if (i % 4 === 1) context.prev()
81+
else if (i % 4 === 2) context.step(5)
82+
else context.step(-3)
83+
}
84+
}, 50)
85+
86+
expect(result.name).toBe('step navigation 1000 times')
87+
expect(result.duration).toBeGreaterThan(0)
88+
expect(result.ops).toBeGreaterThan(0)
89+
90+
console.log(`Step Navigation: ${result.ops} ops/sec (${result.duration.toFixed(2)}ms avg)`)
91+
})
92+
93+
it('should benchmark reset operations', async () => {
94+
const context = useStep('benchmark-test')[2]
95+
96+
// Pre-populate with items and selection
97+
for (let i = 0; i < 1000; i++) {
98+
const item = context.register({ value: `item-${i}`, disabled: false })
99+
if (i === 500) context.select(item.id)
100+
}
101+
102+
const result = await run('reset 1000 items', () => {
103+
context.reset()
104+
}, 100)
105+
106+
expect(result.name).toBe('reset 1000 items')
107+
expect(result.duration).toBeGreaterThan(0)
108+
expect(result.ops).toBeGreaterThan(0)
109+
110+
console.log(`Reset: ${result.ops} ops/sec (${result.duration.toFixed(2)}ms avg)`)
111+
})
112+
113+
it('should benchmark mandate operations', async () => {
114+
const context = useStep('benchmark-test-mandatory', { mandatory: true })[2]
115+
116+
// Pre-populate with items
117+
for (let i = 0; i < 1000; i++) {
118+
context.register({ value: `item-${i}`, disabled: i % 100 === 0 })
119+
}
120+
121+
const result = await run('mandate selection', () => {
122+
context.selectedIds.clear()
123+
context.mandate()
124+
}, 100)
125+
126+
expect(result.name).toBe('mandate selection')
127+
expect(result.duration).toBeGreaterThan(0)
128+
expect(result.ops).toBeGreaterThan(0)
129+
130+
console.log(`Mandate: ${result.ops} ops/sec (${result.duration.toFixed(2)}ms avg)`)
131+
})
132+
133+
it('should compare different step operation types', async () => {
134+
const context = useStep('benchmark-test')[2]
135+
136+
// Pre-populate for operations
137+
const items: any[] = []
138+
for (let i = 0; i < 100; i++) {
139+
const item = context.register({ value: `item-${i}`, disabled: false })
140+
items.push(item)
141+
}
142+
143+
const results = await compare({
144+
'register 100 step items': () => {
145+
const ctx = useStep('register-test')[2]
146+
for (let i = 0; i < 100; i++) {
147+
ctx.register({ value: `item-${i}`, disabled: false })
148+
}
149+
},
150+
'select 100 items': () => {
151+
for (const item of items) {
152+
context.select(item.id)
153+
}
154+
},
155+
'browse 100 values': () => {
156+
for (let i = 0; i < 100; i++) {
157+
context.browse(`item-${i}`)
158+
}
159+
},
160+
'next 100 times': () => {
161+
for (let i = 0; i < 100; i++) {
162+
context.next()
163+
}
164+
},
165+
'prev 100 times': () => {
166+
for (let i = 0; i < 100; i++) {
167+
context.prev()
168+
}
169+
},
170+
'step(5) 100 times': () => {
171+
for (let i = 0; i < 100; i++) {
172+
context.step(5)
173+
}
174+
},
175+
'first/last 100 times': () => {
176+
for (let i = 0; i < 100; i++) {
177+
if (i % 2 === 0) context.first()
178+
else context.last()
179+
}
180+
},
181+
'reset 100 items': () => {
182+
context.reset()
183+
},
184+
})
185+
186+
expect(results).toHaveLength(8)
187+
188+
console.log('Step operation comparison (fastest to slowest):')
189+
for (const [index, result] of results.entries()) {
190+
console.log(`${index + 1}. ${result.name}: ${result.ops} ops/sec`)
191+
}
192+
})
193+
194+
it('should benchmark different step sizes', async () => {
195+
const sizes = [10, 100, 1000, 5000]
196+
197+
console.log('Step registration performance by collection size:')
198+
199+
for (const size of sizes) {
200+
const context = useStep(`benchmark-size-${size}`)[2]
201+
202+
const result = await run(`register ${size} step items`, () => {
203+
for (let i = 0; i < size; i++) {
204+
context.register({ value: `item-${i}`, disabled: false })
205+
}
206+
}, 5)
207+
208+
console.log(`Size ${size}: ${result.ops} ops/sec (${result.duration.toFixed(2)}ms avg)`)
209+
210+
expect(result.ops).toBeGreaterThan(0)
211+
}
212+
})
213+
214+
it('should benchmark different step configurations', async () => {
215+
const configs = [
216+
{ name: 'default', options: {} },
217+
{ name: 'mandatory', options: { mandatory: true } },
218+
{ name: 'returnObject', options: { returnObject: true } },
219+
{ name: 'mandatory + returnObject', options: { mandatory: true, returnObject: true } },
220+
]
221+
222+
console.log('Step registration performance by configuration:')
223+
224+
for (const config of configs) {
225+
const context = useStep(`benchmark-config-${config.name}`, config.options)[2]
226+
227+
const result = await run(`register 500 items (${config.name})`, () => {
228+
for (let i = 0; i < 500; i++) {
229+
context.register({ value: `item-${i}`, disabled: false })
230+
}
231+
}, 5)
232+
233+
console.log(`${config.name}: ${result.ops} ops/sec (${result.duration.toFixed(2)}ms avg)`)
234+
235+
expect(result.ops).toBeGreaterThan(0)
236+
}
237+
})
238+
239+
it('should benchmark step navigation with disabled items', async () => {
240+
const context = useStep('benchmark-disabled')[2]
241+
242+
// Pre-populate with items, some disabled
243+
for (let i = 0; i < 100; i++) {
244+
context.register({ value: `item-${i}`, disabled: i % 10 === 0 })
245+
}
246+
247+
const result = await run('navigate with disabled items', () => {
248+
for (let i = 0; i < 500; i++) {
249+
// Test navigation that needs to skip disabled items
250+
if (i % 3 === 0) context.next()
251+
else if (i % 3 === 1) context.prev()
252+
else context.step(7)
253+
}
254+
}, 10)
255+
256+
expect(result.name).toBe('navigate with disabled items')
257+
expect(result.duration).toBeGreaterThan(0)
258+
expect(result.ops).toBeGreaterThan(0)
259+
260+
console.log(`Navigation with Disabled Items: ${result.ops} ops/sec (${result.duration.toFixed(2)}ms avg)`)
261+
})
262+
263+
it('should benchmark step vs single performance comparison', async () => {
264+
const { useSingle } = await import('#v0/composables/useSingle')
265+
266+
const results = await compare({
267+
'useStep selection': () => {
268+
const ctx = useStep('step-overhead')[2]
269+
const ids: (string | number)[] = []
270+
271+
// Register items
272+
for (let i = 0; i < 100; i++) {
273+
const item = ctx.register({ value: `item-${i}`, disabled: false })
274+
ids.push(item.id)
275+
}
276+
277+
// Test selection pattern
278+
for (const id of ids) {
279+
ctx.select(id)
280+
}
281+
},
282+
'useSingle selection': () => {
283+
const ctx = useSingle('single-overhead')[2]
284+
const ids: (string | number)[] = []
285+
286+
// Register items
287+
for (let i = 0; i < 100; i++) {
288+
const item = ctx.register({ value: `item-${i}`, disabled: false })
289+
ids.push(item.id)
290+
}
291+
292+
// Test selection pattern
293+
for (const id of ids) {
294+
ctx.select(id)
295+
}
296+
},
297+
'useStep navigation': () => {
298+
const ctx = useStep('step-nav-overhead')[2]
299+
300+
// Register items
301+
for (let i = 0; i < 100; i++) {
302+
ctx.register({ value: `item-${i}`, disabled: false })
303+
}
304+
305+
// Test step-specific navigation
306+
for (let i = 0; i < 100; i++) {
307+
if (i % 4 === 0) ctx.next()
308+
else if (i % 4 === 1) ctx.prev()
309+
else if (i % 4 === 2) ctx.first()
310+
else ctx.last()
311+
}
312+
},
313+
})
314+
315+
expect(results).toHaveLength(3)
316+
317+
console.log('useStep vs useSingle performance comparison:')
318+
for (const [index, result] of results.entries()) {
319+
console.log(`${index + 1}. ${result.name}: ${result.ops} ops/sec`)
320+
}
321+
322+
const stepPerf = results.find(r => r.name.includes('useStep selection'))!
323+
const singlePerf = results.find(r => r.name.includes('useSingle'))!
324+
const overhead = ((singlePerf.ops - stepPerf.ops) / singlePerf.ops * 100).toFixed(2)
325+
console.log(`Performance difference: ${overhead}% ${overhead.startsWith('-') ? 'faster' : 'slower'} than useSingle`)
326+
})
327+
})

0 commit comments

Comments
 (0)