mockaton 10.7.0 → 11.0.0
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/Makefile +33 -0
- package/README.md +25 -27
- package/package.json +2 -7
- package/src/Api.js +66 -47
- package/src/ApiCommander.js +12 -29
- package/src/ApiConstants.js +2 -9
- package/src/Dashboard.css +1 -1
- package/src/Dashboard.js +83 -78
- package/src/DashboardDom.js +8 -12
- package/src/DashboardStore.js +11 -17
- package/src/MockDispatcher.js +0 -1
- package/src/Watcher.js +17 -22
- package/src/config.js +1 -1
- package/src/mockBrokersCollection.js +4 -4
- package/src/utils/mime.js +3 -3
package/Makefile
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
start:
|
|
2
|
+
@node src/cli.js
|
|
3
|
+
|
|
4
|
+
watch:
|
|
5
|
+
@node --watch src/cli.js
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
test:
|
|
9
|
+
node --test 'src/**/*.test.js'
|
|
10
|
+
|
|
11
|
+
test-docker:
|
|
12
|
+
docker run --rm -it \
|
|
13
|
+
-v "$$PWD":/app \
|
|
14
|
+
-w /app \
|
|
15
|
+
node:24 \
|
|
16
|
+
make test
|
|
17
|
+
|
|
18
|
+
coverage:
|
|
19
|
+
node --test --experimental-test-coverage \
|
|
20
|
+
--test-reporter=spec --test-reporter-destination=stdout \
|
|
21
|
+
--test-reporter=lcov --test-reporter-destination=lcov.info \
|
|
22
|
+
'src/**/*.test.js'
|
|
23
|
+
|
|
24
|
+
pixaton:
|
|
25
|
+
node --test --experimental-test-isolation=none \
|
|
26
|
+
--import=./pixaton-tests/_setup.js \
|
|
27
|
+
'pixaton-tests/**/*.test.js'
|
|
28
|
+
|
|
29
|
+
outdated:
|
|
30
|
+
@npm outdated --parseable |\
|
|
31
|
+
awk -F: '{ printf "npm i %-30s ;# %s\n", $$4, $$2 }'
|
|
32
|
+
|
|
33
|
+
.PHONY: *
|
package/README.md
CHANGED
|
@@ -10,33 +10,6 @@
|
|
|
10
10
|
An HTTP mock server for simulating APIs with minimal setup
|
|
11
11
|
— ideal for testing difficult to reproduce states.
|
|
12
12
|
|
|
13
|
-
<br/>
|
|
14
|
-
|
|
15
|
-
## Motivation
|
|
16
|
-
|
|
17
|
-
**No API state should be too hard to test.**
|
|
18
|
-
With Mockaton, developers can achieve correctness and speed.
|
|
19
|
-
|
|
20
|
-
### Correctness
|
|
21
|
-
- Enables testing of complex scenarios that would otherwise be skipped. e.g.,
|
|
22
|
-
- Triggering errors on third-party APIs.
|
|
23
|
-
- Triggering errors on your project’s backend (if you are a frontend developer).
|
|
24
|
-
- Allows for deterministic, comprehensive, and consistent state.
|
|
25
|
-
- Spot inadvertent regressions during development.
|
|
26
|
-
- Use it to set up screenshot tests, e.g., with [pixaton](https://github.com/ericfortis/pixaton).
|
|
27
|
-
|
|
28
|
-
### Speed
|
|
29
|
-
- Works around unstable dev backends while developing UIs.
|
|
30
|
-
- Spinning up development infrastructure.
|
|
31
|
-
- Syncing database states.
|
|
32
|
-
- Prevents progress from being blocked by waiting for APIs.
|
|
33
|
-
- Time travel. If you commit the mocks to your repo,
|
|
34
|
-
you don’t have to downgrade backends for:
|
|
35
|
-
- checking out long-lived branches
|
|
36
|
-
- bisecting bugs
|
|
37
|
-
|
|
38
|
-
<br/>
|
|
39
|
-
|
|
40
13
|
## Overview
|
|
41
14
|
With Mockaton, you don’t need to write code for wiring up your
|
|
42
15
|
mocks. Instead, a given directory is scanned for filenames
|
|
@@ -118,6 +91,31 @@ checking ✅ **Save Mocks**, you can collect the responses that hit your backend
|
|
|
118
91
|
They will be saved in your `config.mocksDir` following the filename convention.
|
|
119
92
|
</details>
|
|
120
93
|
|
|
94
|
+
<br/>
|
|
95
|
+
|
|
96
|
+
## Motivation
|
|
97
|
+
|
|
98
|
+
**No API state should be too hard to test.**
|
|
99
|
+
With Mockaton, developers can achieve correctness and speed.
|
|
100
|
+
|
|
101
|
+
### Correctness
|
|
102
|
+
- Enables testing of complex scenarios that would otherwise be skipped. e.g.,
|
|
103
|
+
- Triggering errors on third-party APIs.
|
|
104
|
+
- Triggering errors on your project’s backend (if you are a frontend developer).
|
|
105
|
+
- Allows for deterministic, comprehensive, and consistent state.
|
|
106
|
+
- Spot inadvertent regressions during development.
|
|
107
|
+
- Use it to set up screenshot tests, e.g., with [pixaton](https://github.com/ericfortis/pixaton).
|
|
108
|
+
|
|
109
|
+
### Speed
|
|
110
|
+
- Works around unstable dev backends while developing UIs.
|
|
111
|
+
- Spinning up development infrastructure.
|
|
112
|
+
- Syncing database states.
|
|
113
|
+
- Prevents progress from being blocked by waiting for APIs.
|
|
114
|
+
- Time travel. If you commit the mocks to your repo,
|
|
115
|
+
you don’t have to downgrade backends for:
|
|
116
|
+
- checking out long-lived branches
|
|
117
|
+
- bisecting bugs
|
|
118
|
+
|
|
121
119
|
|
|
122
120
|
<br/>
|
|
123
121
|
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "mockaton",
|
|
3
3
|
"description": "HTTP Mock Server",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "11.0.0",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"types": "index.d.ts",
|
|
8
8
|
"license": "MIT",
|
|
@@ -18,12 +18,7 @@
|
|
|
18
18
|
"mockaton": "src/cli.js"
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
|
-
"
|
|
22
|
-
"coverage": "node --test --test-reporter=lcov --test-reporter-destination=lcov.info --experimental-test-coverage 'src/**/*.test.js'",
|
|
23
|
-
"start": "node src/cli.js",
|
|
24
|
-
"watch": "node --watch src/cli.js",
|
|
25
|
-
"pixaton": "node --test --import=./pixaton-tests/_setup.js --experimental-test-isolation=none 'pixaton-tests/**/*.test.js'",
|
|
26
|
-
"outdated": "npm outdated --parseable | awk -F: '{ printf \"npm i %-30s ;# %s\\n\", $4, $2 }'"
|
|
21
|
+
"start": "make start"
|
|
27
22
|
},
|
|
28
23
|
"devDependencies": {
|
|
29
24
|
"pixaton": "1.1.3",
|
package/src/Api.js
CHANGED
|
@@ -12,17 +12,21 @@ import * as staticCollection from './staticCollection.js'
|
|
|
12
12
|
import * as mockBrokersCollection from './mockBrokersCollection.js'
|
|
13
13
|
import { config, ConfigValidator } from './config.js'
|
|
14
14
|
import { DashboardHtml, CSP } from './DashboardHtml.js'
|
|
15
|
-
import { DF, API, LONG_POLL_SERVER_TIMEOUT } from './ApiConstants.js'
|
|
16
15
|
import { sendOK, sendJSON, sendUnprocessableContent, sendFile, sendHTML } from './utils/http-response.js'
|
|
16
|
+
import { API, LONG_POLL_SERVER_TIMEOUT, HEADER_FOR_SYNC_VERSION } from './ApiConstants.js'
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
export const apiGetRequests = new Map([
|
|
20
20
|
[API.dashboard, serveDashboard],
|
|
21
21
|
...[
|
|
22
|
+
'Logo.svg',
|
|
22
23
|
'Dashboard.css',
|
|
24
|
+
'ApiCommander.js',
|
|
25
|
+
'ApiConstants.js',
|
|
23
26
|
'Dashboard.js',
|
|
24
|
-
'
|
|
25
|
-
'
|
|
27
|
+
'DashboardDom.js',
|
|
28
|
+
'DashboardStore.js',
|
|
29
|
+
'Filename.js'
|
|
26
30
|
].map(f => [API.dashboard + '/' + f, serveStatic(f)]),
|
|
27
31
|
|
|
28
32
|
[API.state, getState],
|
|
@@ -57,6 +61,7 @@ function serveStatic(f) {
|
|
|
57
61
|
return (_, response) => sendFile(response, join(import.meta.dirname, f))
|
|
58
62
|
}
|
|
59
63
|
|
|
64
|
+
|
|
60
65
|
function getState(_, response) {
|
|
61
66
|
sendJSON(response, {
|
|
62
67
|
cookies: cookie.list(),
|
|
@@ -74,12 +79,14 @@ function getState(_, response) {
|
|
|
74
79
|
})
|
|
75
80
|
}
|
|
76
81
|
|
|
82
|
+
|
|
77
83
|
function longPollClientSyncVersion(req, response) {
|
|
78
|
-
if (uiSyncVersion.version !== Number(req.headers[
|
|
84
|
+
if (uiSyncVersion.version !== Number(req.headers[HEADER_FOR_SYNC_VERSION])) {
|
|
79
85
|
// e.g., tab was hidden while new mocks were added or removed
|
|
80
86
|
sendJSON(response, uiSyncVersion.version)
|
|
81
87
|
return
|
|
82
88
|
}
|
|
89
|
+
|
|
83
90
|
function onAddOrRemoveMock() {
|
|
84
91
|
uiSyncVersion.unsubscribe(onAddOrRemoveMock)
|
|
85
92
|
sendJSON(response, uiSyncVersion.version)
|
|
@@ -102,16 +109,21 @@ function reinitialize(_, response) {
|
|
|
102
109
|
sendOK(response)
|
|
103
110
|
}
|
|
104
111
|
|
|
112
|
+
|
|
105
113
|
async function selectCookie(req, response) {
|
|
106
|
-
const
|
|
114
|
+
const label = await parseJSON(req)
|
|
115
|
+
|
|
116
|
+
const error = cookie.setCurrent(label)
|
|
107
117
|
if (error)
|
|
108
118
|
sendUnprocessableContent(response, error?.message || error)
|
|
109
119
|
else
|
|
110
120
|
sendJSON(response, cookie.list())
|
|
111
121
|
}
|
|
112
122
|
|
|
123
|
+
|
|
113
124
|
async function selectMock(req, response) {
|
|
114
125
|
const file = await parseJSON(req)
|
|
126
|
+
|
|
115
127
|
const broker = mockBrokersCollection.brokerByFilename(file)
|
|
116
128
|
if (!broker || !broker.hasMock(file))
|
|
117
129
|
sendUnprocessableContent(response, `Missing Mock: ${file}`)
|
|
@@ -121,28 +133,26 @@ async function selectMock(req, response) {
|
|
|
121
133
|
}
|
|
122
134
|
}
|
|
123
135
|
|
|
136
|
+
|
|
124
137
|
async function toggle500(req, response) {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
body[DF.routeUrlMask])
|
|
138
|
+
const [method, urlMask] = await parseJSON(req)
|
|
139
|
+
|
|
140
|
+
const broker = mockBrokersCollection.brokerByRoute(method, urlMask)
|
|
129
141
|
if (!broker)
|
|
130
|
-
sendUnprocessableContent(response, `Route does not exist: ${
|
|
142
|
+
sendUnprocessableContent(response, `Route does not exist: ${method} ${urlMask}`)
|
|
131
143
|
else {
|
|
132
144
|
broker.toggle500()
|
|
133
145
|
sendJSON(response, broker)
|
|
134
146
|
}
|
|
135
147
|
}
|
|
136
148
|
|
|
149
|
+
|
|
137
150
|
async function setRouteIsDelayed(req, response) {
|
|
138
|
-
const
|
|
139
|
-
const delayed = body[DF.delayed]
|
|
140
|
-
const broker = mockBrokersCollection.brokerByRoute(
|
|
141
|
-
body[DF.routeMethod],
|
|
142
|
-
body[DF.routeUrlMask])
|
|
151
|
+
const [method, urlMask, delayed] = await parseJSON(req)
|
|
143
152
|
|
|
153
|
+
const broker = mockBrokersCollection.brokerByRoute(method, urlMask)
|
|
144
154
|
if (!broker)
|
|
145
|
-
sendUnprocessableContent(response, `Route does not exist: ${
|
|
155
|
+
sendUnprocessableContent(response, `Route does not exist: ${method} ${urlMask}`)
|
|
146
156
|
else if (typeof delayed !== 'boolean')
|
|
147
157
|
sendUnprocessableContent(response, `Expected boolean for "delayed"`)
|
|
148
158
|
else {
|
|
@@ -151,15 +161,13 @@ async function setRouteIsDelayed(req, response) {
|
|
|
151
161
|
}
|
|
152
162
|
}
|
|
153
163
|
|
|
164
|
+
|
|
154
165
|
async function setRouteIsProxied(req, response) {
|
|
155
|
-
const
|
|
156
|
-
const proxied = body[DF.proxied]
|
|
157
|
-
const broker = mockBrokersCollection.brokerByRoute(
|
|
158
|
-
body[DF.routeMethod],
|
|
159
|
-
body[DF.routeUrlMask])
|
|
166
|
+
const [method, urlMask, proxied] = await parseJSON(req)
|
|
160
167
|
|
|
168
|
+
const broker = mockBrokersCollection.brokerByRoute(method, urlMask)
|
|
161
169
|
if (!broker)
|
|
162
|
-
sendUnprocessableContent(response, `Route does not exist: ${
|
|
170
|
+
sendUnprocessableContent(response, `Route does not exist: ${method} ${urlMask}`)
|
|
163
171
|
else if (typeof proxied !== 'boolean')
|
|
164
172
|
sendUnprocessableContent(response, `Expected boolean for "proxied"`)
|
|
165
173
|
else if (proxied && !config.proxyFallback)
|
|
@@ -170,59 +178,70 @@ async function setRouteIsProxied(req, response) {
|
|
|
170
178
|
}
|
|
171
179
|
}
|
|
172
180
|
|
|
181
|
+
|
|
173
182
|
async function updateProxyFallback(req, response) {
|
|
174
183
|
const fallback = await parseJSON(req)
|
|
175
|
-
|
|
184
|
+
|
|
185
|
+
if (!ConfigValidator.proxyFallback(fallback))
|
|
176
186
|
sendUnprocessableContent(response, `Invalid Proxy Fallback URL`)
|
|
177
|
-
|
|
187
|
+
else {
|
|
188
|
+
config.proxyFallback = fallback
|
|
189
|
+
sendOK(response)
|
|
178
190
|
}
|
|
179
|
-
config.proxyFallback = fallback
|
|
180
|
-
sendOK(response)
|
|
181
191
|
}
|
|
182
192
|
|
|
193
|
+
|
|
183
194
|
async function setCollectProxied(req, response) {
|
|
184
195
|
const collectProxied = await parseJSON(req)
|
|
185
|
-
|
|
196
|
+
|
|
197
|
+
if (!ConfigValidator.collectProxied(collectProxied))
|
|
186
198
|
sendUnprocessableContent(response, `Expected a boolean for "collectProxied"`)
|
|
187
|
-
|
|
199
|
+
else {
|
|
200
|
+
config.collectProxied = collectProxied
|
|
201
|
+
sendOK(response)
|
|
188
202
|
}
|
|
189
|
-
config.collectProxied = collectProxied
|
|
190
|
-
sendOK(response)
|
|
191
203
|
}
|
|
192
204
|
|
|
205
|
+
|
|
193
206
|
async function bulkUpdateBrokersByCommentTag(req, response) {
|
|
194
|
-
|
|
207
|
+
const comment = await parseJSON(req)
|
|
208
|
+
|
|
209
|
+
mockBrokersCollection.setMocksMatchingComment(comment)
|
|
195
210
|
sendOK(response)
|
|
196
211
|
}
|
|
197
212
|
|
|
213
|
+
|
|
198
214
|
async function setCorsAllowed(req, response) {
|
|
199
215
|
const corsAllowed = await parseJSON(req)
|
|
200
|
-
|
|
216
|
+
|
|
217
|
+
if (!ConfigValidator.corsAllowed(corsAllowed))
|
|
201
218
|
sendUnprocessableContent(response, `Expected boolean for "corsAllowed"`)
|
|
202
|
-
|
|
219
|
+
else {
|
|
220
|
+
config.corsAllowed = corsAllowed
|
|
221
|
+
sendOK(response)
|
|
203
222
|
}
|
|
204
|
-
config.corsAllowed = corsAllowed
|
|
205
|
-
sendOK(response)
|
|
206
223
|
}
|
|
207
224
|
|
|
225
|
+
|
|
208
226
|
async function setGlobalDelay(req, response) {
|
|
209
227
|
const delay = await parseJSON(req)
|
|
210
|
-
|
|
228
|
+
|
|
229
|
+
if (!ConfigValidator.delay(delay))
|
|
211
230
|
sendUnprocessableContent(response, `Expected non-negative integer for "delay"`)
|
|
212
|
-
|
|
231
|
+
else {
|
|
232
|
+
config.delay = delay
|
|
233
|
+
sendOK(response)
|
|
213
234
|
}
|
|
214
|
-
config.delay = delay
|
|
215
|
-
sendOK(response)
|
|
216
235
|
}
|
|
217
236
|
|
|
218
237
|
|
|
238
|
+
|
|
219
239
|
async function setStaticRouteStatusCode(req, response) {
|
|
220
|
-
const
|
|
221
|
-
const status = Number(body[DF.statusCode])
|
|
222
|
-
const broker = staticCollection.brokerByRoute(body[DF.routeUrlMask])
|
|
240
|
+
const [urlMask, status] = await parseJSON(req)
|
|
223
241
|
|
|
242
|
+
const broker = staticCollection.brokerByRoute(urlMask)
|
|
224
243
|
if (!broker)
|
|
225
|
-
sendUnprocessableContent(response, `Static route does not exist: ${
|
|
244
|
+
sendUnprocessableContent(response, `Static route does not exist: ${urlMask}`)
|
|
226
245
|
else if (!(status === 200 || status === 404))
|
|
227
246
|
sendUnprocessableContent(response, `Expected 200 or 404 status code`)
|
|
228
247
|
else {
|
|
@@ -231,13 +250,13 @@ async function setStaticRouteStatusCode(req, response) {
|
|
|
231
250
|
}
|
|
232
251
|
}
|
|
233
252
|
|
|
253
|
+
|
|
234
254
|
async function setStaticRouteIsDelayed(req, response) {
|
|
235
|
-
const
|
|
236
|
-
const delayed = body[DF.delayed]
|
|
237
|
-
const broker = staticCollection.brokerByRoute(body[DF.routeUrlMask])
|
|
255
|
+
const [urlMask, delayed] = await parseJSON(req)
|
|
238
256
|
|
|
257
|
+
const broker = staticCollection.brokerByRoute(urlMask)
|
|
239
258
|
if (!broker)
|
|
240
|
-
sendUnprocessableContent(response, `Static route does not exist: ${
|
|
259
|
+
sendUnprocessableContent(response, `Static route does not exist: ${urlMask}`)
|
|
241
260
|
else if (typeof delayed !== 'boolean')
|
|
242
261
|
sendUnprocessableContent(response, `Expected boolean for "delayed"`)
|
|
243
262
|
else {
|
package/src/ApiCommander.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { API,
|
|
1
|
+
import { API, LONG_POLL_SERVER_TIMEOUT, HEADER_FOR_SYNC_VERSION } from './ApiConstants.js'
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
/** Client for controlling Mockaton via its HTTP API */
|
|
@@ -27,7 +27,7 @@ export class Commander {
|
|
|
27
27
|
AbortSignal.timeout(LONG_POLL_SERVER_TIMEOUT + 1000)
|
|
28
28
|
]),
|
|
29
29
|
headers: {
|
|
30
|
-
[
|
|
30
|
+
[HEADER_FOR_SYNC_VERSION]: currSyncVer
|
|
31
31
|
}
|
|
32
32
|
})
|
|
33
33
|
|
|
@@ -40,45 +40,28 @@ export class Commander {
|
|
|
40
40
|
return this.#patch(API.select, file)
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
toggle500(
|
|
44
|
-
return this.#patch(API.toggle500,
|
|
45
|
-
[DF.routeMethod]: routeMethod,
|
|
46
|
-
[DF.routeUrlMask]: routeUrlMask
|
|
47
|
-
})
|
|
43
|
+
toggle500(method, urlMask) {
|
|
44
|
+
return this.#patch(API.toggle500, [method, urlMask])
|
|
48
45
|
}
|
|
49
46
|
|
|
50
47
|
bulkSelectByComment(comment) {
|
|
51
48
|
return this.#patch(API.bulkSelect, comment)
|
|
52
49
|
}
|
|
53
50
|
|
|
54
|
-
setRouteIsDelayed(
|
|
55
|
-
return this.#patch(API.delay,
|
|
56
|
-
[DF.routeMethod]: routeMethod,
|
|
57
|
-
[DF.routeUrlMask]: routeUrlMask,
|
|
58
|
-
[DF.delayed]: delayed
|
|
59
|
-
})
|
|
51
|
+
setRouteIsDelayed(method, urlMask, delayed) {
|
|
52
|
+
return this.#patch(API.delay, [method, urlMask, delayed])
|
|
60
53
|
}
|
|
61
54
|
|
|
62
|
-
setStaticRouteIsDelayed(
|
|
63
|
-
return this.#patch(API.delayStatic,
|
|
64
|
-
[DF.routeUrlMask]: routeUrlMask,
|
|
65
|
-
[DF.delayed]: delayed
|
|
66
|
-
})
|
|
55
|
+
setStaticRouteIsDelayed(urlMask, delayed) {
|
|
56
|
+
return this.#patch(API.delayStatic, [urlMask, delayed])
|
|
67
57
|
}
|
|
68
58
|
|
|
69
|
-
setStaticRouteStatus(
|
|
70
|
-
return this.#patch(API.staticStatus,
|
|
71
|
-
[DF.routeUrlMask]: routeUrlMask,
|
|
72
|
-
[DF.statusCode]: status
|
|
73
|
-
})
|
|
59
|
+
setStaticRouteStatus(urlMask, status) {
|
|
60
|
+
return this.#patch(API.staticStatus, [urlMask, status])
|
|
74
61
|
}
|
|
75
62
|
|
|
76
|
-
setRouteIsProxied(
|
|
77
|
-
return this.#patch(API.proxied,
|
|
78
|
-
[DF.routeMethod]: routeMethod,
|
|
79
|
-
[DF.routeUrlMask]: routeUrlMask,
|
|
80
|
-
[DF.proxied]: proxied
|
|
81
|
-
})
|
|
63
|
+
setRouteIsProxied(method, urlMask, proxied) {
|
|
64
|
+
return this.#patch(API.proxied, [method, urlMask, proxied])
|
|
82
65
|
}
|
|
83
66
|
|
|
84
67
|
selectCookie(cookieKey) {
|
package/src/ApiConstants.js
CHANGED
|
@@ -19,16 +19,9 @@ export const API = {
|
|
|
19
19
|
toggle500: MOUNT + '/toggle500'
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export const DF = { // Dashboard Fields (XHR)
|
|
23
|
-
routeMethod: 'route_method',
|
|
24
|
-
routeUrlMask: 'route_url_mask',
|
|
25
|
-
delayed: 'delayed',
|
|
26
|
-
proxied: 'proxied',
|
|
27
|
-
statusCode: 'status_code',
|
|
28
|
-
syncVersion: 'last_received_sync_version'
|
|
29
|
-
}
|
|
30
|
-
|
|
31
22
|
export const HEADER_FOR_502 = 'Mockaton502'
|
|
23
|
+
export const HEADER_FOR_SYNC_VERSION = 'sync_version'
|
|
24
|
+
|
|
32
25
|
export const DEFAULT_MOCK_COMMENT = '(default)'
|
|
33
26
|
export const EXT_FOR_UNKNOWN_MIME = 'unknown'
|
|
34
27
|
export const LONG_POLL_SERVER_TIMEOUT = 8_000
|
package/src/Dashboard.css
CHANGED
package/src/Dashboard.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { createElement as r, createSvgElement as s, className, restoreFocus, Defer, Fragment
|
|
2
|
-
import { store, BrokerRowModel } from './DashboardStore.js'
|
|
1
|
+
import { createElement as r, createSvgElement as s, className, restoreFocus, Defer, Fragment } from './DashboardDom.js'
|
|
3
2
|
import { HEADER_FOR_502 } from './ApiConstants.js'
|
|
4
3
|
import { parseFilename } from './Filename.js'
|
|
4
|
+
import { store } from './DashboardStore.js'
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
const CSS = {
|
|
@@ -56,6 +56,8 @@ const FocusGroup = {
|
|
|
56
56
|
PreviewLink: 3
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
const t = translation => translation[0]
|
|
60
|
+
|
|
59
61
|
store.onError = onError
|
|
60
62
|
store.render = render
|
|
61
63
|
store.renderRow = renderRow
|
|
@@ -71,9 +73,8 @@ function render() {
|
|
|
71
73
|
}
|
|
72
74
|
render.count = 0
|
|
73
75
|
|
|
74
|
-
const t = translation => translation[0]
|
|
75
76
|
|
|
76
|
-
const leftSideRef =
|
|
77
|
+
const leftSideRef = {}
|
|
77
78
|
|
|
78
79
|
function App() {
|
|
79
80
|
return [
|
|
@@ -81,14 +82,14 @@ function App() {
|
|
|
81
82
|
r('main', null,
|
|
82
83
|
r('div', {
|
|
83
84
|
ref: leftSideRef,
|
|
84
|
-
style: { width:
|
|
85
|
+
style: { width: leftSideRef.width },
|
|
85
86
|
className: CSS.leftSide
|
|
86
87
|
},
|
|
87
88
|
r('table', null,
|
|
88
89
|
MockList(),
|
|
89
90
|
StaticFilesList())),
|
|
90
91
|
r('div', { className: CSS.rightSide },
|
|
91
|
-
Resizer(),
|
|
92
|
+
Resizer(leftSideRef),
|
|
92
93
|
PayloadViewer()))
|
|
93
94
|
]
|
|
94
95
|
}
|
|
@@ -159,8 +160,7 @@ function BulkSelector() {
|
|
|
159
160
|
},
|
|
160
161
|
r('option', { value: firstOption }, firstOption),
|
|
161
162
|
r('hr'),
|
|
162
|
-
comments.map(value => r('option', { value }, value))
|
|
163
|
-
)))
|
|
163
|
+
comments.map(value => r('option', { value }, value)))))
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
function CookieSelector() {
|
|
@@ -181,9 +181,9 @@ function CookieSelector() {
|
|
|
181
181
|
|
|
182
182
|
|
|
183
183
|
function ProxyFallbackField() {
|
|
184
|
-
const checkboxRef =
|
|
184
|
+
const checkboxRef = {}
|
|
185
185
|
function onChange() {
|
|
186
|
-
checkboxRef.
|
|
186
|
+
checkboxRef.elem.disabled = !this.validity.valid || !this.value.trim()
|
|
187
187
|
if (!this.validity.valid)
|
|
188
188
|
this.reportValidity()
|
|
189
189
|
else
|
|
@@ -239,7 +239,7 @@ function SettingsMenuTrigger() {
|
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
function SettingsMenu(id) {
|
|
242
|
-
const firstInputRef =
|
|
242
|
+
const firstInputRef = {}
|
|
243
243
|
return (
|
|
244
244
|
r('menu', {
|
|
245
245
|
id,
|
|
@@ -247,7 +247,7 @@ function SettingsMenu(id) {
|
|
|
247
247
|
className: CSS.SettingsMenu,
|
|
248
248
|
onToggle(event) {
|
|
249
249
|
if (event.newState === 'open')
|
|
250
|
-
firstInputRef.
|
|
250
|
+
firstInputRef.elem.focus()
|
|
251
251
|
}
|
|
252
252
|
},
|
|
253
253
|
|
|
@@ -307,8 +307,7 @@ function Row(row, i) {
|
|
|
307
307
|
method,
|
|
308
308
|
urlMask,
|
|
309
309
|
!row.proxied && row.status === 500, // checked
|
|
310
|
-
row.opts.length === 1 && row.status === 500 // disabled
|
|
311
|
-
)),
|
|
310
|
+
row.opts.length === 1 && row.status === 500)), // disabled
|
|
312
311
|
|
|
313
312
|
!store.groupByMethod && r('td', className(CSS.Method),
|
|
314
313
|
method),
|
|
@@ -329,10 +328,10 @@ function renderRow(method, urlMask) {
|
|
|
329
328
|
})
|
|
330
329
|
|
|
331
330
|
function trFor(key) {
|
|
332
|
-
return leftSideRef.
|
|
331
|
+
return leftSideRef.elem.querySelector(`tr[key="${key}"]`)
|
|
333
332
|
}
|
|
334
333
|
function unChooseOld() {
|
|
335
|
-
return leftSideRef.
|
|
334
|
+
return leftSideRef.elem.querySelector(`td > a.${CSS.chosen}`)
|
|
336
335
|
?.classList.remove(CSS.chosen)
|
|
337
336
|
}
|
|
338
337
|
}
|
|
@@ -522,14 +521,14 @@ function ClickDragToggler({ checked, commit, focusGroup }) {
|
|
|
522
521
|
TimerIcon()))
|
|
523
522
|
}
|
|
524
523
|
|
|
525
|
-
function Resizer() {
|
|
524
|
+
function Resizer(ref) {
|
|
526
525
|
let raf = 0
|
|
527
526
|
let initialX = 0
|
|
528
|
-
let
|
|
527
|
+
let initialWidth = 0
|
|
529
528
|
|
|
530
529
|
function onPointerDown(event) {
|
|
531
530
|
initialX = event.clientX
|
|
532
|
-
|
|
531
|
+
initialWidth = ref.elem.clientWidth
|
|
533
532
|
addEventListener('pointerup', onUp, { once: true })
|
|
534
533
|
addEventListener('pointermove', onMove)
|
|
535
534
|
Object.assign(document.body.style, {
|
|
@@ -542,8 +541,8 @@ function Resizer() {
|
|
|
542
541
|
function onMove(event) {
|
|
543
542
|
const MIN_LEFT_WIDTH = 380
|
|
544
543
|
raf = raf || requestAnimationFrame(() => {
|
|
545
|
-
|
|
546
|
-
|
|
544
|
+
ref.width = Math.max(initialWidth - (initialX - event.clientX), MIN_LEFT_WIDTH) + 'px'
|
|
545
|
+
ref.elem.style.width = ref.width
|
|
547
546
|
raf = 0
|
|
548
547
|
})
|
|
549
548
|
}
|
|
@@ -569,8 +568,8 @@ function Resizer() {
|
|
|
569
568
|
|
|
570
569
|
/** # Payload Preview */
|
|
571
570
|
|
|
572
|
-
const payloadViewerTitleRef =
|
|
573
|
-
const payloadViewerCodeRef =
|
|
571
|
+
const payloadViewerTitleRef = {}
|
|
572
|
+
const payloadViewerCodeRef = {}
|
|
574
573
|
|
|
575
574
|
function PayloadViewer() {
|
|
576
575
|
return (
|
|
@@ -582,7 +581,7 @@ function PayloadViewer() {
|
|
|
582
581
|
!store.hasChosenLink && t`Click a link to preview it`))))
|
|
583
582
|
}
|
|
584
583
|
|
|
585
|
-
function PayloadViewerTitle(
|
|
584
|
+
function PayloadViewerTitle(file, statusText) {
|
|
586
585
|
const { method, status, ext } = parseFilename(file)
|
|
587
586
|
const fileNameWithComments = file.split('.').slice(0, -3).join('.')
|
|
588
587
|
return (
|
|
@@ -592,13 +591,15 @@ function PayloadViewerTitle({ file, statusText }) {
|
|
|
592
591
|
'.' + ext))
|
|
593
592
|
}
|
|
594
593
|
|
|
595
|
-
function PayloadViewerTitleWhenProxied(
|
|
594
|
+
function PayloadViewerTitleWhenProxied(response) {
|
|
595
|
+
const mime = response.headers.get('content-type') || ''
|
|
596
|
+
const badGateway = response.headers.get(HEADER_FOR_502)
|
|
596
597
|
return (
|
|
597
598
|
r('span', null,
|
|
598
|
-
|
|
599
|
+
badGateway
|
|
599
600
|
? r('span', null, t`⛔ Fallback Backend Error` + ' ')
|
|
600
601
|
: r('span', null, t`Got` + ' '),
|
|
601
|
-
r('abbr', { title: statusText }, status),
|
|
602
|
+
r('abbr', { title: response.statusText }, response.status),
|
|
602
603
|
' ' + mime))
|
|
603
604
|
}
|
|
604
605
|
|
|
@@ -616,8 +617,8 @@ async function previewMock() {
|
|
|
616
617
|
previewMock.controller = new AbortController
|
|
617
618
|
|
|
618
619
|
const spinnerTimer = setTimeout(() => {
|
|
619
|
-
payloadViewerTitleRef.
|
|
620
|
-
payloadViewerCodeRef.
|
|
620
|
+
payloadViewerTitleRef.elem.replaceChildren(t`Fetching…`)
|
|
621
|
+
payloadViewerCodeRef.elem.replaceChildren(PayloadViewerProgressBar())
|
|
621
622
|
}, SPINNER_DELAY)
|
|
622
623
|
|
|
623
624
|
try {
|
|
@@ -631,37 +632,29 @@ async function previewMock() {
|
|
|
631
632
|
await updatePayloadViewer(proxied, file, response)
|
|
632
633
|
}
|
|
633
634
|
catch {
|
|
634
|
-
payloadViewerCodeRef.
|
|
635
|
+
payloadViewerCodeRef.elem.replaceChildren()
|
|
635
636
|
}
|
|
636
637
|
}
|
|
637
638
|
|
|
638
639
|
async function updatePayloadViewer(proxied, file, response) {
|
|
639
640
|
const mime = response.headers.get('content-type') || ''
|
|
640
641
|
|
|
641
|
-
payloadViewerTitleRef.
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
: PayloadViewerTitle({
|
|
650
|
-
file,
|
|
651
|
-
statusText: response.statusText
|
|
652
|
-
}))
|
|
653
|
-
|
|
654
|
-
if (mime.startsWith('image/')) // Naively assumes GET.200
|
|
655
|
-
payloadViewerCodeRef.current.replaceChildren(
|
|
656
|
-
r('img', { src: URL.createObjectURL(await response.blob()) }))
|
|
642
|
+
payloadViewerTitleRef.elem.replaceChildren(proxied
|
|
643
|
+
? PayloadViewerTitleWhenProxied(response)
|
|
644
|
+
: PayloadViewerTitle(file, response.statusText))
|
|
645
|
+
|
|
646
|
+
if (mime.startsWith('image/')) // Naively assumes GET 200
|
|
647
|
+
payloadViewerCodeRef.elem.replaceChildren(r('img', {
|
|
648
|
+
src: URL.createObjectURL(await response.blob())
|
|
649
|
+
}))
|
|
657
650
|
else {
|
|
658
651
|
const body = await response.text() || t`/* Empty Response Body */`
|
|
659
652
|
if (mime === 'application/json')
|
|
660
|
-
payloadViewerCodeRef.
|
|
653
|
+
payloadViewerCodeRef.elem.replaceChildren(r('span', className(CSS.json), SyntaxJSON(body)))
|
|
661
654
|
else if (isXML(mime))
|
|
662
|
-
payloadViewerCodeRef.
|
|
655
|
+
payloadViewerCodeRef.elem.replaceChildren(SyntaxXML(body))
|
|
663
656
|
else
|
|
664
|
-
payloadViewerCodeRef.
|
|
657
|
+
payloadViewerCodeRef.elem.textContent = body
|
|
665
658
|
}
|
|
666
659
|
}
|
|
667
660
|
|
|
@@ -671,38 +664,47 @@ function isXML(mime) {
|
|
|
671
664
|
}
|
|
672
665
|
|
|
673
666
|
|
|
674
|
-
async function onError(
|
|
675
|
-
|
|
667
|
+
async function onError(error) {
|
|
668
|
+
if (error?.name === 'AbortError')
|
|
669
|
+
return
|
|
676
670
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
671
|
+
let msg = ''
|
|
672
|
+
let isOffline = false
|
|
673
|
+
|
|
674
|
+
if (error instanceof Response) {
|
|
675
|
+
if (error.status === 422)
|
|
676
|
+
msg = await error.text()
|
|
677
|
+
else if (error.statusText)
|
|
678
|
+
msg = error.statusText
|
|
682
679
|
}
|
|
683
|
-
else {
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
if (error?.message === 'Failed to fetch')
|
|
687
|
-
error = t`Looks like the Mockaton server is not running` // TODO clear Error if comes back in ui-sync
|
|
688
|
-
else
|
|
689
|
-
error = error || t`Unexpected Error`
|
|
680
|
+
else if (error?.message === 'Failed to fetch') {
|
|
681
|
+
msg = t`Looks like the Mockaton server is not running`
|
|
682
|
+
isOffline = true
|
|
690
683
|
}
|
|
691
|
-
|
|
692
|
-
|
|
684
|
+
else
|
|
685
|
+
msg = error?.message || t`Unexpected Error`
|
|
686
|
+
|
|
687
|
+
ErrorToast(msg, isOffline)
|
|
688
|
+
console.error(error)
|
|
693
689
|
}
|
|
694
690
|
|
|
695
|
-
function
|
|
696
|
-
|
|
691
|
+
function ErrorToast(msg, isOffline) {
|
|
692
|
+
ErrorToast.isOffline = isOffline
|
|
693
|
+
ErrorToast.ref.elem?.remove()
|
|
697
694
|
document.body.appendChild(
|
|
698
695
|
r('div', {
|
|
696
|
+
role: 'alert',
|
|
697
|
+
ref: ErrorToast.ref,
|
|
699
698
|
className: CSS.ErrorToast,
|
|
700
|
-
onClick
|
|
701
|
-
const toast = this
|
|
702
|
-
document.startViewTransition(() => toast.remove())
|
|
703
|
-
}
|
|
699
|
+
onClick: ErrorToast.close
|
|
704
700
|
}, msg))
|
|
705
701
|
}
|
|
702
|
+
ErrorToast.isOffline = false
|
|
703
|
+
ErrorToast.ref = {}
|
|
704
|
+
ErrorToast.close = () => {
|
|
705
|
+
document.startViewTransition(() =>
|
|
706
|
+
ErrorToast.ref.elem?.remove())
|
|
707
|
+
}
|
|
706
708
|
|
|
707
709
|
|
|
708
710
|
/** # Icons */
|
|
@@ -732,7 +734,7 @@ function SettingsIcon() {
|
|
|
732
734
|
* The version increments when a mock file is added, removed, or renamed.
|
|
733
735
|
*/
|
|
734
736
|
function initRealTimeUpdates() {
|
|
735
|
-
let
|
|
737
|
+
let oldVersion = -1
|
|
736
738
|
let controller = new AbortController()
|
|
737
739
|
|
|
738
740
|
longPoll()
|
|
@@ -747,12 +749,15 @@ function initRealTimeUpdates() {
|
|
|
747
749
|
|
|
748
750
|
async function longPoll() {
|
|
749
751
|
try {
|
|
750
|
-
const response = await store.getSyncVersion(
|
|
752
|
+
const response = await store.getSyncVersion(oldVersion, controller.signal)
|
|
751
753
|
if (response.ok) {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
754
|
+
if (ErrorToast.isOffline)
|
|
755
|
+
ErrorToast.close()
|
|
756
|
+
|
|
757
|
+
const version = await response.json()
|
|
758
|
+
const skipUpdate = oldVersion === -1
|
|
759
|
+
if (oldVersion !== version) { // because it could be < or >
|
|
760
|
+
oldVersion = version
|
|
756
761
|
if (!skipUpdate)
|
|
757
762
|
store.fetchState()
|
|
758
763
|
}
|
|
@@ -804,7 +809,7 @@ function initKeyboardNavigation() {
|
|
|
804
809
|
}
|
|
805
810
|
|
|
806
811
|
function allInFocusGroup(focusGroup) {
|
|
807
|
-
return Array.from(leftSideRef.
|
|
812
|
+
return Array.from(leftSideRef.elem.querySelectorAll(
|
|
808
813
|
`tr > td [data-focus-group="${focusGroup}"]:is(input, a)`))
|
|
809
814
|
}
|
|
810
815
|
|
package/src/DashboardDom.js
CHANGED
|
@@ -3,15 +3,15 @@ export function className(...args) {
|
|
|
3
3
|
}
|
|
4
4
|
|
|
5
5
|
export function createElement(tag, props, ...children) {
|
|
6
|
-
const
|
|
6
|
+
const elem = document.createElement(tag)
|
|
7
7
|
for (const [k, v] of Object.entries(props || {}))
|
|
8
|
-
if (k === 'ref') v.
|
|
9
|
-
else if (k === 'style') Object.assign(
|
|
10
|
-
else if (k.startsWith('on'))
|
|
11
|
-
else if (k in
|
|
12
|
-
else
|
|
13
|
-
|
|
14
|
-
return
|
|
8
|
+
if (k === 'ref') v.elem = elem
|
|
9
|
+
else if (k === 'style') Object.assign(elem.style, v)
|
|
10
|
+
else if (k.startsWith('on')) elem.addEventListener(k.slice(2).toLowerCase(), ...[v].flat())
|
|
11
|
+
else if (k in elem) elem[k] = v
|
|
12
|
+
else elem.setAttribute(k, v)
|
|
13
|
+
elem.append(...children.flat().filter(Boolean))
|
|
14
|
+
return elem
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export function createSvgElement(tagName, props, ...children) {
|
|
@@ -22,10 +22,6 @@ export function createSvgElement(tagName, props, ...children) {
|
|
|
22
22
|
return elem
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
export function useRef() {
|
|
26
|
-
return { current: null }
|
|
27
|
-
}
|
|
28
|
-
|
|
29
25
|
export function Fragment(...args) {
|
|
30
26
|
const frag = new DocumentFragment()
|
|
31
27
|
for (const arg of args)
|
package/src/DashboardStore.js
CHANGED
|
@@ -11,8 +11,8 @@ export const store = {
|
|
|
11
11
|
render() {},
|
|
12
12
|
renderRow(method, urlMask) {},
|
|
13
13
|
|
|
14
|
-
brokersByMethod: /** @type
|
|
15
|
-
staticBrokers: /** @type
|
|
14
|
+
brokersByMethod: /** @type ClientBrokersByMethod */ {},
|
|
15
|
+
staticBrokers: /** @type ClientStaticBrokers */ {},
|
|
16
16
|
|
|
17
17
|
cookies: [],
|
|
18
18
|
comments: [],
|
|
@@ -37,8 +37,6 @@ export const store = {
|
|
|
37
37
|
},
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
leftSideWidth: undefined,
|
|
41
|
-
|
|
42
40
|
groupByMethod: initPreference('groupByMethod'),
|
|
43
41
|
toggleGroupByMethod() {
|
|
44
42
|
store.groupByMethod = !store.groupByMethod
|
|
@@ -47,17 +45,13 @@ export const store = {
|
|
|
47
45
|
},
|
|
48
46
|
|
|
49
47
|
|
|
50
|
-
chosenLink: {
|
|
51
|
-
method: '',
|
|
52
|
-
urlMask: ''
|
|
53
|
-
},
|
|
54
|
-
get hasChosenLink() {
|
|
55
|
-
return store.chosenLink.method
|
|
56
|
-
&& store.chosenLink.urlMask
|
|
57
|
-
},
|
|
48
|
+
chosenLink: { method: '', urlMask: '' },
|
|
58
49
|
setChosenLink(method, urlMask) {
|
|
59
50
|
store.chosenLink = { method, urlMask }
|
|
60
51
|
},
|
|
52
|
+
get hasChosenLink() {
|
|
53
|
+
return store.chosenLink.method && store.chosenLink.urlMask
|
|
54
|
+
},
|
|
61
55
|
|
|
62
56
|
|
|
63
57
|
async reset() {
|
|
@@ -277,7 +271,7 @@ function togglePreference(param, nextVal) {
|
|
|
277
271
|
* the repeated folder paths are kept but styled differently.
|
|
278
272
|
* @param {string[]} paths - sorted
|
|
279
273
|
*/
|
|
280
|
-
|
|
274
|
+
function dittoSplitPaths(paths) {
|
|
281
275
|
const result = [['', paths[0]]]
|
|
282
276
|
const pathsInParts = paths.map(p => p.split('/').filter(Boolean))
|
|
283
277
|
|
|
@@ -327,14 +321,14 @@ dittoSplitPaths.test = function () {
|
|
|
327
321
|
deferred(dittoSplitPaths.test)
|
|
328
322
|
|
|
329
323
|
|
|
330
|
-
|
|
324
|
+
class BrokerRowModel {
|
|
331
325
|
opts = /** @type {[key:string, label:string, selected:boolean][]} */ []
|
|
332
326
|
isNew = false
|
|
333
327
|
key = ''
|
|
334
328
|
method = ''
|
|
335
329
|
urlMask = ''
|
|
336
330
|
urlMaskDittoed = ['', '']
|
|
337
|
-
#broker = /** @type
|
|
331
|
+
#broker = /** @type ClientMockBroker */ {}
|
|
338
332
|
#canProxy = false
|
|
339
333
|
|
|
340
334
|
/**
|
|
@@ -456,12 +450,12 @@ const TestBrokerRowModel = {
|
|
|
456
450
|
deferred(() => Object.values(TestBrokerRowModel).forEach(t => t()))
|
|
457
451
|
|
|
458
452
|
|
|
459
|
-
|
|
453
|
+
class StaticBrokerRowModel {
|
|
460
454
|
isNew = false
|
|
461
455
|
key = ''
|
|
462
456
|
method = 'GET'
|
|
463
457
|
urlMaskDittoed = ['', '']
|
|
464
|
-
#broker = /** @type
|
|
458
|
+
#broker = /** @type ClientStaticBroker */ {}
|
|
465
459
|
|
|
466
460
|
/** @param {ClientStaticBroker} broker */
|
|
467
461
|
constructor(broker) {
|
package/src/MockDispatcher.js
CHANGED
package/src/Watcher.js
CHANGED
|
@@ -10,7 +10,7 @@ import * as mockBrokerCollection from './mockBrokersCollection.js'
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* ARR = Add, Remove, or Rename Mock Event
|
|
13
|
-
*
|
|
13
|
+
*
|
|
14
14
|
* The emitter is debounced so it handles e.g. bulk deletes,
|
|
15
15
|
* and also renames, which are two events (delete + add).
|
|
16
16
|
*/
|
|
@@ -44,22 +44,19 @@ export function watchMocksDir() {
|
|
|
44
44
|
if (!file)
|
|
45
45
|
return
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (isDirectory(path)) {
|
|
47
|
+
if (isDirectory(join(dir, file))) {
|
|
50
48
|
mockBrokerCollection.init()
|
|
51
49
|
uiSyncVersion.increment()
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (isFile(path)) {
|
|
56
|
-
if (mockBrokerCollection.registerMock(file, Boolean('isFromWatcher')))
|
|
57
|
-
uiSyncVersion.increment()
|
|
58
50
|
}
|
|
59
|
-
else {
|
|
51
|
+
else if (!isFile(join(dir, file))) { // file deleted
|
|
60
52
|
mockBrokerCollection.unregisterMock(file)
|
|
61
53
|
uiSyncVersion.increment()
|
|
62
54
|
}
|
|
55
|
+
else if (mockBrokerCollection.registerMock(file, Boolean('isFromWatcher')))
|
|
56
|
+
uiSyncVersion.increment()
|
|
57
|
+
else {
|
|
58
|
+
// ignore file edits
|
|
59
|
+
}
|
|
63
60
|
})
|
|
64
61
|
}
|
|
65
62
|
|
|
@@ -67,27 +64,25 @@ export function watchStaticDir() {
|
|
|
67
64
|
const dir = config.staticDir
|
|
68
65
|
if (!dir)
|
|
69
66
|
return
|
|
67
|
+
|
|
70
68
|
watch(dir, { recursive: true, persistent: false }, (_, file) => {
|
|
71
69
|
if (!file)
|
|
72
70
|
return
|
|
73
71
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (isDirectory(path)) {
|
|
72
|
+
if (isDirectory(join(dir, file))) {
|
|
77
73
|
staticCollection.init()
|
|
78
74
|
uiSyncVersion.increment()
|
|
79
|
-
return
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (isFile(path)) {
|
|
83
|
-
if (staticCollection.registerMock(file))
|
|
84
|
-
uiSyncVersion.increment()
|
|
85
75
|
}
|
|
86
|
-
else {
|
|
76
|
+
else if (!isFile(join(dir, file))) { // file deleted
|
|
87
77
|
staticCollection.unregisterMock(file)
|
|
88
78
|
uiSyncVersion.increment()
|
|
89
79
|
}
|
|
80
|
+
else if (staticCollection.registerMock(file))
|
|
81
|
+
uiSyncVersion.increment()
|
|
82
|
+
else {
|
|
83
|
+
// ignore file edits
|
|
84
|
+
}
|
|
90
85
|
})
|
|
91
86
|
}
|
|
92
87
|
|
|
93
|
-
// TODO config changes
|
|
88
|
+
// TODO ThinkAbout watching for config changes
|
package/src/config.js
CHANGED
|
@@ -60,7 +60,7 @@ for (const [k, [defaultVal, validator]] of Object.entries(schema)) {
|
|
|
60
60
|
validators[k] = validator
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
/** @type
|
|
63
|
+
/** @type Config */
|
|
64
64
|
export const config = Object.seal(defaults)
|
|
65
65
|
|
|
66
66
|
/** @type {Record<keyof Config, (val: unknown) => val is Config[keyof Config]>} */
|
|
@@ -105,16 +105,16 @@ export function brokerByRoute(method, url) {
|
|
|
105
105
|
|
|
106
106
|
export function extractAllComments() {
|
|
107
107
|
const comments = new Set()
|
|
108
|
-
forEachBroker(
|
|
109
|
-
for (const c of
|
|
108
|
+
forEachBroker(b => {
|
|
109
|
+
for (const c of b.extractComments())
|
|
110
110
|
comments.add(c)
|
|
111
111
|
})
|
|
112
112
|
return Array.from(comments)
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
export function setMocksMatchingComment(comment) {
|
|
116
|
-
forEachBroker(
|
|
117
|
-
|
|
116
|
+
forEachBroker(b =>
|
|
117
|
+
b.setByMatchingComment(comment))
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
function forEachBroker(fn) {
|
package/src/utils/mime.js
CHANGED
|
@@ -105,9 +105,9 @@ export function mimeFor(filename) {
|
|
|
105
105
|
}
|
|
106
106
|
function extname(filename) {
|
|
107
107
|
const i = filename.lastIndexOf('.')
|
|
108
|
-
return i
|
|
109
|
-
?
|
|
110
|
-
:
|
|
108
|
+
return i === -1
|
|
109
|
+
? ''
|
|
110
|
+
: filename.slice(i + 1)
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
|