-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathindex.ts
121 lines (103 loc) · 3.02 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import { watch, WatchOptions, WatchSource } from '@vue-reactivity/watch'
interface WhenToMatchOptions {
flush?: WatchOptions['flush']
timeout?: number
throwOnTimeout?: boolean
}
export function promiseTimeout(ms: number, throwOnTimeout = false): Promise<void> {
return new Promise((resolve, reject) => {
if (throwOnTimeout)
setTimeout(reject, ms)
else
setTimeout(resolve, ms)
})
}
export function invoke<T>(fn: () => T): T {
return fn()
}
export interface WhenInstance<T> {
readonly not: WhenInstance<T>
toMatch(condition: (v: T | object) => boolean, options?: WhenToMatchOptions): Promise<void>
toBe<P>(value: P | T, options?: WhenToMatchOptions): Promise<void>
toBeTruthy(options?: WhenToMatchOptions): Promise<void>
toBeNull(options?: WhenToMatchOptions): Promise<void>
toBeUndefined(options?: WhenToMatchOptions): Promise<void>
toBeNaN(options?: WhenToMatchOptions): Promise<void>
toContain<P>(value: P, options?: WhenToMatchOptions): Promise<void>
changed(options?: WhenToMatchOptions): Promise<void>
changedTimes(n?: number, options?: WhenToMatchOptions): Promise<void>
}
export function when<T>(r: WatchSource<T> | object): WhenInstance<T> {
let isNot = false
function toMatch(
condition: (v: T | object) => boolean,
{ flush = 'sync', timeout, throwOnTimeout }: WhenToMatchOptions = {},
): Promise<void> {
let stop: Function | null = null
const watcher = new Promise<void>((resolve) => {
stop = watch(r, (v) => {
if (condition(v) === !isNot) {
stop?.()
resolve()
}
}, {
flush,
immediate: true,
})
})
const promises = [watcher]
if (timeout) {
promises.push(
promiseTimeout(timeout, throwOnTimeout)
.finally(() => { stop?.() }),
)
}
return Promise.race(promises)
}
function toBe<P>(value: P | T, options?: WhenToMatchOptions) {
return toMatch(v => v === value, options)
}
function toBeTruthy(options?: WhenToMatchOptions) {
return toMatch(v => Boolean(v), options)
}
function toBeNull(options?: WhenToMatchOptions) {
return toBe<null>(null, options)
}
function toBeUndefined(options?: WhenToMatchOptions) {
return toBe<undefined>(undefined, options)
}
function toBeNaN(options?: WhenToMatchOptions) {
return toMatch(Number.isNaN, options)
}
function toContain<P>(value: P, options?: WhenToMatchOptions) {
return toMatch((v) => {
const array = Array.from(v as any)
return array.includes(value)
}, options)
}
function changed(options?: WhenToMatchOptions) {
return changedTimes(1, options)
}
function changedTimes(n = 1, options?: WhenToMatchOptions) {
let count = -1 // skip the immediate check
return toMatch(() => {
count += 1
return count >= n
}, options)
}
return {
toMatch,
toBe,
toBeTruthy,
toBeNull,
toBeNaN,
toBeUndefined,
toContain,
changed,
changedTimes,
get not() {
isNot = !isNot
return this
},
}
}