@usefy/use-timer 0.0.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/README.md +492 -0
- package/dist/index.d.mts +275 -0
- package/dist/index.d.ts +275 -0
- package/dist/index.js +436 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +404 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/geon0529/usefy/master/assets/logo.png" alt="usefy logo" width="120" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">@usefy/use-timer</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>A powerful, accurate countdown timer hook for React applications</strong>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/@usefy/use-timer">
|
|
13
|
+
<img src="https://img.shields.io/npm/v/@usefy/use-timer.svg?style=flat-square&color=007acc" alt="npm version" />
|
|
14
|
+
</a>
|
|
15
|
+
<a href="https://www.npmjs.com/package/@usefy/use-timer">
|
|
16
|
+
<img src="https://img.shields.io/npm/dm/@usefy/use-timer.svg?style=flat-square&color=007acc" alt="npm downloads" />
|
|
17
|
+
</a>
|
|
18
|
+
<a href="https://bundlephobia.com/package/@usefy/use-timer">
|
|
19
|
+
<img src="https://img.shields.io/bundlephobia/minzip/@usefy/use-timer?style=flat-square&color=007acc" alt="bundle size" />
|
|
20
|
+
</a>
|
|
21
|
+
<a href="https://github.com/geon0529/usefy/blob/master/LICENSE">
|
|
22
|
+
<img src="https://img.shields.io/npm/l/@usefy/use-timer.svg?style=flat-square&color=007acc" alt="license" />
|
|
23
|
+
</a>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
<p align="center">
|
|
27
|
+
<a href="#installation">Installation</a> •
|
|
28
|
+
<a href="#quick-start">Quick Start</a> •
|
|
29
|
+
<a href="#api-reference">API Reference</a> •
|
|
30
|
+
<a href="#examples">Examples</a> •
|
|
31
|
+
<a href="#license">License</a>
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Overview
|
|
37
|
+
|
|
38
|
+
`@usefy/use-timer` is a feature-rich countdown timer hook for React applications. It provides accurate time tracking with drift compensation, multiple time formats, flexible controls, and smart render optimization.
|
|
39
|
+
|
|
40
|
+
**Part of the [@usefy](https://www.npmjs.com/org/usefy) ecosystem** — a collection of production-ready React hooks designed for modern applications.
|
|
41
|
+
|
|
42
|
+
### Why use-timer?
|
|
43
|
+
|
|
44
|
+
- **Accurate Timing** — Uses `performance.now()` with drift compensation for precise countdowns
|
|
45
|
+
- **Smart Render Optimization** — Only re-renders when the formatted time actually changes
|
|
46
|
+
- **Multiple Formats** — Built-in formats (`MM:SS`, `HH:MM:SS`, `mm:ss.SSS`) or custom formatters
|
|
47
|
+
- **Full Control** — Start, pause, stop, reset, restart, and toggle controls
|
|
48
|
+
- **Time Manipulation** — Add, subtract, or set time dynamically during countdown
|
|
49
|
+
- **Background Tab Support** — Visibility API integration for accurate time when switching tabs
|
|
50
|
+
- **Loop Mode** — Automatically restart the timer when it completes
|
|
51
|
+
- **TypeScript First** — Full type safety with comprehensive type definitions
|
|
52
|
+
- **Zero Dependencies** — Pure React implementation
|
|
53
|
+
- **Well Tested** — 123 tests with comprehensive coverage
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# npm
|
|
61
|
+
npm install @usefy/use-timer
|
|
62
|
+
|
|
63
|
+
# yarn
|
|
64
|
+
yarn add @usefy/use-timer
|
|
65
|
+
|
|
66
|
+
# pnpm
|
|
67
|
+
pnpm add @usefy/use-timer
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Peer Dependencies
|
|
71
|
+
|
|
72
|
+
This package requires React 18 or 19:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"peerDependencies": {
|
|
77
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Quick Start
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
import { useTimer, ms } from "@usefy/use-timer";
|
|
88
|
+
|
|
89
|
+
function Timer() {
|
|
90
|
+
const timer = useTimer(ms.minutes(5), { format: "MM:SS" });
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div>
|
|
94
|
+
<p>{timer.formattedTime}</p>
|
|
95
|
+
<button onClick={timer.toggle}>
|
|
96
|
+
{timer.isRunning ? "Pause" : "Start"}
|
|
97
|
+
</button>
|
|
98
|
+
<button onClick={timer.reset}>Reset</button>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## API Reference
|
|
107
|
+
|
|
108
|
+
### `useTimer(initialTimeMs, options?)`
|
|
109
|
+
|
|
110
|
+
A hook that manages countdown timer state with comprehensive controls.
|
|
111
|
+
|
|
112
|
+
#### Parameters
|
|
113
|
+
|
|
114
|
+
| Parameter | Type | Description |
|
|
115
|
+
| --------------- | ----------------- | -------------------------------------- |
|
|
116
|
+
| `initialTimeMs` | `number` | Initial countdown time in milliseconds |
|
|
117
|
+
| `options` | `UseTimerOptions` | Optional configuration object |
|
|
118
|
+
|
|
119
|
+
#### Options
|
|
120
|
+
|
|
121
|
+
| Option | Type | Default | Description |
|
|
122
|
+
| ------------ | ----------------------------- | --------- | ------------------------------------------------ |
|
|
123
|
+
| `interval` | `number` | `1` | Update interval in milliseconds |
|
|
124
|
+
| `format` | `TimeFormat \| TimeFormatter` | `"MM:SS"` | Time display format or custom formatter function |
|
|
125
|
+
| `autoStart` | `boolean` | `false` | Start timer automatically on mount |
|
|
126
|
+
| `loop` | `boolean` | `false` | Restart timer automatically when it completes |
|
|
127
|
+
| `useRAF` | `boolean` | `false` | Use `requestAnimationFrame` instead of interval |
|
|
128
|
+
| `onComplete` | `() => void` | - | Callback when timer reaches 0 |
|
|
129
|
+
| `onTick` | `(remaining: number) => void` | - | Callback on each tick with remaining time |
|
|
130
|
+
| `onStart` | `() => void` | - | Callback when timer starts |
|
|
131
|
+
| `onPause` | `() => void` | - | Callback when timer is paused |
|
|
132
|
+
| `onReset` | `() => void` | - | Callback when timer is reset |
|
|
133
|
+
| `onStop` | `() => void` | - | Callback when timer is stopped |
|
|
134
|
+
|
|
135
|
+
#### Time Formats
|
|
136
|
+
|
|
137
|
+
| Format | Example | Description |
|
|
138
|
+
| ---------------- | -------------- | ---------------------------------- |
|
|
139
|
+
| `"MM:SS"` | `05:30` | Minutes and seconds |
|
|
140
|
+
| `"HH:MM:SS"` | `01:05:30` | Hours, minutes, and seconds |
|
|
141
|
+
| `"SS"` | `330` | Total seconds |
|
|
142
|
+
| `"mm:ss.SSS"` | `05:30.123` | Minutes, seconds, and milliseconds |
|
|
143
|
+
| `"HH:MM:SS.SSS"` | `01:05:30.123` | Full format with milliseconds |
|
|
144
|
+
| Custom function | Any string | `(ms: number) => string` |
|
|
145
|
+
|
|
146
|
+
#### Returns `UseTimerReturn`
|
|
147
|
+
|
|
148
|
+
| Property | Type | Description |
|
|
149
|
+
| --------------- | ---------------------- | ------------------------------------------------------- |
|
|
150
|
+
| `time` | `number` | Current remaining time in milliseconds |
|
|
151
|
+
| `initialTime` | `number` | Initial time value |
|
|
152
|
+
| `formattedTime` | `string` | Formatted time string |
|
|
153
|
+
| `progress` | `number` | Progress percentage (0-100) |
|
|
154
|
+
| `status` | `TimerStatus` | Current status: `idle`, `running`, `paused`, `finished` |
|
|
155
|
+
| `isRunning` | `boolean` | Whether timer is running |
|
|
156
|
+
| `isPaused` | `boolean` | Whether timer is paused |
|
|
157
|
+
| `isFinished` | `boolean` | Whether timer has finished |
|
|
158
|
+
| `isIdle` | `boolean` | Whether timer is idle |
|
|
159
|
+
| `hours` | `number` | Decomposed hours component |
|
|
160
|
+
| `minutes` | `number` | Decomposed minutes component |
|
|
161
|
+
| `seconds` | `number` | Decomposed seconds component |
|
|
162
|
+
| `milliseconds` | `number` | Decomposed milliseconds component |
|
|
163
|
+
| `start` | `() => void` | Start the timer |
|
|
164
|
+
| `pause` | `() => void` | Pause the timer |
|
|
165
|
+
| `stop` | `() => void` | Stop timer (keep current time) |
|
|
166
|
+
| `reset` | `() => void` | Reset to initial time |
|
|
167
|
+
| `restart` | `() => void` | Reset and immediately start |
|
|
168
|
+
| `toggle` | `() => void` | Toggle between start/pause |
|
|
169
|
+
| `addTime` | `(ms: number) => void` | Add time to the timer |
|
|
170
|
+
| `subtractTime` | `(ms: number) => void` | Subtract time from the timer |
|
|
171
|
+
| `setTime` | `(ms: number) => void` | Set timer to a specific value |
|
|
172
|
+
|
|
173
|
+
### `ms` Helper Object
|
|
174
|
+
|
|
175
|
+
A utility object for converting time units to milliseconds.
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
import { ms } from "@usefy/use-timer";
|
|
179
|
+
|
|
180
|
+
ms.seconds(30); // 30000
|
|
181
|
+
ms.minutes(5); // 300000
|
|
182
|
+
ms.hours(1); // 3600000
|
|
183
|
+
|
|
184
|
+
// Combine them
|
|
185
|
+
ms.hours(1) + ms.minutes(30); // 1h 30m = 5400000
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Examples
|
|
191
|
+
|
|
192
|
+
### Basic Countdown Timer
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
import { useTimer, ms } from "@usefy/use-timer";
|
|
196
|
+
|
|
197
|
+
function CountdownTimer() {
|
|
198
|
+
const timer = useTimer(ms.minutes(5), {
|
|
199
|
+
format: "MM:SS",
|
|
200
|
+
onComplete: () => alert("Time's up!"),
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<div>
|
|
205
|
+
<div className="time-display">{timer.formattedTime}</div>
|
|
206
|
+
<div className="controls">
|
|
207
|
+
<button onClick={timer.toggle}>
|
|
208
|
+
{timer.isRunning ? "⏸ Pause" : "▶ Start"}
|
|
209
|
+
</button>
|
|
210
|
+
<button onClick={timer.reset}>↺ Reset</button>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Precision Timer with Milliseconds
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
import { useTimer, ms } from "@usefy/use-timer";
|
|
221
|
+
|
|
222
|
+
function PrecisionTimer() {
|
|
223
|
+
const timer = useTimer(ms.seconds(10), {
|
|
224
|
+
format: "mm:ss.SSS",
|
|
225
|
+
interval: 1, // Update every 1ms
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
<div>
|
|
230
|
+
<div className="precision-display">{timer.formattedTime}</div>
|
|
231
|
+
<button onClick={timer.toggle}>
|
|
232
|
+
{timer.isRunning ? "Pause" : "Start"}
|
|
233
|
+
</button>
|
|
234
|
+
</div>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Auto-Start Timer
|
|
240
|
+
|
|
241
|
+
```tsx
|
|
242
|
+
import { useTimer, ms } from "@usefy/use-timer";
|
|
243
|
+
|
|
244
|
+
function AutoStartTimer() {
|
|
245
|
+
const timer = useTimer(ms.seconds(30), {
|
|
246
|
+
autoStart: true,
|
|
247
|
+
format: "MM:SS",
|
|
248
|
+
onComplete: () => console.log("Completed!"),
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
return <div>{timer.formattedTime}</div>;
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Looping Timer
|
|
256
|
+
|
|
257
|
+
```tsx
|
|
258
|
+
import { useTimer, ms } from "@usefy/use-timer";
|
|
259
|
+
|
|
260
|
+
function LoopingTimer() {
|
|
261
|
+
const timer = useTimer(ms.seconds(10), {
|
|
262
|
+
loop: true,
|
|
263
|
+
format: "MM:SS",
|
|
264
|
+
onComplete: () => console.log("Loop completed!"),
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<div>
|
|
269
|
+
<p>{timer.formattedTime}</p>
|
|
270
|
+
<button onClick={timer.toggle}>
|
|
271
|
+
{timer.isRunning ? "Pause" : "Start"}
|
|
272
|
+
</button>
|
|
273
|
+
</div>
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Time Manipulation
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
import { useTimer, ms } from "@usefy/use-timer";
|
|
282
|
+
|
|
283
|
+
function ManipulableTimer() {
|
|
284
|
+
const timer = useTimer(ms.minutes(1), { format: "MM:SS" });
|
|
285
|
+
|
|
286
|
+
return (
|
|
287
|
+
<div>
|
|
288
|
+
<p>{timer.formattedTime}</p>
|
|
289
|
+
<div className="controls">
|
|
290
|
+
<button onClick={timer.toggle}>
|
|
291
|
+
{timer.isRunning ? "Pause" : "Start"}
|
|
292
|
+
</button>
|
|
293
|
+
<button onClick={() => timer.addTime(ms.seconds(10))}>+10s</button>
|
|
294
|
+
<button onClick={() => timer.subtractTime(ms.seconds(10))}>-10s</button>
|
|
295
|
+
<button onClick={timer.reset}>Reset</button>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Progress Bar
|
|
303
|
+
|
|
304
|
+
```tsx
|
|
305
|
+
import { useTimer, ms } from "@usefy/use-timer";
|
|
306
|
+
|
|
307
|
+
function TimerWithProgress() {
|
|
308
|
+
const timer = useTimer(ms.minutes(1), { format: "MM:SS" });
|
|
309
|
+
|
|
310
|
+
return (
|
|
311
|
+
<div>
|
|
312
|
+
<p>{timer.formattedTime}</p>
|
|
313
|
+
<div className="progress-bar">
|
|
314
|
+
<div
|
|
315
|
+
className="progress-fill"
|
|
316
|
+
style={{ width: `${100 - timer.progress}%` }}
|
|
317
|
+
/>
|
|
318
|
+
</div>
|
|
319
|
+
<button onClick={timer.toggle}>
|
|
320
|
+
{timer.isRunning ? "Pause" : "Start"}
|
|
321
|
+
</button>
|
|
322
|
+
</div>
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Custom Formatter
|
|
328
|
+
|
|
329
|
+
```tsx
|
|
330
|
+
import { useTimer, ms } from "@usefy/use-timer";
|
|
331
|
+
|
|
332
|
+
function CustomFormattedTimer() {
|
|
333
|
+
const timer = useTimer(ms.hours(2) + ms.minutes(30), {
|
|
334
|
+
format: (ms) => {
|
|
335
|
+
const hours = Math.floor(ms / 3600000);
|
|
336
|
+
const minutes = Math.floor((ms % 3600000) / 60000);
|
|
337
|
+
const seconds = Math.floor((ms % 60000) / 1000);
|
|
338
|
+
return `${hours}h ${minutes}m ${seconds}s`;
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
return (
|
|
343
|
+
<div>
|
|
344
|
+
<p>{timer.formattedTime}</p> {/* "2h 30m 0s" */}
|
|
345
|
+
<button onClick={timer.toggle}>
|
|
346
|
+
{timer.isRunning ? "Pause" : "Start"}
|
|
347
|
+
</button>
|
|
348
|
+
</div>
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Kitchen Timer with Presets
|
|
354
|
+
|
|
355
|
+
```tsx
|
|
356
|
+
import { useTimer, ms } from "@usefy/use-timer";
|
|
357
|
+
|
|
358
|
+
function KitchenTimer() {
|
|
359
|
+
const timer = useTimer(ms.minutes(5), {
|
|
360
|
+
format: "MM:SS",
|
|
361
|
+
onComplete: () => playAlarm(),
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
const presets = [1, 3, 5, 10, 15, 30];
|
|
365
|
+
|
|
366
|
+
return (
|
|
367
|
+
<div>
|
|
368
|
+
<div className="presets">
|
|
369
|
+
{presets.map((min) => (
|
|
370
|
+
<button key={min} onClick={() => timer.setTime(ms.minutes(min))}>
|
|
371
|
+
{min}m
|
|
372
|
+
</button>
|
|
373
|
+
))}
|
|
374
|
+
</div>
|
|
375
|
+
<p>{timer.formattedTime}</p>
|
|
376
|
+
<button onClick={timer.toggle}>
|
|
377
|
+
{timer.isRunning ? "Pause" : "Start"}
|
|
378
|
+
</button>
|
|
379
|
+
</div>
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## Render Optimization
|
|
387
|
+
|
|
388
|
+
The hook is optimized to **only trigger re-renders when the formatted time changes**, not on every interval tick. This means:
|
|
389
|
+
|
|
390
|
+
| Format | Re-render frequency |
|
|
391
|
+
| ----------- | ------------------- |
|
|
392
|
+
| `HH:MM` | Every minute |
|
|
393
|
+
| `MM:SS` | Every second |
|
|
394
|
+
| `MM:SS.S` | Every 100ms |
|
|
395
|
+
| `MM:SS.SS` | Every 10ms |
|
|
396
|
+
| `MM:SS.SSS` | Every 1ms |
|
|
397
|
+
|
|
398
|
+
This optimization is automatic — no configuration needed!
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
## TypeScript
|
|
403
|
+
|
|
404
|
+
This hook is written in TypeScript and exports comprehensive type definitions.
|
|
405
|
+
|
|
406
|
+
```tsx
|
|
407
|
+
import {
|
|
408
|
+
useTimer,
|
|
409
|
+
ms,
|
|
410
|
+
type UseTimerOptions,
|
|
411
|
+
type UseTimerReturn,
|
|
412
|
+
type TimerStatus,
|
|
413
|
+
type TimeFormat,
|
|
414
|
+
type TimeFormatter,
|
|
415
|
+
} from "@usefy/use-timer";
|
|
416
|
+
|
|
417
|
+
// Full type inference
|
|
418
|
+
const timer: UseTimerReturn = useTimer(60000, {
|
|
419
|
+
format: "MM:SS",
|
|
420
|
+
onComplete: () => console.log("Done!"),
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// Status is typed as union
|
|
424
|
+
const status: TimerStatus = timer.status; // "idle" | "running" | "paused" | "finished"
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
## Performance
|
|
430
|
+
|
|
431
|
+
- **Stable Function References** — All control functions are memoized with `useCallback`
|
|
432
|
+
- **Smart Re-renders** — Only re-renders when formatted time changes
|
|
433
|
+
- **Drift Compensation** — Uses `performance.now()` for accurate timing
|
|
434
|
+
- **Background Tab Handling** — Visibility API integration prevents timer drift when tab is inactive
|
|
435
|
+
|
|
436
|
+
```tsx
|
|
437
|
+
const { start, pause, toggle, reset } = useTimer(60000);
|
|
438
|
+
|
|
439
|
+
// These references remain stable across renders
|
|
440
|
+
useEffect(() => {
|
|
441
|
+
// Safe to use as dependencies
|
|
442
|
+
}, [start, pause, toggle, reset]);
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## Testing
|
|
448
|
+
|
|
449
|
+
This package maintains comprehensive test coverage to ensure reliability and stability.
|
|
450
|
+
|
|
451
|
+
### Test Coverage
|
|
452
|
+
|
|
453
|
+
| File | Statements | Branches | Functions | Lines |
|
|
454
|
+
| ---------------- | ---------- | -------- | --------- | ------ |
|
|
455
|
+
| `useTimer.ts` | 84.11% | 72.63% | 94.11% | 84.36% |
|
|
456
|
+
| `formatUtils.ts` | 100% | 100% | 100% | 100% |
|
|
457
|
+
| `timeUtils.ts` | 100% | 100% | 100% | 100% |
|
|
458
|
+
|
|
459
|
+
### Test Files
|
|
460
|
+
|
|
461
|
+
- `useTimer.test.ts` — 67 tests for hook behavior
|
|
462
|
+
- `formatUtils.test.ts` — 24 tests for time formatting
|
|
463
|
+
- `timeUtils.test.ts` — 32 tests for time utilities
|
|
464
|
+
|
|
465
|
+
**Total: 123 tests**
|
|
466
|
+
|
|
467
|
+
### Running Tests
|
|
468
|
+
|
|
469
|
+
```bash
|
|
470
|
+
# Run all tests
|
|
471
|
+
pnpm test
|
|
472
|
+
|
|
473
|
+
# Run tests in watch mode
|
|
474
|
+
pnpm test:watch
|
|
475
|
+
|
|
476
|
+
# Run tests with coverage report
|
|
477
|
+
pnpm test --coverage
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## License
|
|
483
|
+
|
|
484
|
+
MIT © [mirunamu](https://github.com/geon0529)
|
|
485
|
+
|
|
486
|
+
This package is part of the [usefy](https://github.com/geon0529/usefy) monorepo.
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
<p align="center">
|
|
491
|
+
<sub>Built with care by the usefy team</sub>
|
|
492
|
+
</p>
|