@aiquants/virtualscroll 1.20.0 → 1.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -0
- package/README.md +5 -3
- package/dist/ScrollBar.d.cts +123 -0
- package/dist/ScrollBar.d.ts +123 -0
- package/dist/ScrollBar.d.ts.map +1 -1
- package/dist/ScrollPane.d.cts +16 -0
- package/dist/ScrollPane.d.ts +16 -0
- package/dist/ScrollPane.d.ts.map +1 -1
- package/dist/TapScrollCircle.d.ts.map +1 -1
- package/dist/VirtualScroll.d.cts +9 -0
- package/dist/VirtualScroll.d.ts +9 -0
- package/dist/VirtualScroll.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1558 -1436
- package/dist/styles/virtualscroll.css +1 -1
- package/dist/styles/virtualscroll.standalone.css +1 -1
- package/dist/tapScrollCircleSampleVisual.d.ts.map +1 -1
- package/dist/useFenwickMapTree.d.cts +43 -0
- package/dist/useFenwickMapTree.d.ts +43 -0
- package/dist/useFenwickMapTree.d.ts.map +1 -1
- package/package.json +4 -1
- package/src/ScrollBar.tsx +218 -33
- package/src/ScrollPane.tsx +366 -24
- package/src/TapScrollCircle.tsx +10 -0
- package/src/VirtualScroll.tsx +11 -1
- package/src/index.ts +1 -1
- package/src/styles/virtualscroll.css +3 -3
- package/src/tapScrollCircleSampleVisual.tsx +22 -5
- package/src/useFenwickMapTree.ts +95 -13
- package/src/ScrollBar.spec.tsx +0 -620
- package/src/ScrollPane.spec.tsx +0 -496
- package/src/TapScrollCircle.spec.tsx +0 -275
- package/src/VirtualScroll.spec.ts +0 -641
- package/src/cli.server.spec.ts +0 -137
- package/src/logger.spec.ts +0 -128
- package/src/useFenwickMapTree.huge.spec.ts +0 -388
- package/src/useFenwickMapTree.spec.ts +0 -1518
- package/src/useLruCache.spec.ts +0 -382
- package/src/utils.spec.ts +0 -39
package/src/useLruCache.spec.ts
DELETED
|
@@ -1,382 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Tests for useLruCache hook.
|
|
3
|
-
* useLruCache フックのテスト。
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { act, renderHook } from "@testing-library/react"
|
|
7
|
-
import { describe, expect, it, vi } from "vitest"
|
|
8
|
-
import { useLruCache } from "./useLruCache"
|
|
9
|
-
|
|
10
|
-
describe("useLruCache フック", () => {
|
|
11
|
-
it("値を追加して取得できる", () => {
|
|
12
|
-
const { result } = renderHook(() => useLruCache<string, number>(2))
|
|
13
|
-
|
|
14
|
-
act(() => {
|
|
15
|
-
result.current.set("alpha", 1)
|
|
16
|
-
result.current.set("beta", 2)
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
expect(result.current.get("alpha")).toBe(1)
|
|
20
|
-
expect(result.current.get("beta")).toBe(2)
|
|
21
|
-
expect(result.current.has("alpha")).toBe(true)
|
|
22
|
-
expect(result.current.has("beta")).toBe(true)
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it("容量を超えると最も古い要素を削除する", () => {
|
|
26
|
-
const { result } = renderHook(() => useLruCache<string, number>(2))
|
|
27
|
-
|
|
28
|
-
act(() => {
|
|
29
|
-
result.current.set("first", 1)
|
|
30
|
-
result.current.set("second", 2)
|
|
31
|
-
result.current.set("third", 3)
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
expect(result.current.has("first")).toBe(false)
|
|
35
|
-
expect(result.current.has("second")).toBe(true)
|
|
36
|
-
expect(result.current.has("third")).toBe(true)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it("get の呼び出しで要素を最新扱いにする", () => {
|
|
40
|
-
const { result } = renderHook(() => useLruCache<string, number>(2))
|
|
41
|
-
|
|
42
|
-
act(() => {
|
|
43
|
-
result.current.set("first", 1)
|
|
44
|
-
result.current.set("second", 2)
|
|
45
|
-
result.current.get("first")
|
|
46
|
-
result.current.set("third", 3)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
expect(result.current.has("first")).toBe(true)
|
|
50
|
-
expect(result.current.has("second")).toBe(false)
|
|
51
|
-
expect(result.current.has("third")).toBe(true)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it("remove で指定キーを削除する", () => {
|
|
55
|
-
const { result } = renderHook(() => useLruCache<string, number>(2))
|
|
56
|
-
|
|
57
|
-
act(() => {
|
|
58
|
-
result.current.set("alpha", 1)
|
|
59
|
-
result.current.set("beta", 2)
|
|
60
|
-
result.current.remove("alpha")
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
expect(result.current.has("alpha")).toBe(false)
|
|
64
|
-
expect(result.current.get("alpha")).toBeUndefined()
|
|
65
|
-
expect(result.current.has("beta")).toBe(true)
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
it("clear ですべての要素を削除する", () => {
|
|
69
|
-
const { result } = renderHook(() => useLruCache<string, number>(2))
|
|
70
|
-
|
|
71
|
-
act(() => {
|
|
72
|
-
result.current.set("first", 1)
|
|
73
|
-
result.current.set("second", 2)
|
|
74
|
-
result.current.clear()
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
expect(result.current.has("first")).toBe(false)
|
|
78
|
-
expect(result.current.has("second")).toBe(false)
|
|
79
|
-
expect(result.current.get("first")).toBeUndefined()
|
|
80
|
-
expect(result.current.get("second")).toBeUndefined()
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
it("capacity に NaN を渡しても無制限に成長しない (eviction 無効化を防ぐ)", () => {
|
|
84
|
-
const { result } = renderHook(() => useLruCache<number, number>(Number.NaN))
|
|
85
|
-
|
|
86
|
-
act(() => {
|
|
87
|
-
for (let key = 0; key < 100; key++) {
|
|
88
|
-
result.current.set(key, key)
|
|
89
|
-
}
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
// NaN は有効な容量ではないため、キャッシュには何も保持されない (無制限成長を防止)
|
|
93
|
-
for (let key = 0; key < 100; key++) {
|
|
94
|
-
expect(result.current.has(key)).toBe(false)
|
|
95
|
-
}
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
it("capacity = Infinity は無制限キャッシュとして格納でき evict されない", () => {
|
|
99
|
-
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
|
|
100
|
-
try {
|
|
101
|
-
const { result } = renderHook(() => useLruCache<number, number>(Number.POSITIVE_INFINITY))
|
|
102
|
-
|
|
103
|
-
act(() => {
|
|
104
|
-
for (let key = 0; key < 500; key++) {
|
|
105
|
-
result.current.set(key, key * 10)
|
|
106
|
-
}
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
// Infinity は「無制限」なので全エントリが evict されずに保持される
|
|
110
|
-
for (let key = 0; key < 500; key++) {
|
|
111
|
-
expect(result.current.has(key)).toBe(true)
|
|
112
|
-
expect(result.current.get(key)).toBe(key * 10)
|
|
113
|
-
}
|
|
114
|
-
// 有効な容量なので警告は出ない
|
|
115
|
-
expect(warnSpy).not.toHaveBeenCalled()
|
|
116
|
-
} finally {
|
|
117
|
-
warnSpy.mockRestore()
|
|
118
|
-
}
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
it("capacity に NaN を渡すと格納せず警告をログする", () => {
|
|
122
|
-
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
|
|
123
|
-
try {
|
|
124
|
-
const { result } = renderHook(() => useLruCache<string, number>(Number.NaN))
|
|
125
|
-
|
|
126
|
-
act(() => {
|
|
127
|
-
result.current.set("alpha", 1)
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
// 無効容量なので格納されない
|
|
131
|
-
expect(result.current.has("alpha")).toBe(false)
|
|
132
|
-
expect(result.current.get("alpha")).toBeUndefined()
|
|
133
|
-
// capacity 変更 (マウント) につき 1 回だけ警告が出る
|
|
134
|
-
expect(warnSpy).toHaveBeenCalledTimes(1)
|
|
135
|
-
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("invalid capacity"))
|
|
136
|
-
} finally {
|
|
137
|
-
warnSpy.mockRestore()
|
|
138
|
-
}
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
it("capacity に負値を渡すと格納せず警告をログする", () => {
|
|
142
|
-
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
|
|
143
|
-
try {
|
|
144
|
-
const { result } = renderHook(() => useLruCache<string, number>(-5))
|
|
145
|
-
|
|
146
|
-
act(() => {
|
|
147
|
-
result.current.set("alpha", 1)
|
|
148
|
-
result.current.set("beta", 2)
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
// 無効容量なので格納されない
|
|
152
|
-
expect(result.current.has("alpha")).toBe(false)
|
|
153
|
-
expect(result.current.has("beta")).toBe(false)
|
|
154
|
-
// set の回数によらず、capacity 変更 (マウント) につき 1 回だけ警告が出る
|
|
155
|
-
expect(warnSpy).toHaveBeenCalledTimes(1)
|
|
156
|
-
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("invalid capacity"))
|
|
157
|
-
} finally {
|
|
158
|
-
warnSpy.mockRestore()
|
|
159
|
-
}
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
it("小数 capacity は floor に正規化され set の eviction が実効容量を守る", () => {
|
|
163
|
-
const { result } = renderHook(() => useLruCache<string, number>(2.5))
|
|
164
|
-
|
|
165
|
-
act(() => {
|
|
166
|
-
result.current.set("a", 1)
|
|
167
|
-
result.current.set("b", 2)
|
|
168
|
-
result.current.set("c", 3)
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
// floor(2.5) = 2 が実効容量なので、3 件目の追加で最古の "a" が evict される
|
|
172
|
-
expect(result.current.has("a")).toBe(false)
|
|
173
|
-
expect(result.current.has("b")).toBe(true)
|
|
174
|
-
expect(result.current.has("c")).toBe(true)
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
it("小数 capacity への縮小でも刈り込み effect が floor 値まで刈り込む (set と同一の実効値)", () => {
|
|
178
|
-
const { result, rerender } = renderHook(({ capacity }) => useLruCache<number, number>(capacity), {
|
|
179
|
-
initialProps: { capacity: 5 },
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
act(() => {
|
|
183
|
-
for (let key = 0; key < 5; key++) {
|
|
184
|
-
result.current.set(key, key)
|
|
185
|
-
}
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
// capacity を 5 → 2.5 に縮小: floor(2.5) = 2 件まで刈り込まれる
|
|
189
|
-
rerender({ capacity: 2.5 })
|
|
190
|
-
|
|
191
|
-
let count = 0
|
|
192
|
-
for (let key = 0; key < 5; key++) {
|
|
193
|
-
if (result.current.has(key)) {
|
|
194
|
-
count++
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
expect(count).toBe(2)
|
|
198
|
-
|
|
199
|
-
// 以後の set も同じ実効容量 2 を守る
|
|
200
|
-
act(() => {
|
|
201
|
-
result.current.set(100, 100)
|
|
202
|
-
})
|
|
203
|
-
let countAfterSet = 0
|
|
204
|
-
for (let key = 0; key <= 100; key++) {
|
|
205
|
-
if (result.current.has(key)) {
|
|
206
|
-
countAfterSet++
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
expect(countAfterSet).toBe(2)
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
it("capacity が有効値から NaN に変わると既存エントリを全消去し警告をログする", () => {
|
|
213
|
-
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
|
|
214
|
-
try {
|
|
215
|
-
const { result, rerender } = renderHook(({ capacity }) => useLruCache<string, number>(capacity), {
|
|
216
|
-
initialProps: { capacity: 10 },
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
act(() => {
|
|
220
|
-
result.current.set("alpha", 1)
|
|
221
|
-
result.current.set("beta", 2)
|
|
222
|
-
})
|
|
223
|
-
expect(result.current.has("alpha")).toBe(true)
|
|
224
|
-
|
|
225
|
-
// 有効値 → NaN への動的遷移
|
|
226
|
-
rerender({ capacity: Number.NaN })
|
|
227
|
-
|
|
228
|
-
// 警告文言「the cache will not store any entries」どおり、既存エントリも保持されない
|
|
229
|
-
expect(result.current.has("alpha")).toBe(false)
|
|
230
|
-
expect(result.current.has("beta")).toBe(false)
|
|
231
|
-
expect(result.current.get("alpha")).toBeUndefined()
|
|
232
|
-
expect(warnSpy).toHaveBeenCalledTimes(1)
|
|
233
|
-
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("invalid capacity"))
|
|
234
|
-
} finally {
|
|
235
|
-
warnSpy.mockRestore()
|
|
236
|
-
}
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
it("capacity が有効値から負値に変わると既存エントリを全消去する", () => {
|
|
240
|
-
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
|
|
241
|
-
try {
|
|
242
|
-
const { result, rerender } = renderHook(({ capacity }) => useLruCache<string, number>(capacity), {
|
|
243
|
-
initialProps: { capacity: 10 },
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
act(() => {
|
|
247
|
-
result.current.set("alpha", 1)
|
|
248
|
-
})
|
|
249
|
-
expect(result.current.has("alpha")).toBe(true)
|
|
250
|
-
|
|
251
|
-
// 有効値 → 負値への動的遷移
|
|
252
|
-
rerender({ capacity: -1 })
|
|
253
|
-
|
|
254
|
-
expect(result.current.has("alpha")).toBe(false)
|
|
255
|
-
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("invalid capacity"))
|
|
256
|
-
|
|
257
|
-
// 無効容量のままの set は no-op
|
|
258
|
-
act(() => {
|
|
259
|
-
result.current.set("beta", 2)
|
|
260
|
-
})
|
|
261
|
-
expect(result.current.has("beta")).toBe(false)
|
|
262
|
-
} finally {
|
|
263
|
-
warnSpy.mockRestore()
|
|
264
|
-
}
|
|
265
|
-
})
|
|
266
|
-
|
|
267
|
-
it("capacity を有限値から Infinity に変更すると以後は無制限に格納できる (仕様維持)", () => {
|
|
268
|
-
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {})
|
|
269
|
-
try {
|
|
270
|
-
const { result, rerender } = renderHook(({ capacity }) => useLruCache<number, number>(capacity), {
|
|
271
|
-
initialProps: { capacity: 2 },
|
|
272
|
-
})
|
|
273
|
-
|
|
274
|
-
act(() => {
|
|
275
|
-
result.current.set(0, 0)
|
|
276
|
-
result.current.set(1, 1)
|
|
277
|
-
result.current.set(2, 2)
|
|
278
|
-
})
|
|
279
|
-
// 有限容量 2 の間は evict される
|
|
280
|
-
expect(result.current.has(0)).toBe(false)
|
|
281
|
-
|
|
282
|
-
// Infinity へ動的変更: 無制限キャッシュとして格納・保持できる
|
|
283
|
-
rerender({ capacity: Number.POSITIVE_INFINITY })
|
|
284
|
-
act(() => {
|
|
285
|
-
for (let key = 10; key < 110; key++) {
|
|
286
|
-
result.current.set(key, key)
|
|
287
|
-
}
|
|
288
|
-
})
|
|
289
|
-
for (let key = 10; key < 110; key++) {
|
|
290
|
-
expect(result.current.has(key)).toBe(true)
|
|
291
|
-
}
|
|
292
|
-
// Infinity は有効な容量なので警告は出ない
|
|
293
|
-
expect(warnSpy).not.toHaveBeenCalled()
|
|
294
|
-
} finally {
|
|
295
|
-
warnSpy.mockRestore()
|
|
296
|
-
}
|
|
297
|
-
})
|
|
298
|
-
|
|
299
|
-
it("handler の識別子がマウント後の再レンダーで安定している", () => {
|
|
300
|
-
const { result, rerender } = renderHook(() => useLruCache<string, number>(2))
|
|
301
|
-
|
|
302
|
-
const firstHandler = result.current
|
|
303
|
-
rerender()
|
|
304
|
-
|
|
305
|
-
// useMemo により余計な再生成が起きないので同一参照であること
|
|
306
|
-
expect(result.current).toBe(firstHandler)
|
|
307
|
-
expect(result.current.get).toBe(firstHandler.get)
|
|
308
|
-
expect(result.current.set).toBe(firstHandler.set)
|
|
309
|
-
})
|
|
310
|
-
|
|
311
|
-
it("capacity を縮小すると以後の set が新容量を守る (stale set 公開を防ぐ)", () => {
|
|
312
|
-
const { result, rerender } = renderHook(({ capacity }) => useLruCache<number, number>(capacity), {
|
|
313
|
-
initialProps: { capacity: 100 },
|
|
314
|
-
})
|
|
315
|
-
|
|
316
|
-
act(() => {
|
|
317
|
-
for (let key = 0; key < 11; key++) {
|
|
318
|
-
result.current.set(key, key)
|
|
319
|
-
}
|
|
320
|
-
})
|
|
321
|
-
expect(result.current.has(0)).toBe(true)
|
|
322
|
-
|
|
323
|
-
// capacity を 100 → 5 に縮小
|
|
324
|
-
rerender({ capacity: 5 })
|
|
325
|
-
|
|
326
|
-
act(() => {
|
|
327
|
-
result.current.set(100, 100)
|
|
328
|
-
})
|
|
329
|
-
|
|
330
|
-
// 刈り込み effect と最新 capacity を参照する set により、サイズは新容量 5 を超えない
|
|
331
|
-
let count = 0
|
|
332
|
-
for (let key = 0; key <= 100; key++) {
|
|
333
|
-
if (result.current.has(key)) {
|
|
334
|
-
count++
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
expect(count).toBe(5)
|
|
338
|
-
})
|
|
339
|
-
|
|
340
|
-
it("Number.MAX_SAFE_INTEGER 回の追加後でも値取得と全削除ができる", () => {
|
|
341
|
-
const capacity = 3
|
|
342
|
-
const { result } = renderHook(() => useLruCache<number, string>(capacity))
|
|
343
|
-
|
|
344
|
-
act(() => {
|
|
345
|
-
result.current.set(-1, "initial")
|
|
346
|
-
// LRU の性質上、末尾の capacity 件の追加で全体と同じ最終状態になる
|
|
347
|
-
for (let key = Number.MAX_SAFE_INTEGER - capacity + 1; key <= Number.MAX_SAFE_INTEGER; key++) {
|
|
348
|
-
result.current.set(key, `value-${key}`)
|
|
349
|
-
}
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
expect(result.current.has(-1)).toBe(false)
|
|
353
|
-
for (let key = Number.MAX_SAFE_INTEGER - capacity + 1; key <= Number.MAX_SAFE_INTEGER; key++) {
|
|
354
|
-
expect(result.current.has(key)).toBe(true)
|
|
355
|
-
expect(result.current.get(key)).toBe(`value-${key}`)
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
act(() => {
|
|
359
|
-
for (let key = Number.MAX_SAFE_INTEGER - capacity + 1; key <= Number.MAX_SAFE_INTEGER; key++) {
|
|
360
|
-
result.current.remove(key)
|
|
361
|
-
}
|
|
362
|
-
})
|
|
363
|
-
|
|
364
|
-
for (let key = Number.MAX_SAFE_INTEGER - capacity + 1; key <= Number.MAX_SAFE_INTEGER; key++) {
|
|
365
|
-
expect(result.current.has(key)).toBe(false)
|
|
366
|
-
expect(result.current.get(key)).toBeUndefined()
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
act(() => {
|
|
370
|
-
for (let key = Number.MAX_SAFE_INTEGER - capacity + 1; key <= Number.MAX_SAFE_INTEGER; key++) {
|
|
371
|
-
result.current.set(key, `value-${key}`)
|
|
372
|
-
}
|
|
373
|
-
result.current.clear()
|
|
374
|
-
})
|
|
375
|
-
|
|
376
|
-
expect(result.current.has(-1)).toBe(false)
|
|
377
|
-
for (let key = Number.MAX_SAFE_INTEGER - capacity + 1; key <= Number.MAX_SAFE_INTEGER; key++) {
|
|
378
|
-
expect(result.current.has(key)).toBe(false)
|
|
379
|
-
expect(result.current.get(key)).toBeUndefined()
|
|
380
|
-
}
|
|
381
|
-
})
|
|
382
|
-
})
|
package/src/utils.spec.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Tests for utils.
|
|
3
|
-
* utils のテスト。
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { describe, expect, it } from "vitest"
|
|
7
|
-
import { minmax } from "./utils"
|
|
8
|
-
|
|
9
|
-
describe("minmax", () => {
|
|
10
|
-
it("値を [min, max] にクランプする", () => {
|
|
11
|
-
expect(minmax(5, 0, 10)).toBe(5)
|
|
12
|
-
expect(minmax(-3, 0, 10)).toBe(0)
|
|
13
|
-
expect(minmax(42, 0, 10)).toBe(10)
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
it("通常の min <= max では従来と同一結果を返す(境界含む)", () => {
|
|
17
|
-
// 従来実装 Math.min(max, Math.max(min, value)) と一致することを保証。
|
|
18
|
-
for (const [value, min, max] of [
|
|
19
|
-
[5, 0, 10],
|
|
20
|
-
[0, 0, 10],
|
|
21
|
-
[10, 0, 10],
|
|
22
|
-
[-1, 0, 10],
|
|
23
|
-
[11, 0, 10],
|
|
24
|
-
[-100, -50, 50],
|
|
25
|
-
[7, 7, 7],
|
|
26
|
-
] as const) {
|
|
27
|
-
expect(minmax(value, min, max)).toBe(Math.min(max, Math.max(min, value)))
|
|
28
|
-
}
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it("退化入力 min > max でも下限 (min) を下回らない", () => {
|
|
32
|
-
// 回帰: 旧実装 Math.min(max, Math.max(min, value)) は max を返し
|
|
33
|
-
// min を割り込んでいた (例: 空リストで safeIndex=-1 が漏れる)。
|
|
34
|
-
expect(minmax(5, 0, -1)).toBe(0)
|
|
35
|
-
expect(minmax(0, 0, -1)).toBe(0)
|
|
36
|
-
expect(minmax(-5, 0, -1)).toBe(0)
|
|
37
|
-
expect(minmax(100, 3, 1)).toBe(3)
|
|
38
|
-
})
|
|
39
|
-
})
|