mockaton 8.7.0 → 8.7.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 +11 -7
- package/package.json +1 -1
- package/src/Dashboard.css +6 -2
- package/src/Dashboard.js +139 -105
package/README.md
CHANGED
|
@@ -214,6 +214,9 @@ Response Status Code, and File Extension.
|
|
|
214
214
|
api/user.GET.200.json
|
|
215
215
|
```
|
|
216
216
|
|
|
217
|
+
You can also use `.empty` if you don’t want the response to have a
|
|
218
|
+
`Content-Type` header.
|
|
219
|
+
|
|
217
220
|
|
|
218
221
|
### Dynamic Parameters
|
|
219
222
|
Anything within square brackets is always matched. For example, for this route
|
|
@@ -269,7 +272,7 @@ api/foo/bar.GET.200.json
|
|
|
269
272
|
---
|
|
270
273
|
## Config
|
|
271
274
|
### `mocksDir: string`
|
|
272
|
-
This is the only required field
|
|
275
|
+
This is the only required field. The directory must exist.
|
|
273
276
|
|
|
274
277
|
|
|
275
278
|
### `host?: string`
|
|
@@ -285,10 +288,10 @@ Defaults to `/(\.DS_Store|~)$/`
|
|
|
285
288
|
|
|
286
289
|
|
|
287
290
|
### `delay?: number`
|
|
288
|
-
Defaults to `
|
|
291
|
+
Defaults to `1200` milliseconds.
|
|
289
292
|
|
|
290
293
|
Although routes can individually be delayed with the 🕓
|
|
291
|
-
checkbox, delay
|
|
294
|
+
checkbox, the delay amount is globally configurable.
|
|
292
295
|
|
|
293
296
|
|
|
294
297
|
### `proxyFallback?: string`
|
|
@@ -298,7 +301,7 @@ For example, `config.proxyFallback = 'http://example.com'`
|
|
|
298
301
|
### `collectProxied?: boolean`
|
|
299
302
|
Defaults to `false`. With this flag you can save mocks that hit
|
|
300
303
|
your proxy fallback to `config.mocksDir`. If there are UUIDv4 in the
|
|
301
|
-
URL the filename will have `[id]` in their place. For example,
|
|
304
|
+
URL, the filename will have `[id]` in their place. For example,
|
|
302
305
|
|
|
303
306
|
```
|
|
304
307
|
/api/user/d14e09c8-d970-4b07-be42-b2f4ee22f0a6/likes =>
|
|
@@ -377,7 +380,7 @@ config.extraMimes = {
|
|
|
377
380
|
jpe: 'application/jpeg'
|
|
378
381
|
}
|
|
379
382
|
```
|
|
380
|
-
|
|
383
|
+
Those extra media types take precedence over the built-in
|
|
381
384
|
[utils/mime.js](src/utils/mime.js), so you can override them.
|
|
382
385
|
|
|
383
386
|
|
|
@@ -392,8 +395,9 @@ type Plugin = (
|
|
|
392
395
|
body: string | Uint8Array
|
|
393
396
|
}>
|
|
394
397
|
```
|
|
395
|
-
Plugins are for processing mocks before sending them. If no
|
|
396
|
-
|
|
398
|
+
Plugins are for processing mocks before sending them. If no
|
|
399
|
+
regex matches the filename, the fallback plugin will read
|
|
400
|
+
the file from disk and compute the MIME from the extension.
|
|
397
401
|
|
|
398
402
|
Note: don’t call `response.end()` on any plugin.
|
|
399
403
|
|
package/package.json
CHANGED
package/src/Dashboard.css
CHANGED
|
@@ -84,7 +84,7 @@ select {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
.Header {
|
|
88
88
|
position: fixed;
|
|
89
89
|
z-index: 100;
|
|
90
90
|
top: 0;
|
|
@@ -171,7 +171,7 @@ menu {
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
.MockList {
|
|
175
175
|
display: flex;
|
|
176
176
|
align-items: flex-start;
|
|
177
177
|
margin-top: 64px;
|
|
@@ -189,6 +189,10 @@ main {
|
|
|
189
189
|
border-top: 1px solid transparent;
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
|
+
|
|
193
|
+
&.empty {
|
|
194
|
+
margin-top: 80px;
|
|
195
|
+
}
|
|
192
196
|
}
|
|
193
197
|
|
|
194
198
|
|
package/src/Dashboard.js
CHANGED
|
@@ -26,7 +26,9 @@ const CSS = {
|
|
|
26
26
|
DelayToggler: 'DelayToggler',
|
|
27
27
|
FallbackBackend: 'FallbackBackend',
|
|
28
28
|
Field: 'Field',
|
|
29
|
+
Header: 'Header',
|
|
29
30
|
InternalServerErrorToggler: 'InternalServerErrorToggler',
|
|
31
|
+
MockList: 'MockList',
|
|
30
32
|
MockSelector: 'MockSelector',
|
|
31
33
|
PayloadViewer: 'PayloadViewer',
|
|
32
34
|
PreviewLink: 'PreviewLink',
|
|
@@ -36,6 +38,7 @@ const CSS = {
|
|
|
36
38
|
StaticFilesList: 'StaticFilesList',
|
|
37
39
|
|
|
38
40
|
bold: 'bold',
|
|
41
|
+
empty: 'empty',
|
|
39
42
|
chosen: 'chosen',
|
|
40
43
|
status4xx: 'status4xx'
|
|
41
44
|
}
|
|
@@ -55,45 +58,50 @@ function init() {
|
|
|
55
58
|
mockaton.getProxyFallback(),
|
|
56
59
|
mockaton.listStaticFiles()
|
|
57
60
|
].map(api => api.then(response => response.ok && response.json())))
|
|
58
|
-
.then(
|
|
61
|
+
.then(data => {
|
|
62
|
+
empty(document.body)
|
|
63
|
+
document.body.append(...App(data))
|
|
64
|
+
})
|
|
59
65
|
.catch(onError)
|
|
60
66
|
}
|
|
61
67
|
init()
|
|
62
68
|
|
|
63
|
-
function App(
|
|
64
|
-
|
|
65
|
-
|
|
69
|
+
function App([brokersByMethod, cookies, comments, collectProxied, fallbackAddress, staticFiles]) {
|
|
70
|
+
return [
|
|
71
|
+
r(Header, { cookies, comments, fallbackAddress, collectProxied }),
|
|
72
|
+
r(MockList, { brokersByMethod }),
|
|
73
|
+
r(StaticFilesList, { staticFiles })
|
|
74
|
+
]
|
|
66
75
|
}
|
|
67
76
|
|
|
68
|
-
|
|
69
|
-
|
|
77
|
+
|
|
78
|
+
// Header ===============
|
|
79
|
+
|
|
80
|
+
function Header({ cookies, comments, fallbackAddress, collectProxied }) {
|
|
81
|
+
return (
|
|
82
|
+
r('menu', { className: CSS.Header },
|
|
83
|
+
r(Logo),
|
|
84
|
+
r(CookieSelector, { cookies }),
|
|
85
|
+
r(BulkSelector, { comments }),
|
|
86
|
+
r(ProxyFallbackField, { fallbackAddress, collectProxied }),
|
|
87
|
+
r(ResetButton)))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function Logo() {
|
|
70
91
|
return (
|
|
71
|
-
r('
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
? r('main', null, Strings.no_mocks_found)
|
|
80
|
-
: r('main', null,
|
|
81
|
-
r('table', null, Object.entries(brokersByMethod).map(([method, brokers]) =>
|
|
82
|
-
r(SectionByMethod, { method, brokers }))),
|
|
83
|
-
r('div', { className: CSS.PayloadViewer },
|
|
84
|
-
r('h2', { ref: refPayloadViewerFileTitle }, Strings.mock),
|
|
85
|
-
r('pre', null,
|
|
86
|
-
r('code', { ref: refPayloadViewer }, Strings.click_link_to_preview)))),
|
|
87
|
-
r(StaticFilesList, { staticFiles })))
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
function CookieSelector({ list }) {
|
|
92
|
+
r('img', {
|
|
93
|
+
alt: Strings.title,
|
|
94
|
+
src: '/mockaton-logo.svg',
|
|
95
|
+
width: 160
|
|
96
|
+
}))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function CookieSelector({ cookies }) {
|
|
92
100
|
function onChange() {
|
|
93
101
|
mockaton.selectCookie(this.value)
|
|
94
102
|
.catch(onError)
|
|
95
103
|
}
|
|
96
|
-
const disabled =
|
|
104
|
+
const disabled = cookies.length <= 1
|
|
97
105
|
return (
|
|
98
106
|
r('label', { className: CSS.Field },
|
|
99
107
|
r('span', null, Strings.cookie),
|
|
@@ -102,11 +110,10 @@ function CookieSelector({ list }) {
|
|
|
102
110
|
disabled,
|
|
103
111
|
title: disabled ? Strings.cookie_disabled_title : '',
|
|
104
112
|
onChange
|
|
105
|
-
},
|
|
113
|
+
}, cookies.map(([value, selected]) =>
|
|
106
114
|
r('option', { value, selected }, value)))))
|
|
107
115
|
}
|
|
108
116
|
|
|
109
|
-
|
|
110
117
|
function BulkSelector({ comments }) {
|
|
111
118
|
function onChange() {
|
|
112
119
|
mockaton.bulkSelectByComment(this.value)
|
|
@@ -130,7 +137,6 @@ function BulkSelector({ comments }) {
|
|
|
130
137
|
r('option', { value }, value)))))
|
|
131
138
|
}
|
|
132
139
|
|
|
133
|
-
|
|
134
140
|
function ProxyFallbackField({ fallbackAddress = '', collectProxied }) {
|
|
135
141
|
const refSaveProxiedCheckbox = useRef()
|
|
136
142
|
function onChange(event) {
|
|
@@ -139,8 +145,7 @@ function ProxyFallbackField({ fallbackAddress = '', collectProxied }) {
|
|
|
139
145
|
if (!input.validity.valid)
|
|
140
146
|
input.reportValidity()
|
|
141
147
|
else
|
|
142
|
-
mockaton.setProxyFallback(input.value.trim())
|
|
143
|
-
.catch(onError)
|
|
148
|
+
mockaton.setProxyFallback(input.value.trim()).catch(onError)
|
|
144
149
|
}
|
|
145
150
|
return (
|
|
146
151
|
r('div', { className: cssClass(CSS.Field, CSS.FallbackBackend) },
|
|
@@ -160,7 +165,6 @@ function ProxyFallbackField({ fallbackAddress = '', collectProxied }) {
|
|
|
160
165
|
})))
|
|
161
166
|
}
|
|
162
167
|
|
|
163
|
-
|
|
164
168
|
function SaveProxiedCheckbox({ ref, disabled, collectProxied }) {
|
|
165
169
|
function onChange(event) {
|
|
166
170
|
mockaton.setCollectProxied(event.currentTarget.checked)
|
|
@@ -178,7 +182,6 @@ function SaveProxiedCheckbox({ ref, disabled, collectProxied }) {
|
|
|
178
182
|
r('span', null, Strings.save_proxied)))
|
|
179
183
|
}
|
|
180
184
|
|
|
181
|
-
|
|
182
185
|
function ResetButton() {
|
|
183
186
|
return (
|
|
184
187
|
r('button', {
|
|
@@ -192,18 +195,20 @@ function ResetButton() {
|
|
|
192
195
|
}
|
|
193
196
|
|
|
194
197
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
+
|
|
199
|
+
// MockList ===============
|
|
200
|
+
|
|
201
|
+
function MockList({ brokersByMethod }) {
|
|
202
|
+
const hasMocks = Object.keys(brokersByMethod).length
|
|
203
|
+
if (!hasMocks)
|
|
204
|
+
return (
|
|
205
|
+
r('main', { className: cssClass(CSS.MockList, CSS.empty) },
|
|
206
|
+
Strings.no_mocks_found))
|
|
198
207
|
return (
|
|
199
|
-
r('
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
r('summary', null, Strings.static),
|
|
204
|
-
r('ul', null, staticFiles.map(f =>
|
|
205
|
-
r('li', null,
|
|
206
|
-
r('a', { href: f, target: '_blank' }, f))))))
|
|
208
|
+
r('main', { className: CSS.MockList },
|
|
209
|
+
r('table', null, Object.entries(brokersByMethod).map(([method, brokers]) =>
|
|
210
|
+
r(SectionByMethod, { method, brokers }))),
|
|
211
|
+
r(PayloadViewer)))
|
|
207
212
|
}
|
|
208
213
|
|
|
209
214
|
|
|
@@ -227,25 +232,16 @@ function PreviewLink({ method, urlMask }) {
|
|
|
227
232
|
async function onClick(event) {
|
|
228
233
|
event.preventDefault()
|
|
229
234
|
try {
|
|
230
|
-
const
|
|
235
|
+
const preloader = setTimeout(() => {
|
|
231
236
|
empty(refPayloadViewer.current)
|
|
232
|
-
refPayloadViewer.current.append(
|
|
237
|
+
refPayloadViewer.current.append(PayloadViewerProgressBar())
|
|
233
238
|
}, 180)
|
|
234
|
-
|
|
239
|
+
|
|
240
|
+
const response = await fetch(this.href, { method })
|
|
241
|
+
clearTimeout(preloader)
|
|
242
|
+
await updatePayloadViewer(method, urlMask, this.href, response)
|
|
235
243
|
document.querySelector(`.${CSS.PreviewLink}.${CSS.chosen}`)?.classList.remove(CSS.chosen)
|
|
236
244
|
this.classList.add(CSS.chosen)
|
|
237
|
-
clearTimeout(spinner)
|
|
238
|
-
|
|
239
|
-
const mime = res.headers.get('content-type') || ''
|
|
240
|
-
if (mime.startsWith('image/')) // naively assumes GET.200
|
|
241
|
-
renderPayloadImage(this.href)
|
|
242
|
-
else
|
|
243
|
-
updatePayloadViewer(await res.text() || Strings.empty_response_body, mime)
|
|
244
|
-
|
|
245
|
-
empty(refPayloadViewerFileTitle.current)
|
|
246
|
-
refPayloadViewerFileTitle.current.append(PayloadViewerTitle({
|
|
247
|
-
file: mockSelectorFor(method, urlMask).value
|
|
248
|
-
}))
|
|
249
245
|
}
|
|
250
246
|
catch (error) {
|
|
251
247
|
onError(error)
|
|
@@ -260,37 +256,14 @@ function PreviewLink({ method, urlMask }) {
|
|
|
260
256
|
}
|
|
261
257
|
|
|
262
258
|
|
|
263
|
-
function PayloadViewerTitle({ file }) {
|
|
264
|
-
const { urlMask, method, status, ext } = parseFilename(file)
|
|
265
|
-
return (
|
|
266
|
-
r('span', null,
|
|
267
|
-
urlMask + '.' + method + '.',
|
|
268
|
-
r('abbr', { title: HttpStatus[status] }, status),
|
|
269
|
-
'.' + ext))
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
function ProgressBar() {
|
|
274
|
-
return (
|
|
275
|
-
r('div', { className: CSS.ProgressBar },
|
|
276
|
-
r('div', { style: { animationDuration: '1000ms' } }))) // TODO from Config.delay - 180
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
function renderPayloadImage(href) {
|
|
281
|
-
empty(refPayloadViewer.current)
|
|
282
|
-
refPayloadViewer.current.append(r('img', { src: href }))
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
function updatePayloadViewer(body, mime) {
|
|
286
|
-
if (mime === 'application/json' && window?.Prism.languages)
|
|
287
|
-
refPayloadViewer.current.innerHTML = window.Prism.highlight(body, window.Prism.languages.json, 'json')
|
|
288
|
-
else
|
|
289
|
-
refPayloadViewer.current.innerText = body
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
|
|
293
259
|
function MockSelector({ broker }) {
|
|
260
|
+
function className(defaultIsSelected, status) {
|
|
261
|
+
return cssClass(
|
|
262
|
+
CSS.MockSelector,
|
|
263
|
+
!defaultIsSelected && CSS.bold,
|
|
264
|
+
status >= 400 && status < 500 && CSS.status4xx)
|
|
265
|
+
}
|
|
266
|
+
|
|
294
267
|
function onChange() {
|
|
295
268
|
const { status, urlMask, method } = parseFilename(this.value)
|
|
296
269
|
this.style.fontWeight = this.value === this.options[0].value // default is selected
|
|
@@ -305,13 +278,6 @@ function MockSelector({ broker }) {
|
|
|
305
278
|
.catch(onError)
|
|
306
279
|
}
|
|
307
280
|
|
|
308
|
-
function className(defaultIsSelected, status) {
|
|
309
|
-
return cssClass(
|
|
310
|
-
CSS.MockSelector,
|
|
311
|
-
!defaultIsSelected && CSS.bold,
|
|
312
|
-
status >= 400 && status < 500 && CSS.status4xx)
|
|
313
|
-
}
|
|
314
|
-
|
|
315
281
|
const selected = broker.currentMock.file
|
|
316
282
|
const { status, urlMask } = parseFilename(selected)
|
|
317
283
|
const files = broker.mocks.filter(item =>
|
|
@@ -351,13 +317,12 @@ function DelayRouteToggler({ broker }) {
|
|
|
351
317
|
onChange
|
|
352
318
|
}),
|
|
353
319
|
TimerIcon()))
|
|
354
|
-
}
|
|
355
320
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
321
|
+
function TimerIcon() {
|
|
322
|
+
return (
|
|
323
|
+
r('svg', { viewBox: '0 0 24 24' },
|
|
324
|
+
r('path', { d: 'M12 7H11v6l5 3.2.75-1.23-4.5-3z' })))
|
|
325
|
+
}
|
|
361
326
|
}
|
|
362
327
|
|
|
363
328
|
|
|
@@ -387,6 +352,54 @@ function InternalServerErrorToggler({ broker }) {
|
|
|
387
352
|
)
|
|
388
353
|
}
|
|
389
354
|
|
|
355
|
+
function PayloadViewerProgressBar() {
|
|
356
|
+
return (
|
|
357
|
+
r('div', { className: CSS.ProgressBar },
|
|
358
|
+
r('div', { style: { animationDuration: '1000ms' } }))) // TODO from Config.delay - 180
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function PayloadViewer() {
|
|
362
|
+
return (
|
|
363
|
+
r('div', { className: CSS.PayloadViewer },
|
|
364
|
+
r('h2', { ref: refPayloadViewerFileTitle }, Strings.mock),
|
|
365
|
+
r('pre', null,
|
|
366
|
+
r('code', { ref: refPayloadViewer }, Strings.click_link_to_preview))))
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function PayloadViewerTitle({ file }) {
|
|
370
|
+
const { urlMask, method, status, ext } = parseFilename(file)
|
|
371
|
+
return (
|
|
372
|
+
r('span', null,
|
|
373
|
+
urlMask + '.' + method + '.',
|
|
374
|
+
r('abbr', { title: HttpStatus[status] }, status),
|
|
375
|
+
'.' + ext))
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async function updatePayloadViewer(method, urlMask, imgSrc, response) {
|
|
379
|
+
empty(refPayloadViewerFileTitle.current)
|
|
380
|
+
refPayloadViewerFileTitle.current.append(PayloadViewerTitle({
|
|
381
|
+
file: mockSelectorFor(method, urlMask).value
|
|
382
|
+
}))
|
|
383
|
+
|
|
384
|
+
const mime = response.headers.get('content-type') || ''
|
|
385
|
+
if (mime.startsWith('image/')) // naively assumes GET.200
|
|
386
|
+
renderPayloadImage(imgSrc) // TESTME in pixaton
|
|
387
|
+
else
|
|
388
|
+
renderPayloadBody(await response.text() || Strings.empty_response_body, mime)
|
|
389
|
+
|
|
390
|
+
function renderPayloadImage(src) {
|
|
391
|
+
empty(refPayloadViewer.current)
|
|
392
|
+
refPayloadViewer.current.append(r('img', { src }))
|
|
393
|
+
}
|
|
394
|
+
function renderPayloadBody(body, mime) {
|
|
395
|
+
if (mime === 'application/json' && window.Prism?.highlight && window.Prism?.languages)
|
|
396
|
+
refPayloadViewer.current.innerHTML = window.Prism.highlight(body, window.Prism.languages.json, 'json')
|
|
397
|
+
else
|
|
398
|
+
refPayloadViewer.current.innerText = body
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
|
|
390
403
|
function trFor(method, urlMask) {
|
|
391
404
|
return document.querySelector(`tr[data-method="${method}"][data-urlMask="${urlMask}"]`)
|
|
392
405
|
}
|
|
@@ -401,6 +414,25 @@ function mockSelectorFor(method, urlMask) {
|
|
|
401
414
|
}
|
|
402
415
|
|
|
403
416
|
|
|
417
|
+
|
|
418
|
+
// StaticFilesList ===============
|
|
419
|
+
|
|
420
|
+
function StaticFilesList({ staticFiles }) {
|
|
421
|
+
if (!staticFiles.length)
|
|
422
|
+
return null
|
|
423
|
+
return (
|
|
424
|
+
r('details', {
|
|
425
|
+
open: true,
|
|
426
|
+
className: CSS.StaticFilesList
|
|
427
|
+
},
|
|
428
|
+
r('summary', null, Strings.static),
|
|
429
|
+
r('ul', null, staticFiles.map(f =>
|
|
430
|
+
r('li', null,
|
|
431
|
+
r('a', { href: f, target: '_blank' }, f))))))
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
|
|
404
436
|
function onError(error) {
|
|
405
437
|
if (error?.message === 'Failed to fetch')
|
|
406
438
|
alert('Looks like the Mockaton server is not running')
|
|
@@ -408,7 +440,9 @@ function onError(error) {
|
|
|
408
440
|
}
|
|
409
441
|
|
|
410
442
|
|
|
411
|
-
|
|
443
|
+
|
|
444
|
+
// Utils ============
|
|
445
|
+
|
|
412
446
|
function cssClass(...args) {
|
|
413
447
|
return args.filter(a => a).join(' ')
|
|
414
448
|
}
|