mockaton 8.1.0 → 8.1.2
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-dashboard-dark.png +0 -0
- package/README-dashboard-light.png +0 -0
- package/README.md +2 -14
- package/package.json +1 -1
- package/src/Dashboard.js +127 -113
- package/src/Mockaton.js +1 -1
- package/src/utils/http-cors.js +1 -0
|
Binary file
|
|
Binary file
|
package/README.md
CHANGED
|
@@ -226,7 +226,7 @@ Also, they take precedence over the `GET` mocks in `Config.mockDir`.
|
|
|
226
226
|
For example, if you have two files for `GET /foo/bar.jpg`
|
|
227
227
|
```
|
|
228
228
|
my-static-dir/foo/bar.jpg
|
|
229
|
-
|
|
229
|
+
my-mocks-dir/foo/bar.jpg.GET.200.jpg // Unreacheable
|
|
230
230
|
```
|
|
231
231
|
|
|
232
232
|
Use Case 1: If you have a bunch of static assets you don’t want to add `.GET.200.ext`
|
|
@@ -347,17 +347,7 @@ If you don’t want to open a browser, pass a noop, such as
|
|
|
347
347
|
Config.onReady = () => {}
|
|
348
348
|
```
|
|
349
349
|
|
|
350
|
-
|
|
351
|
-
```js
|
|
352
|
-
import { exec } from 'node:child_process'
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
Config.onReady = function openInBrowser(address) {
|
|
356
|
-
exec(`xdg-open ${address}`)
|
|
357
|
-
}
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
Or, for more cross-platform utility, you could `npm install open` and pass it.
|
|
350
|
+
For a more cross-platform utility, you could `npm install open` and pass it.
|
|
361
351
|
```js
|
|
362
352
|
import open from 'open'
|
|
363
353
|
|
|
@@ -420,5 +410,3 @@ await mockaton.reset()
|
|
|
420
410
|
|
|
421
411
|
## TODO
|
|
422
412
|
- Refactor Tests
|
|
423
|
-
- Dashboard. Indicate if some file on `staticDir` is overriding a mock.
|
|
424
|
-
- jsonc, json5?
|
package/package.json
CHANGED
package/src/Dashboard.js
CHANGED
|
@@ -36,7 +36,7 @@ const CSS = {
|
|
|
36
36
|
|
|
37
37
|
const r = createElement
|
|
38
38
|
const refPayloadViewer = useRef()
|
|
39
|
-
const
|
|
39
|
+
const refPayloadViewerFileTitle = useRef()
|
|
40
40
|
|
|
41
41
|
const mockaton = new Commander(window.location.origin)
|
|
42
42
|
|
|
@@ -53,13 +53,13 @@ function init() {
|
|
|
53
53
|
}
|
|
54
54
|
init()
|
|
55
55
|
|
|
56
|
-
function App(
|
|
56
|
+
function App(apiResponses) {
|
|
57
57
|
empty(document.body)
|
|
58
|
-
createRoot(document.body)
|
|
59
|
-
DevPanel(
|
|
58
|
+
createRoot(document.body)
|
|
59
|
+
.render(DevPanel(apiResponses))
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
function DevPanel(brokersByMethod, cookies, comments, corsAllowed, staticFiles) {
|
|
62
|
+
function DevPanel([brokersByMethod, cookies, comments, corsAllowed, staticFiles]) {
|
|
63
63
|
return (
|
|
64
64
|
r('div', null,
|
|
65
65
|
r('menu', null,
|
|
@@ -72,33 +72,39 @@ function DevPanel(brokersByMethod, cookies, comments, corsAllowed, staticFiles)
|
|
|
72
72
|
r('table', null, Object.entries(brokersByMethod).map(([method, brokers]) =>
|
|
73
73
|
r(SectionByMethod, { method, brokers }))),
|
|
74
74
|
r('div', { className: CSS.PayloadViewer },
|
|
75
|
-
r('h2', { ref:
|
|
75
|
+
r('h2', { ref: refPayloadViewerFileTitle }, Strings.mock),
|
|
76
76
|
r('pre', null,
|
|
77
77
|
r('code', { ref: refPayloadViewer }, Strings.click_link_to_preview)))),
|
|
78
78
|
r(StaticFilesList, { staticFiles })))
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
|
|
81
82
|
function CookieSelector({ list }) {
|
|
83
|
+
function onChange() {
|
|
84
|
+
mockaton.selectCookie(this.value)
|
|
85
|
+
.catch(console.error)
|
|
86
|
+
}
|
|
82
87
|
const disabled = list.length <= 1
|
|
83
88
|
return (
|
|
84
89
|
r('label', null,
|
|
85
90
|
r('span', null, Strings.cookie),
|
|
86
91
|
r('select', {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}, list.map(([key, selected]) =>
|
|
95
|
-
r('option', {
|
|
96
|
-
value: key,
|
|
97
|
-
selected
|
|
98
|
-
}, key)))))
|
|
92
|
+
autocomplete: 'off',
|
|
93
|
+
disabled,
|
|
94
|
+
title: disabled ? Strings.cookie_disabled_title : '',
|
|
95
|
+
onChange
|
|
96
|
+
},
|
|
97
|
+
list.map(([key, selected]) =>
|
|
98
|
+
r('option', { value: key, selected }, key)))))
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
|
|
101
102
|
function BulkSelector({ comments }) {
|
|
103
|
+
function onChange() {
|
|
104
|
+
mockaton.bulkSelectByComment(this.value)
|
|
105
|
+
.then(init)
|
|
106
|
+
.catch(console.error)
|
|
107
|
+
}
|
|
102
108
|
const disabled = !comments.length
|
|
103
109
|
const list = disabled
|
|
104
110
|
? []
|
|
@@ -107,34 +113,32 @@ function BulkSelector({ comments }) {
|
|
|
107
113
|
r('label', null,
|
|
108
114
|
r('span', null, Strings.bulk_select_by_comment),
|
|
109
115
|
r('select', {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
}, list.map(item =>
|
|
119
|
-
r('option', {
|
|
120
|
-
value: item
|
|
121
|
-
}, item)))))
|
|
116
|
+
autocomplete: 'off',
|
|
117
|
+
disabled,
|
|
118
|
+
title: disabled ? Strings.bulk_select_by_comment_disabled_title : '',
|
|
119
|
+
onChange
|
|
120
|
+
},
|
|
121
|
+
list.map(value =>
|
|
122
|
+
r('option', { value }, value)))))
|
|
122
123
|
}
|
|
123
124
|
|
|
125
|
+
|
|
124
126
|
function CorsCheckbox({ corsAllowed }) {
|
|
127
|
+
function onChange(event) {
|
|
128
|
+
mockaton.setCorsAllowed(event.currentTarget.checked)
|
|
129
|
+
.catch(console.error)
|
|
130
|
+
}
|
|
125
131
|
return (
|
|
126
132
|
r('label', { className: CSS.CorsCheckbox },
|
|
127
133
|
r('input', {
|
|
128
134
|
type: 'checkbox',
|
|
129
135
|
checked: corsAllowed,
|
|
130
|
-
onChange
|
|
131
|
-
mockaton.setCorsAllowed(event.currentTarget.checked)
|
|
132
|
-
.catch(console.error)
|
|
133
|
-
}
|
|
136
|
+
onChange
|
|
134
137
|
}),
|
|
135
138
|
Strings.allow_cors))
|
|
136
139
|
}
|
|
137
140
|
|
|
141
|
+
|
|
138
142
|
function ResetButton() {
|
|
139
143
|
return (
|
|
140
144
|
r('button', {
|
|
@@ -147,6 +151,7 @@ function ResetButton() {
|
|
|
147
151
|
)
|
|
148
152
|
}
|
|
149
153
|
|
|
154
|
+
|
|
150
155
|
function StaticFilesList({ staticFiles }) {
|
|
151
156
|
if (!staticFiles.length)
|
|
152
157
|
return null
|
|
@@ -177,46 +182,50 @@ function SectionByMethod({ method, brokers }) {
|
|
|
177
182
|
r('td', null, r(PreviewLink, { method, urlMask })),
|
|
178
183
|
r('td', null, r(MockSelector, { broker })),
|
|
179
184
|
r('td', null, r(DelayRouteToggler, { broker })),
|
|
180
|
-
r('td', null, r(InternalServerErrorToggler, { broker }))
|
|
181
|
-
))))
|
|
185
|
+
r('td', null, r(InternalServerErrorToggler, { broker }))))))
|
|
182
186
|
}
|
|
183
187
|
|
|
188
|
+
|
|
184
189
|
function PreviewLink({ method, urlMask }) {
|
|
190
|
+
async function onClick(event) {
|
|
191
|
+
event.preventDefault()
|
|
192
|
+
try {
|
|
193
|
+
const spinner = setTimeout(() => {
|
|
194
|
+
empty(refPayloadViewer.current)
|
|
195
|
+
refPayloadViewer.current.append(ProgressBar())
|
|
196
|
+
}, 180)
|
|
197
|
+
const res = await fetch(this.href, {
|
|
198
|
+
method: this.getAttribute('data-method')
|
|
199
|
+
})
|
|
200
|
+
document.querySelector(`.${CSS.PreviewLink}.${CSS.chosen}`)?.classList.remove(CSS.chosen)
|
|
201
|
+
this.classList.add(CSS.chosen)
|
|
202
|
+
clearTimeout(spinner)
|
|
203
|
+
|
|
204
|
+
const mime = res.headers.get('content-type') || ''
|
|
205
|
+
if (mime.startsWith('image/')) // naively assumes GET.200
|
|
206
|
+
renderPayloadImage(this.href)
|
|
207
|
+
else
|
|
208
|
+
updatePayloadViewer(await res.text() || Strings.empty_response_body, mime)
|
|
209
|
+
|
|
210
|
+
empty(refPayloadViewerFileTitle.current)
|
|
211
|
+
refPayloadViewerFileTitle.current.append(PayloadViewerTitle({
|
|
212
|
+
file: this.closest('tr').querySelector('select').value
|
|
213
|
+
}))
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
console.error(error)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
185
219
|
return (
|
|
186
220
|
r('a', {
|
|
187
221
|
className: CSS.PreviewLink,
|
|
188
222
|
href: urlMask,
|
|
189
223
|
'data-method': method,
|
|
190
|
-
|
|
191
|
-
event.preventDefault()
|
|
192
|
-
try {
|
|
193
|
-
const spinner = setTimeout(() => {
|
|
194
|
-
empty(refPayloadViewer.current)
|
|
195
|
-
refPayloadViewer.current.append(ProgressBar())
|
|
196
|
-
}, 180)
|
|
197
|
-
const res = await fetch(this.href, {
|
|
198
|
-
method: this.getAttribute('data-method')
|
|
199
|
-
})
|
|
200
|
-
document.querySelector(`.${CSS.PreviewLink}.${CSS.chosen}`)?.classList.remove(CSS.chosen)
|
|
201
|
-
this.classList.add(CSS.chosen)
|
|
202
|
-
clearTimeout(spinner)
|
|
203
|
-
const mime = res.headers.get('content-type') || ''
|
|
204
|
-
if (mime.startsWith('image/')) // naively assumes GET.200
|
|
205
|
-
renderPayloadImage(this.href)
|
|
206
|
-
else
|
|
207
|
-
updatePayloadViewer(await res.text() || Strings.empty_response_body, mime)
|
|
208
|
-
empty(refPayloadFile.current)
|
|
209
|
-
refPayloadFile.current.append(PayloadViewerTitle({
|
|
210
|
-
file: this.closest('tr').querySelector('select').value
|
|
211
|
-
}))
|
|
212
|
-
}
|
|
213
|
-
catch (error) {
|
|
214
|
-
console.error(error)
|
|
215
|
-
}
|
|
216
|
-
}
|
|
224
|
+
onClick
|
|
217
225
|
}, urlMask))
|
|
218
226
|
}
|
|
219
227
|
|
|
228
|
+
|
|
220
229
|
function PayloadViewerTitle({ file }) {
|
|
221
230
|
const { urlMask, method, status, ext } = parseFilename(file)
|
|
222
231
|
return (
|
|
@@ -226,12 +235,14 @@ function PayloadViewerTitle({ file }) {
|
|
|
226
235
|
'.' + ext))
|
|
227
236
|
}
|
|
228
237
|
|
|
238
|
+
|
|
229
239
|
function ProgressBar() {
|
|
230
240
|
return (
|
|
231
241
|
r('div', { className: CSS.ProgressBar },
|
|
232
242
|
r('div', { style: { animationDuration: '1000ms' } }))) // TODO from Config.delay - 180
|
|
233
243
|
}
|
|
234
244
|
|
|
245
|
+
|
|
235
246
|
function renderPayloadImage(href) {
|
|
236
247
|
empty(refPayloadViewer.current)
|
|
237
248
|
refPayloadViewer.current.append(r('img', { src: href }))
|
|
@@ -246,46 +257,55 @@ function updatePayloadViewer(body, mime) {
|
|
|
246
257
|
|
|
247
258
|
|
|
248
259
|
function MockSelector({ broker }) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
260
|
+
function onChange() {
|
|
261
|
+
const { status } = parseFilename(this.value)
|
|
262
|
+
this.style.fontWeight = this.value === this.options[0].value // default is selected
|
|
263
|
+
? 'normal'
|
|
264
|
+
: 'bold'
|
|
265
|
+
mockaton.select(this.value)
|
|
266
|
+
.then(() => {
|
|
267
|
+
this.closest('tr').querySelector('a').click()
|
|
268
|
+
this.closest('tr').querySelector(`.${CSS.InternalServerErrorToggler}>[type=checkbox]`).checked = status === 500
|
|
269
|
+
this.className = className(this.value === this.options[0].value, status)
|
|
270
|
+
})
|
|
271
|
+
.catch(console.error)
|
|
272
|
+
}
|
|
253
273
|
|
|
254
|
-
const items = broker.mocks
|
|
255
|
-
const selected = broker.currentMock.file
|
|
256
274
|
|
|
275
|
+
function className(defaultIsSelected, status) {
|
|
276
|
+
return cssClass(
|
|
277
|
+
CSS.MockSelector,
|
|
278
|
+
!defaultIsSelected && CSS.bold,
|
|
279
|
+
status >= 400 && status < 500 && CSS.status4xx)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const selected = broker.currentMock.file
|
|
257
283
|
const { status } = parseFilename(selected)
|
|
258
|
-
const files =
|
|
284
|
+
const files = broker.mocks.filter(item =>
|
|
259
285
|
status === 500 ||
|
|
260
286
|
!item.includes(DEFAULT_500_COMMENT))
|
|
261
287
|
|
|
262
288
|
return (
|
|
263
289
|
r('select', {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
:
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
this.closest('tr').querySelector('a').click()
|
|
275
|
-
this.closest('tr').querySelector(`.${CSS.InternalServerErrorToggler}>[type=checkbox]`).checked = status === 500
|
|
276
|
-
this.className = className(this.value === this.options[0].value, status)
|
|
277
|
-
})
|
|
278
|
-
.catch(console.error)
|
|
279
|
-
}
|
|
280
|
-
}, files.map(file => r('option', {
|
|
281
|
-
value: file,
|
|
282
|
-
selected: file === selected
|
|
283
|
-
}, file))))
|
|
290
|
+
autocomplete: 'off',
|
|
291
|
+
className: className(selected === files[0], status),
|
|
292
|
+
disabled: files.length <= 1,
|
|
293
|
+
onChange
|
|
294
|
+
},
|
|
295
|
+
files.map(file =>
|
|
296
|
+
r('option', {
|
|
297
|
+
value: file,
|
|
298
|
+
selected: file === selected
|
|
299
|
+
}, file))))
|
|
284
300
|
}
|
|
285
301
|
|
|
302
|
+
|
|
286
303
|
function DelayRouteToggler({ broker }) {
|
|
287
|
-
|
|
288
|
-
|
|
304
|
+
function onChange(event) {
|
|
305
|
+
const { method, urlMask } = parseFilename(this.name)
|
|
306
|
+
mockaton.setRouteIsDelayed(method, urlMask, event.currentTarget.checked)
|
|
307
|
+
.catch(console.error)
|
|
308
|
+
}
|
|
289
309
|
return (
|
|
290
310
|
r('label', {
|
|
291
311
|
className: CSS.DelayToggler,
|
|
@@ -293,28 +313,29 @@ function DelayRouteToggler({ broker }) {
|
|
|
293
313
|
},
|
|
294
314
|
r('input', {
|
|
295
315
|
type: 'checkbox',
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
onChange(event) {
|
|
300
|
-
const { method, urlMask } = parseFilename(this.name)
|
|
301
|
-
mockaton.setRouteIsDelayed(method, urlMask, event.currentTarget.checked)
|
|
302
|
-
.catch(console.error)
|
|
303
|
-
}
|
|
316
|
+
name: broker.currentMock.file,
|
|
317
|
+
checked: Boolean(broker.currentMock.delay),
|
|
318
|
+
onChange
|
|
304
319
|
}),
|
|
305
320
|
TimerIcon()))
|
|
306
321
|
}
|
|
307
322
|
|
|
323
|
+
|
|
308
324
|
function TimerIcon() {
|
|
309
325
|
return (
|
|
310
326
|
r('svg', { viewBox: '0 0 24 24' },
|
|
311
327
|
r('path', { d: 'M12 7H11v6l5 3.2.75-1.23-4.5-3z' })))
|
|
312
328
|
}
|
|
313
329
|
|
|
330
|
+
|
|
314
331
|
function InternalServerErrorToggler({ broker }) {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
332
|
+
function onChange(event) {
|
|
333
|
+
mockaton.select(event.currentTarget.checked
|
|
334
|
+
? broker.mocks.find(f => parseFilename(f).status === 500)
|
|
335
|
+
: broker.mocks[0])
|
|
336
|
+
.then(init)
|
|
337
|
+
.catch(console.error)
|
|
338
|
+
}
|
|
318
339
|
return (
|
|
319
340
|
r('label', {
|
|
320
341
|
className: CSS.InternalServerErrorToggler,
|
|
@@ -322,16 +343,9 @@ function InternalServerErrorToggler({ broker }) {
|
|
|
322
343
|
},
|
|
323
344
|
r('input', {
|
|
324
345
|
type: 'checkbox',
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
onChange(event) {
|
|
329
|
-
mockaton.select(event.currentTarget.checked
|
|
330
|
-
? items.find(f => parseFilename(f).status === 500)
|
|
331
|
-
: items[0])
|
|
332
|
-
.then(init)
|
|
333
|
-
.catch(console.error)
|
|
334
|
-
}
|
|
346
|
+
name: broker.currentMock.file,
|
|
347
|
+
checked: parseFilename(broker.currentMock.file).status === 500,
|
|
348
|
+
onChange
|
|
335
349
|
}),
|
|
336
350
|
r('span', null, '500')
|
|
337
351
|
)
|
package/src/Mockaton.js
CHANGED
|
@@ -38,7 +38,7 @@ async function onRequest(req, response) {
|
|
|
38
38
|
methods: Config.corsMethods,
|
|
39
39
|
maxAge: Config.corsMaxAge,
|
|
40
40
|
credentials: Config.corsCredentials,
|
|
41
|
-
exposedHeaders: Config.
|
|
41
|
+
exposedHeaders: Config.corsExposedHeaders
|
|
42
42
|
})
|
|
43
43
|
|
|
44
44
|
const { url, method } = req
|
package/src/utils/http-cors.js
CHANGED