-
Notifications
You must be signed in to change notification settings - Fork 45
Expand file tree
/
Copy pathtest.ts
More file actions
230 lines (209 loc) · 5.77 KB
/
test.ts
File metadata and controls
230 lines (209 loc) · 5.77 KB
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
import { MemoryStore } from '@logux/core'
import { delay } from 'nanodelay'
import { atom } from 'nanostores'
import type { Credentials } from './auth.ts'
import type { EnvironmentAndStore } from './environment.ts'
import { type RequestMethod, setRequestMethod } from './request.ts'
import { type BaseRoute, stringifyPopups } from './router.ts'
export let testSession: string | undefined
let testRouter = atom<BaseRoute | undefined>()
let warningTracking: undefined | unknown[]
export function setWarningTracking(tracking: undefined | unknown[]): void {
warningTracking = tracking
}
/**
* Ensures a route has a hash property, adding an empty string if missing.
*
* Syntax sugar to avoid setting `hash` in every route in tests.
*/
export function addHashToBaseRoute(
route: BaseRoute | Omit<BaseRoute, 'hash'> | undefined
): BaseRoute | undefined {
if (!route) return undefined
return { hash: '', ...route } as BaseRoute
}
export function setBaseTestRoute(
route: BaseRoute | Omit<BaseRoute, 'hash'> | undefined
): void {
testRouter.set(addHashToBaseRoute(route))
}
export function getTestEnvironment(): EnvironmentAndStore {
testSession = undefined
let persistentStore: Record<string, string> = {}
return {
baseRouter: testRouter,
cleanStorage() {
for (let key in persistentStore) {
delete persistentStore[key]
}
},
errorEvents: { addEventListener() {} },
getSession() {
return testSession
},
locale: atom('en'),
logStoreCreator() {
return new MemoryStore()
},
networkType() {
return { saveData: undefined, type: undefined }
},
openRoute(route) {
setBaseTestRoute({ ...route, hash: stringifyPopups(route.popups) })
},
persistentEvents: { addEventListener() {}, removeEventListener() {} },
persistentStore,
restartApp() {},
saveFile() {},
savePassword() {
return Promise.resolve()
},
saveSession(session) {
testSession = session
},
server: 'localhost:2554',
translationLoader() {
return Promise.resolve({})
},
updateClient() {},
warn(e) {
if (warningTracking) {
warningTracking.push(e)
} else {
throw e
}
}
}
}
/**
* Useful for visual tests and cases where need reproducible result.
*/
export function testCredentials(): Credentials {
return {
encryptionKey: '5>@v9xbKP!',
password: '&5$K?EJuJ=',
userId: '2750177048377147'
}
}
export interface RequestWaiter {
(status: number, body?: null | string, contentType?: string): Promise<void>
aborted?: true
}
export interface RequestMock {
andFail(): void
andRespond(status: number, body?: null | string, contentType?: string): void
andWait(): RequestWaiter
}
interface RequestExpect {
contentType: string
error: boolean
response: null | string
status: number
url: string
wait: Promise<void>
waiter: RequestWaiter | undefined
}
let requestExpects: RequestExpect[] = []
export class MockRequestError extends Error {
constructor(message: string) {
super(message)
this.name = 'MockRequestError'
Error.captureStackTrace(this, MockRequestError)
}
}
let fetchMock: RequestMethod = async (url, opts = {}) => {
let expect = requestExpects.shift()
if (!expect) {
throw new MockRequestError(
`Unexpected request ${url} ${JSON.stringify(opts)}`
)
} else if (expect.url !== url) {
throw new MockRequestError(
`Expected request ${expect.url} instead of ${url}`
)
} else if (expect.error) {
await delay(10)
throw new Error('Network Error')
} else {
let { promise, reject } = Promise.withResolvers()
function abortCallback(): void {
if (expect?.waiter) expect.waiter.aborted = true
reject(new DOMException('', 'AbortError'))
}
opts.signal?.addEventListener('abort', abortCallback)
await Promise.race([expect.wait, promise])
opts.signal?.removeEventListener('abort', abortCallback)
let response = new Response(expect.response, {
headers: { 'Content-Type': expect.contentType },
status: expect.status
})
Object.defineProperty(response, 'url', { value: url })
return response
}
}
/**
* Enable request mocking for tests to be used in `beforeEach()`.
*
* Use `checkAndRemoveRequestMock()` in `afterEach()`.
*/
export function mockRequest(): void {
requestExpects = []
setRequestMethod(fetchMock)
}
/**
* Mark that we are waiting HTTP request and define HTTP response
*/
export function expectRequest(url: string): RequestMock {
let expect: RequestExpect = {
contentType: 'text/html',
error: false,
response: '',
status: 200,
url,
wait: Promise.resolve(),
waiter: undefined
}
requestExpects.push(expect)
return {
/**
* Generate network error on request
*/
andFail() {
expect.error = true
},
/**
* Setup simple immediately response
*/
andRespond(status, body = '', contentType = 'text/html') {
expect.contentType = contentType
expect.status = status
expect.response = body
},
/**
* Returns a function that allows defining response later to test latency
*/
andWait() {
let { promise, resolve } = Promise.withResolvers<void>()
expect.wait = promise
expect.waiter = (status, body = '', contentType = 'text/html') => {
expect.contentType = contentType
expect.status = status
expect.response = body
resolve()
return delay(10)
}
return expect.waiter
}
}
}
/**
* Fail test if there is expected request which was not sent during test
*/
export function checkAndRemoveRequestMock(): void {
if (requestExpects.length > 0) {
throw new Error(
'Test didn’t send requests: ' + requestExpects.map(i => i.url).join(', ')
)
}
setRequestMethod(fetch)
}