mockaton 10.6.6 → 10.6.7
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/package.json +1 -1
- package/src/Api.js +1 -1
- package/src/ApiCommander.js +0 -9
- package/src/Dashboard.css +1 -1
- package/src/Dashboard.js +59 -147
- package/src/DashboardDom.js +1 -3
- package/src/DashboardStore.js +272 -62
package/package.json
CHANGED
package/src/Api.js
CHANGED
package/src/ApiCommander.js
CHANGED
|
@@ -4,25 +4,16 @@ import { API, DF, LONG_POLL_SERVER_TIMEOUT } from './ApiConstants.js'
|
|
|
4
4
|
/** Client for controlling Mockaton via its HTTP API */
|
|
5
5
|
export class Commander {
|
|
6
6
|
#addr = ''
|
|
7
|
-
#then = a => a
|
|
8
|
-
#catch = e => { throw e }
|
|
9
7
|
|
|
10
8
|
constructor(addr) {
|
|
11
9
|
this.#addr = addr
|
|
12
10
|
}
|
|
13
11
|
|
|
14
|
-
setupPatchCallbacks(_then = undefined, _catch = undefined) {
|
|
15
|
-
if (_then) this.#then = _then
|
|
16
|
-
if (_catch) this.#catch = _catch
|
|
17
|
-
}
|
|
18
|
-
|
|
19
12
|
#patch = (api, body) => {
|
|
20
13
|
return fetch(this.#addr + api, {
|
|
21
14
|
method: 'PATCH',
|
|
22
15
|
body: JSON.stringify(body)
|
|
23
16
|
})
|
|
24
|
-
.then(this.#then)
|
|
25
|
-
.catch(this.#catch)
|
|
26
17
|
}
|
|
27
18
|
|
|
28
19
|
/** @returns {JsonPromise<State>} */
|
package/src/Dashboard.css
CHANGED
package/src/Dashboard.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { createElement as r, createSvgElement as s, className, restoreFocus,
|
|
1
|
+
import { createElement as r, createSvgElement as s, className, restoreFocus, Defer, Fragment, useRef } from './DashboardDom.js'
|
|
2
2
|
import { AUTO_500_COMMENT, HEADER_FOR_502 } from './ApiConstants.js'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { store, dittoSplitPaths, BrokerRowModel } from './DashboardStore.js'
|
|
4
|
+
import { parseFilename } from './Filename.js'
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
const CSS = {
|
|
@@ -55,11 +55,10 @@ const FocusGroup = {
|
|
|
55
55
|
PreviewLink: 3
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
store.onError = onError
|
|
58
59
|
store.render = render
|
|
59
60
|
store.renderRow = renderRow
|
|
60
|
-
store.
|
|
61
|
-
store.fetchState().catch(onError)
|
|
62
|
-
|
|
61
|
+
store.fetchState()
|
|
63
62
|
initRealTimeUpdates()
|
|
64
63
|
initKeyboardNavigation()
|
|
65
64
|
|
|
@@ -136,13 +135,12 @@ function GlobalDelayField() {
|
|
|
136
135
|
}
|
|
137
136
|
|
|
138
137
|
function BulkSelector() {
|
|
138
|
+
// TODO For a11y, this should be a `menu` instead of this `select`
|
|
139
139
|
const { comments } = store
|
|
140
|
-
// UX wise this should be a menu instead of this `select`.
|
|
141
|
-
// But this way is easier to implement, with a few hacks.
|
|
142
140
|
const firstOption = t`Pick Comment…`
|
|
143
141
|
function onChange() {
|
|
144
142
|
const value = this.value
|
|
145
|
-
this.value = firstOption // Hack
|
|
143
|
+
this.value = firstOption // Hack
|
|
146
144
|
store.bulkSelectByComment(value)
|
|
147
145
|
}
|
|
148
146
|
const disabled = !comments.length
|
|
@@ -281,57 +279,44 @@ function MockList() {
|
|
|
281
279
|
r('tr', null,
|
|
282
280
|
r('th', { colspan: 2 + Number(store.canProxy) }),
|
|
283
281
|
r('th', null, method)),
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return rowsFor('*').map(Row)
|
|
287
|
-
}
|
|
282
|
+
store.brokersAsRowsByMethod(method).map(Row)))
|
|
288
283
|
|
|
289
|
-
|
|
290
|
-
const sorted = store.brokersByMethodAsArray(targetMethod)
|
|
291
|
-
const urlMasksDittoed = dittoSplitPaths(sorted.map(r => r.urlMask))
|
|
292
|
-
return sorted.map((r, i) => ({
|
|
293
|
-
...r,
|
|
294
|
-
urlMaskDittoed: urlMasksDittoed[i]
|
|
295
|
-
}))
|
|
284
|
+
return store.brokersAsRowsByMethod('*').map(Row)
|
|
296
285
|
}
|
|
297
286
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
287
|
+
/**
|
|
288
|
+
* @param {BrokerRowModel} row
|
|
289
|
+
* @param {number} i
|
|
290
|
+
*/
|
|
291
|
+
function Row(row, i) {
|
|
292
|
+
const { method, urlMask } = row
|
|
302
293
|
return (
|
|
303
|
-
r('tr', { key },
|
|
294
|
+
r('tr', { key: Row.key(method, urlMask) },
|
|
304
295
|
store.canProxy && r('td', null,
|
|
305
|
-
ProxyToggler(method, urlMask, proxied)),
|
|
296
|
+
ProxyToggler(method, urlMask, row.proxied)),
|
|
306
297
|
|
|
307
298
|
r('td', null,
|
|
308
|
-
DelayRouteToggler(method, urlMask, delayed)),
|
|
299
|
+
DelayRouteToggler(method, urlMask, row.delayed)),
|
|
309
300
|
|
|
310
301
|
r('td', null,
|
|
311
|
-
InternalServerErrorToggler(method, urlMask,
|
|
302
|
+
InternalServerErrorToggler(method, urlMask, !row.proxied && row.selectedFileIs500)),
|
|
312
303
|
|
|
313
304
|
!store.groupByMethod && r('td', className(CSS.Method),
|
|
314
305
|
method),
|
|
315
306
|
|
|
316
307
|
r('td', null,
|
|
317
|
-
PreviewLink(method, urlMask, urlMaskDittoed, i === 0)),
|
|
308
|
+
PreviewLink(method, urlMask, row.urlMaskDittoed, i === 0)),
|
|
318
309
|
|
|
319
310
|
r('td', null,
|
|
320
|
-
MockSelector(
|
|
311
|
+
MockSelector(row))))
|
|
321
312
|
}
|
|
322
313
|
Row.key = (method, urlMask) => method + '::' + urlMask
|
|
323
|
-
Row.ditto = new Map()
|
|
324
314
|
|
|
325
315
|
function renderRow(method, urlMask) {
|
|
326
316
|
restoreFocus(() => {
|
|
327
317
|
unChooseOld()
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
method,
|
|
331
|
-
urlMask,
|
|
332
|
-
urlMaskDittoed: Row.ditto.get(key),
|
|
333
|
-
broker: store.brokerFor(method, urlMask)
|
|
334
|
-
}))
|
|
318
|
+
trFor(Row.key(method, urlMask))
|
|
319
|
+
.replaceWith(Row(store.brokerAsRow(method, urlMask)))
|
|
335
320
|
previewMock(method, urlMask)
|
|
336
321
|
})
|
|
337
322
|
|
|
@@ -365,91 +350,21 @@ function PreviewLink(method, urlMask, urlMaskDittoed, autofocus) {
|
|
|
365
350
|
}
|
|
366
351
|
|
|
367
352
|
|
|
368
|
-
/** @param {
|
|
369
|
-
function MockSelector(
|
|
370
|
-
let selected = broker.currentMock.file
|
|
371
|
-
const selectedStatus = parseFilename(selected).status
|
|
372
|
-
|
|
373
|
-
const files = baseOptionsFor(broker.mocks, selectedStatus === 500)
|
|
374
|
-
if (store.canProxy && broker.currentMock.proxied) {
|
|
375
|
-
selected = t`Proxied`
|
|
376
|
-
files.push([selected, selected])
|
|
377
|
-
}
|
|
378
|
-
|
|
353
|
+
/** @param {BrokerRowModel} row */
|
|
354
|
+
function MockSelector(row) {
|
|
379
355
|
return (
|
|
380
356
|
r('select', {
|
|
381
357
|
onChange() { store.selectFile(this.value) },
|
|
382
358
|
autocomplete: 'off',
|
|
383
359
|
'aria-label': t`Mock Selector`,
|
|
384
|
-
disabled:
|
|
360
|
+
disabled: row.opts.length < 2,
|
|
385
361
|
...className(
|
|
386
362
|
CSS.MockSelector,
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
},
|
|
390
|
-
r('option', {
|
|
391
|
-
value: file,
|
|
392
|
-
selected: file === selected
|
|
393
|
-
}, name)))))
|
|
394
|
-
}
|
|
395
|
-
function baseOptionsFor(mocks, selectedIs500) {
|
|
396
|
-
return mocks
|
|
397
|
-
.filter(f => selectedIs500 || !f.includes(AUTO_500_COMMENT))
|
|
398
|
-
.map(f => [f, nameFor(f)])
|
|
399
|
-
|
|
400
|
-
function nameFor(file) {
|
|
401
|
-
const { status, ext } = parseFilename(file)
|
|
402
|
-
const comments = extractComments(file)
|
|
403
|
-
const isAutogen500 = comments.includes(AUTO_500_COMMENT)
|
|
404
|
-
return [
|
|
405
|
-
isAutogen500 ? '' : status,
|
|
406
|
-
ext === 'empty' || ext === 'unknown' ? '' : ext,
|
|
407
|
-
isAutogen500 ? t`Auto500` : comments.join(' ')
|
|
408
|
-
].filter(Boolean).join(' ')
|
|
409
|
-
}
|
|
363
|
+
row.selectedIdx > 0 && CSS.nonDefault,
|
|
364
|
+
row.selectedFileIs4xx && CSS.status4xx)
|
|
365
|
+
}, row.opts.map(([value, label, selected]) =>
|
|
366
|
+
r('option', { value, selected }, label))))
|
|
410
367
|
}
|
|
411
|
-
baseOptionsFor.test = function () {
|
|
412
|
-
(function ignoresAutoGen500WhenUnselected() {
|
|
413
|
-
const files = baseOptionsFor([
|
|
414
|
-
`api/user${AUTO_500_COMMENT}.GET.500.empty`
|
|
415
|
-
], false)
|
|
416
|
-
console.assert(files.length === 0)
|
|
417
|
-
}());
|
|
418
|
-
|
|
419
|
-
(function keepsNonAutoGen500WhenUnselected() {
|
|
420
|
-
const files = baseOptionsFor([
|
|
421
|
-
`api/user.GET.500.txt`
|
|
422
|
-
], false)
|
|
423
|
-
console.assert(files.length === 1)
|
|
424
|
-
console.assert(files[0][1] === t`500 txt`)
|
|
425
|
-
}());
|
|
426
|
-
|
|
427
|
-
(function renamesAutoGenFileToAuto500() {
|
|
428
|
-
const files = baseOptionsFor([
|
|
429
|
-
`api/user${AUTO_500_COMMENT}.GET.500.empty`
|
|
430
|
-
], true)
|
|
431
|
-
console.assert(files.length === 1)
|
|
432
|
-
console.assert(files[0][1] === t`Auto500`)
|
|
433
|
-
}());
|
|
434
|
-
|
|
435
|
-
(function filenameHasExtensionExceptWhenEmptyOrUnknown() {
|
|
436
|
-
const files = baseOptionsFor([
|
|
437
|
-
`api/user0.GET.200.empty`,
|
|
438
|
-
`api/user1.GET.200.unknown`,
|
|
439
|
-
`api/user2.GET.200.json`,
|
|
440
|
-
`api/user3(another json).GET.200.json`,
|
|
441
|
-
], true)
|
|
442
|
-
console.assert(deepEqual(files.map(([, n]) => n), [
|
|
443
|
-
// Think about, in cases like this, the only option the user
|
|
444
|
-
// has for discerning empty and unknown is on the Previewer Title
|
|
445
|
-
'200',
|
|
446
|
-
'200',
|
|
447
|
-
'200 json',
|
|
448
|
-
'200 json (another json)',
|
|
449
|
-
]))
|
|
450
|
-
}())
|
|
451
|
-
}
|
|
452
|
-
deferred(baseOptionsFor.test)
|
|
453
368
|
|
|
454
369
|
|
|
455
370
|
function DelayRouteToggler(method, urlMask, checked) {
|
|
@@ -498,6 +413,7 @@ function StaticFilesList() {
|
|
|
498
413
|
const { staticBrokers, canProxy, groupByMethod } = store
|
|
499
414
|
if (!Object.keys(staticBrokers).length)
|
|
500
415
|
return null
|
|
416
|
+
|
|
501
417
|
const dp = dittoSplitPaths(Object.keys(staticBrokers)).map(([ditto, tail]) => ditto
|
|
502
418
|
? [r('span', className(CSS.dittoDir), ditto), tail]
|
|
503
419
|
: tail)
|
|
@@ -670,6 +586,7 @@ function PayloadViewerTitleWhenProxied({ mime, status, statusText, gatewayIsBad
|
|
|
670
586
|
' ' + mime))
|
|
671
587
|
}
|
|
672
588
|
|
|
589
|
+
// TODO indeterminate when there's store.delayJitter
|
|
673
590
|
const SPINNER_DELAY = 80
|
|
674
591
|
function PayloadViewerProgressBar() {
|
|
675
592
|
return (
|
|
@@ -696,8 +613,7 @@ async function previewMock(method, urlMask) {
|
|
|
696
613
|
if (proxied || file)
|
|
697
614
|
await updatePayloadViewer(proxied, file, response)
|
|
698
615
|
}
|
|
699
|
-
catch
|
|
700
|
-
onError(err)
|
|
616
|
+
catch {
|
|
701
617
|
payloadViewerCodeRef.current.replaceChildren()
|
|
702
618
|
}
|
|
703
619
|
}
|
|
@@ -717,7 +633,7 @@ async function updatePayloadViewer(proxied, file, response) {
|
|
|
717
633
|
file,
|
|
718
634
|
statusText: response.statusText
|
|
719
635
|
}))
|
|
720
|
-
|
|
636
|
+
|
|
721
637
|
if (mime.startsWith('image/')) // Naively assumes GET.200
|
|
722
638
|
payloadViewerCodeRef.current.replaceChildren(
|
|
723
639
|
r('img', { src: URL.createObjectURL(await response.blob()) }))
|
|
@@ -725,7 +641,7 @@ async function updatePayloadViewer(proxied, file, response) {
|
|
|
725
641
|
const body = await response.text() || t`/* Empty Response Body */`
|
|
726
642
|
if (mime === 'application/json')
|
|
727
643
|
payloadViewerCodeRef.current.replaceChildren(r('span', className(CSS.json), SyntaxJSON(body)))
|
|
728
|
-
else if (isXML(mime))
|
|
644
|
+
else if (isXML(mime))
|
|
729
645
|
payloadViewerCodeRef.current.replaceChildren(SyntaxXML(body))
|
|
730
646
|
else
|
|
731
647
|
payloadViewerCodeRef.current.textContent = body
|
|
@@ -738,24 +654,25 @@ function isXML(mime) {
|
|
|
738
654
|
}
|
|
739
655
|
|
|
740
656
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
async function parseError(response) {
|
|
744
|
-
if (response.ok)
|
|
745
|
-
return response
|
|
746
|
-
if (response.status === 422)
|
|
747
|
-
throw await response.text()
|
|
748
|
-
throw response.statusText
|
|
749
|
-
}
|
|
657
|
+
async function onError(_error) {
|
|
658
|
+
let error = _error
|
|
750
659
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
660
|
+
if (_error instanceof Response) {
|
|
661
|
+
if (_error.status === 422)
|
|
662
|
+
error = await _error.text()
|
|
663
|
+
else if (_error.statusText)
|
|
664
|
+
error = _error.statusText
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
if (error?.name === 'AbortError')
|
|
668
|
+
return
|
|
669
|
+
if (error?.message === 'Failed to fetch')
|
|
670
|
+
error = t`Looks like the Mockaton server is not running` // TODO clear Error if comes back in ui-sync
|
|
671
|
+
else
|
|
672
|
+
error = error || t`Unexpected Error`
|
|
673
|
+
}
|
|
674
|
+
showErrorToast(error)
|
|
675
|
+
console.error(_error)
|
|
759
676
|
}
|
|
760
677
|
|
|
761
678
|
function showErrorToast(msg) {
|
|
@@ -801,17 +718,17 @@ function initRealTimeUpdates() {
|
|
|
801
718
|
let oldSyncVersion = -1
|
|
802
719
|
let controller = new AbortController()
|
|
803
720
|
|
|
804
|
-
|
|
721
|
+
longPoll()
|
|
805
722
|
document.addEventListener('visibilitychange', () => {
|
|
806
723
|
if (document.hidden) {
|
|
807
724
|
controller.abort('_hidden_tab_')
|
|
808
725
|
controller = new AbortController()
|
|
809
726
|
}
|
|
810
727
|
else
|
|
811
|
-
|
|
728
|
+
longPoll()
|
|
812
729
|
})
|
|
813
730
|
|
|
814
|
-
async function
|
|
731
|
+
async function longPoll() {
|
|
815
732
|
try {
|
|
816
733
|
const response = await store.getSyncVersion(oldSyncVersion, controller.signal)
|
|
817
734
|
if (response.ok) {
|
|
@@ -820,16 +737,16 @@ function initRealTimeUpdates() {
|
|
|
820
737
|
if (oldSyncVersion !== syncVersion) { // because it could be < or >
|
|
821
738
|
oldSyncVersion = syncVersion
|
|
822
739
|
if (!skipUpdate)
|
|
823
|
-
store.fetchState()
|
|
740
|
+
store.fetchState()
|
|
824
741
|
}
|
|
825
|
-
|
|
742
|
+
longPoll()
|
|
826
743
|
}
|
|
827
744
|
else
|
|
828
745
|
throw response.status
|
|
829
746
|
}
|
|
830
747
|
catch (error) {
|
|
831
748
|
if (error !== '_hidden_tab_')
|
|
832
|
-
setTimeout(
|
|
749
|
+
setTimeout(longPoll, 3000)
|
|
833
750
|
}
|
|
834
751
|
}
|
|
835
752
|
}
|
|
@@ -969,8 +886,3 @@ function SyntaxXML(xml) {
|
|
|
969
886
|
SyntaxXML.regex = /(<\/?|\/?>|\?>)|(?<=<\??\/?)([A-Za-z_:][\w:.-]*)|([A-Za-z_:][\w:.-]*)(?==)|("(?:[^"\\]|\\.)*")/g
|
|
970
887
|
// Capture groups order: [tagPunc, tagName, attrName, attrVal]
|
|
971
888
|
|
|
972
|
-
|
|
973
|
-
function deepEqual(a, b) {
|
|
974
|
-
return JSON.stringify(a) === JSON.stringify(b)
|
|
975
|
-
}
|
|
976
|
-
|
package/src/DashboardDom.js
CHANGED
package/src/DashboardStore.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { deferred } from './DashboardDom.js'
|
|
2
2
|
import { Commander } from './ApiCommander.js'
|
|
3
|
-
import { parseFilename } from './Filename.js'
|
|
3
|
+
import { parseFilename, extractComments } from './Filename.js'
|
|
4
|
+
import { AUTO_500_COMMENT } from './ApiConstants.js'
|
|
4
5
|
|
|
5
6
|
|
|
7
|
+
const t = translation => translation[0]
|
|
6
8
|
const mockaton = new Commander(location.origin)
|
|
7
9
|
|
|
8
10
|
export const store = {
|
|
9
|
-
|
|
10
|
-
mockaton.setupPatchCallbacks(_then, _catch)
|
|
11
|
-
},
|
|
12
|
-
|
|
11
|
+
onError(err) {},
|
|
13
12
|
render() {},
|
|
14
13
|
renderRow(method, urlMask) {},
|
|
15
14
|
|
|
@@ -31,20 +30,18 @@ export const store = {
|
|
|
31
30
|
|
|
32
31
|
getSyncVersion: mockaton.getSyncVersion,
|
|
33
32
|
|
|
34
|
-
fetchState() {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
})
|
|
43
|
-
})
|
|
33
|
+
async fetchState() {
|
|
34
|
+
try {
|
|
35
|
+
const response = await mockaton.getState()
|
|
36
|
+
if (!response.ok) throw response
|
|
37
|
+
Object.assign(store, await response.json())
|
|
38
|
+
store.render()
|
|
39
|
+
}
|
|
40
|
+
catch (error) { store.onError(error) }
|
|
44
41
|
},
|
|
45
42
|
|
|
46
43
|
|
|
47
|
-
leftSideWidth:
|
|
44
|
+
leftSideWidth: undefined,
|
|
48
45
|
|
|
49
46
|
groupByMethod: initPreference('groupByMethod'),
|
|
50
47
|
toggleGroupByMethod() {
|
|
@@ -54,7 +51,6 @@ export const store = {
|
|
|
54
51
|
},
|
|
55
52
|
|
|
56
53
|
|
|
57
|
-
|
|
58
54
|
chosenLink: {
|
|
59
55
|
method: '',
|
|
60
56
|
urlMask: ''
|
|
@@ -67,37 +63,60 @@ export const store = {
|
|
|
67
63
|
store.chosenLink = { method, urlMask }
|
|
68
64
|
},
|
|
69
65
|
|
|
70
|
-
reset() {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
66
|
+
async reset() {
|
|
67
|
+
try {
|
|
68
|
+
const response = await mockaton.reset()
|
|
69
|
+
if (!response.ok) throw response
|
|
70
|
+
store.setChosenLink('', '')
|
|
71
|
+
await store.fetchState()
|
|
72
|
+
}
|
|
73
|
+
catch (error) { store.onError(error) }
|
|
74
74
|
},
|
|
75
75
|
|
|
76
|
-
bulkSelectByComment(value) {
|
|
77
|
-
|
|
78
|
-
.
|
|
76
|
+
async bulkSelectByComment(value) {
|
|
77
|
+
try {
|
|
78
|
+
const response = await mockaton.bulkSelectByComment(value)
|
|
79
|
+
if (!response.ok) throw response
|
|
80
|
+
await store.fetchState()
|
|
81
|
+
}
|
|
82
|
+
catch (error) { store.onError(error) }
|
|
79
83
|
},
|
|
80
84
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
async setGlobalDelay(value) {
|
|
86
|
+
try {
|
|
87
|
+
const response = await mockaton.setGlobalDelay(value)
|
|
88
|
+
if (!response.ok) throw response
|
|
89
|
+
store.delay = value
|
|
90
|
+
}
|
|
91
|
+
catch (error) { store.onError(error) }
|
|
85
92
|
},
|
|
86
93
|
|
|
87
|
-
selectCookie(name) {
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
async selectCookie(name) {
|
|
95
|
+
try {
|
|
96
|
+
const response = await mockaton.selectCookie(name)
|
|
97
|
+
if (!response.ok) throw response
|
|
98
|
+
store.cookies = await response.json()
|
|
99
|
+
}
|
|
100
|
+
catch (error) { store.onError(error) }
|
|
90
101
|
},
|
|
91
102
|
|
|
92
|
-
setProxyFallback(value) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
103
|
+
async setProxyFallback(value) {
|
|
104
|
+
try {
|
|
105
|
+
const response = await mockaton.setProxyFallback(value)
|
|
106
|
+
if (!response.ok) throw response
|
|
107
|
+
store.proxyFallback = value
|
|
108
|
+
store.render()
|
|
109
|
+
}
|
|
110
|
+
catch (error) { store.onError(error) }
|
|
96
111
|
},
|
|
97
112
|
|
|
98
|
-
setCollectProxied(checked) {
|
|
99
|
-
|
|
100
|
-
|
|
113
|
+
async setCollectProxied(checked) {
|
|
114
|
+
try {
|
|
115
|
+
const response = await mockaton.setCollectProxied(checked)
|
|
116
|
+
if (!response.ok) throw response
|
|
117
|
+
store.collectProxied = checked
|
|
118
|
+
}
|
|
119
|
+
catch (error) { store.onError(error) }
|
|
101
120
|
},
|
|
102
121
|
|
|
103
122
|
|
|
@@ -105,62 +124,103 @@ export const store = {
|
|
|
105
124
|
return store.brokersByMethod[method]?.[urlMask]
|
|
106
125
|
},
|
|
107
126
|
|
|
108
|
-
|
|
127
|
+
_dittoCache: new Map(),
|
|
128
|
+
|
|
129
|
+
dittoedUrlFor(method, urlMask) {
|
|
130
|
+
return store._dittoCache.get(method + '::' + urlMask)
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
brokersAsRowsByMethod(method) {
|
|
134
|
+
const rows = store._brokersAsArray(method)
|
|
135
|
+
const urlMasksDittoed = dittoSplitPaths(rows.map(r => r.urlMask))
|
|
136
|
+
for (let i = 0; i < rows.length; i++) {
|
|
137
|
+
const r = rows[i]
|
|
138
|
+
r.setUrlMaskDittoed(urlMasksDittoed[i])
|
|
139
|
+
store._dittoCache.set(r.method + '::' + r.urlMask, r.urlMaskDittoed)
|
|
140
|
+
}
|
|
141
|
+
return rows
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
_brokersAsArray(byMethod = '*') {
|
|
109
145
|
const rows = []
|
|
110
146
|
for (const [method, brokers] of Object.entries(store.brokersByMethod))
|
|
111
|
-
if (
|
|
112
|
-
for (const
|
|
113
|
-
rows.push(
|
|
147
|
+
if (byMethod === '*' || byMethod === method)
|
|
148
|
+
for (const broker of Object.values(brokers))
|
|
149
|
+
rows.push(new BrokerRowModel(broker, store.canProxy))
|
|
114
150
|
return rows.sort((a, b) => a.urlMask.localeCompare(b.urlMask))
|
|
115
151
|
},
|
|
116
152
|
|
|
153
|
+
brokerAsRow(method, urlMask) {
|
|
154
|
+
const row = new BrokerRowModel(store.brokerFor(method, urlMask), store.canProxy)
|
|
155
|
+
row.setUrlMaskDittoed(store.dittoedUrlFor(method, urlMask))
|
|
156
|
+
return row
|
|
157
|
+
},
|
|
158
|
+
|
|
117
159
|
previewLink(method, urlMask) {
|
|
118
160
|
store.setChosenLink(method, urlMask)
|
|
119
161
|
store.renderRow(method, urlMask)
|
|
120
162
|
},
|
|
121
163
|
|
|
122
|
-
selectFile(file) {
|
|
123
|
-
|
|
164
|
+
async selectFile(file) {
|
|
165
|
+
try {
|
|
166
|
+
const response = await mockaton.select(file)
|
|
167
|
+
if (!response.ok) throw response
|
|
124
168
|
const { method, urlMask } = parseFilename(file)
|
|
125
169
|
store.brokerFor(method, urlMask).currentMock = await response.json()
|
|
126
170
|
store.setChosenLink(method, urlMask)
|
|
127
171
|
store.renderRow(method, urlMask)
|
|
128
|
-
}
|
|
172
|
+
}
|
|
173
|
+
catch (error) { store.onError(error) }
|
|
129
174
|
},
|
|
130
175
|
|
|
131
|
-
toggle500(method, urlMask) {
|
|
132
|
-
|
|
176
|
+
async toggle500(method, urlMask) {
|
|
177
|
+
try {
|
|
178
|
+
const response = await mockaton.toggle500(method, urlMask)
|
|
179
|
+
if (!response.ok) throw response
|
|
133
180
|
store.brokerFor(method, urlMask).currentMock = await response.json()
|
|
134
181
|
store.setChosenLink(method, urlMask)
|
|
135
182
|
store.renderRow(method, urlMask)
|
|
136
|
-
}
|
|
183
|
+
}
|
|
184
|
+
catch (error) { store.onError(error) }
|
|
137
185
|
},
|
|
138
186
|
|
|
139
|
-
setProxied(method, urlMask, checked) {
|
|
140
|
-
|
|
187
|
+
async setProxied(method, urlMask, checked) {
|
|
188
|
+
try {
|
|
189
|
+
const response = await mockaton.setRouteIsProxied(method, urlMask, checked)
|
|
190
|
+
if (!response.ok) throw response
|
|
141
191
|
store.brokerFor(method, urlMask).currentMock.proxied = checked
|
|
142
192
|
store.setChosenLink(method, urlMask)
|
|
143
193
|
store.renderRow(method, urlMask)
|
|
144
|
-
}
|
|
194
|
+
}
|
|
195
|
+
catch (error) { store.onError(error) }
|
|
145
196
|
},
|
|
146
197
|
|
|
147
|
-
setDelayed(method, urlMask, checked) {
|
|
148
|
-
|
|
198
|
+
async setDelayed(method, urlMask, checked) {
|
|
199
|
+
try {
|
|
200
|
+
const response = await mockaton.setRouteIsDelayed(method, urlMask, checked)
|
|
201
|
+
if (!response.ok) throw response
|
|
149
202
|
store.brokerFor(method, urlMask).currentMock.delayed = checked
|
|
150
|
-
}
|
|
203
|
+
}
|
|
204
|
+
catch (error) { store.onError(error) }
|
|
151
205
|
},
|
|
152
206
|
|
|
153
207
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
208
|
+
async setDelayedStatic(route, checked) {
|
|
209
|
+
try {
|
|
210
|
+
const response = await mockaton.setStaticRouteIsDelayed(route, checked)
|
|
211
|
+
if (!response.ok) throw response
|
|
212
|
+
store.staticBrokers[route].delayed = checked
|
|
213
|
+
}
|
|
214
|
+
catch (error) { store.onError(error) }
|
|
159
215
|
},
|
|
160
216
|
|
|
161
|
-
setStaticRouteStatus(route, status) {
|
|
162
|
-
|
|
163
|
-
|
|
217
|
+
async setStaticRouteStatus(route, status) {
|
|
218
|
+
try {
|
|
219
|
+
const response = await mockaton.setStaticRouteStatus(route, status)
|
|
220
|
+
if (!response.ok) throw response
|
|
221
|
+
store.staticBrokers[route].status = status
|
|
222
|
+
}
|
|
223
|
+
catch (error) { store.onError(error) }
|
|
164
224
|
}
|
|
165
225
|
}
|
|
166
226
|
|
|
@@ -251,6 +311,156 @@ dittoSplitPaths.test = function () {
|
|
|
251
311
|
}
|
|
252
312
|
deferred(dittoSplitPaths.test)
|
|
253
313
|
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
export class BrokerRowModel {
|
|
317
|
+
opts = /** @type {[key:string, label:string, selected:boolean][]} */ []
|
|
318
|
+
method = ''
|
|
319
|
+
urlMask = ''
|
|
320
|
+
urlMaskDittoed = ['', '']
|
|
321
|
+
#broker = /** @type {ClientMockBroker} */ {}
|
|
322
|
+
#canProxy = false
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* @param {ClientMockBroker} broker
|
|
326
|
+
* @param {boolean} canProxy
|
|
327
|
+
*/
|
|
328
|
+
constructor(broker, canProxy) {
|
|
329
|
+
this.#broker = broker
|
|
330
|
+
this.#canProxy = canProxy
|
|
331
|
+
const { method, urlMask } = parseFilename(broker.currentMock.file)
|
|
332
|
+
this.method = method
|
|
333
|
+
this.urlMask = urlMask
|
|
334
|
+
this.opts = this.#makeOptions()
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
setUrlMaskDittoed(urlMaskDittoed) {
|
|
338
|
+
this.urlMaskDittoed = urlMaskDittoed
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
get delayed() {
|
|
342
|
+
return this.#broker.currentMock.delayed
|
|
343
|
+
}
|
|
344
|
+
get proxied() {
|
|
345
|
+
return this.#canProxy && this.#broker.currentMock.proxied
|
|
346
|
+
}
|
|
347
|
+
get selectedIdx() {
|
|
348
|
+
return this.opts.findIndex(([, , selected]) => selected)
|
|
349
|
+
}
|
|
350
|
+
get selectedFile() {
|
|
351
|
+
return this.#broker.currentMock.file
|
|
352
|
+
}
|
|
353
|
+
get selectedFileIs4xx() {
|
|
354
|
+
const { status } = parseFilename(this.selectedFile)
|
|
355
|
+
return status >= 400 && status < 500
|
|
356
|
+
}
|
|
357
|
+
get selectedFileIs500() {
|
|
358
|
+
const { status } = parseFilename(this.selectedFile)
|
|
359
|
+
return status === 500
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
#makeOptions() {
|
|
363
|
+
const proxied = this.proxied
|
|
364
|
+
const selectedIs500 = this.selectedFileIs500
|
|
365
|
+
|
|
366
|
+
const opts = this.#broker.mocks
|
|
367
|
+
.filter(f => selectedIs500 || !f.includes(AUTO_500_COMMENT))
|
|
368
|
+
.map(f => [
|
|
369
|
+
f,
|
|
370
|
+
this.#optionLabelFor(f),
|
|
371
|
+
!proxied && f === this.selectedFile
|
|
372
|
+
])
|
|
373
|
+
|
|
374
|
+
if (proxied)
|
|
375
|
+
opts.push([
|
|
376
|
+
'__PROXIED__',
|
|
377
|
+
t`Proxied`,
|
|
378
|
+
true
|
|
379
|
+
])
|
|
380
|
+
|
|
381
|
+
return opts
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
#optionLabelFor(file) {
|
|
385
|
+
const { status, ext } = parseFilename(file)
|
|
386
|
+
const comments = extractComments(file)
|
|
387
|
+
const isAutogen500 = comments.includes(AUTO_500_COMMENT)
|
|
388
|
+
return [
|
|
389
|
+
isAutogen500 ? '' : status,
|
|
390
|
+
ext === 'empty' || ext === 'unknown' ? '' : ext,
|
|
391
|
+
isAutogen500 ? t`Auto500` : comments.join(' ')
|
|
392
|
+
].filter(Boolean).join(' ')
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const TestBrokerRowModelOptions = {
|
|
397
|
+
'ignores autogen500 when unselected'() {
|
|
398
|
+
const broker = {
|
|
399
|
+
currentMock: { file: 'api/other' },
|
|
400
|
+
mocks: [`api/user${AUTO_500_COMMENT}.GET.500.empty`]
|
|
401
|
+
}
|
|
402
|
+
const row = new BrokerRowModel(broker, false)
|
|
403
|
+
console.assert(row.opts.length === 0)
|
|
404
|
+
},
|
|
405
|
+
|
|
406
|
+
'keeps non-autogen500 when unselected'() {
|
|
407
|
+
const broker = {
|
|
408
|
+
currentMock: { file: 'api/other' },
|
|
409
|
+
mocks: [`api/user.GET.500.txt`]
|
|
410
|
+
}
|
|
411
|
+
const row = new BrokerRowModel(broker, false)
|
|
412
|
+
console.assert(row.opts.length === 1)
|
|
413
|
+
console.assert(row.opts[0][1] === t`500 txt`)
|
|
414
|
+
},
|
|
415
|
+
|
|
416
|
+
'renames autogen file to Auto500'() {
|
|
417
|
+
const broker = {
|
|
418
|
+
currentMock: { file: `api/user${AUTO_500_COMMENT}.GET.500.empty` },
|
|
419
|
+
mocks: [`api/user${AUTO_500_COMMENT}.GET.500.empty`]
|
|
420
|
+
}
|
|
421
|
+
const row = new BrokerRowModel(broker, false)
|
|
422
|
+
console.assert(row.opts.length === 1)
|
|
423
|
+
console.assert(row.opts[0][1] === t`Auto500`)
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
'filename has extension except when empty or unknown'() {
|
|
427
|
+
const broker = {
|
|
428
|
+
currentMock: { file: `api/other` },
|
|
429
|
+
mocks: [
|
|
430
|
+
`api/user0.GET.200.empty`,
|
|
431
|
+
`api/user1.GET.200.unknown`,
|
|
432
|
+
`api/user2.GET.200.json`,
|
|
433
|
+
`api/user3(another json).GET.200.json`,
|
|
434
|
+
]
|
|
435
|
+
}
|
|
436
|
+
const row = new BrokerRowModel(broker, false)
|
|
437
|
+
// Think about, in cases like this, the only option the user has
|
|
438
|
+
// for discerning empty and unknown is on the Previewer Title
|
|
439
|
+
console.assert(deepEqual(row.opts.map(([, n]) => n), [
|
|
440
|
+
'200',
|
|
441
|
+
'200',
|
|
442
|
+
'200 json',
|
|
443
|
+
'200 json (another json)',
|
|
444
|
+
]))
|
|
445
|
+
},
|
|
446
|
+
|
|
447
|
+
'appends "Proxied" label iff current is proxied'() {
|
|
448
|
+
const broker = {
|
|
449
|
+
currentMock: {
|
|
450
|
+
file: 'api/foo',
|
|
451
|
+
proxied: true
|
|
452
|
+
},
|
|
453
|
+
mocks: [`api/foo.GET.200.json`]
|
|
454
|
+
}
|
|
455
|
+
const row = new BrokerRowModel(broker, true)
|
|
456
|
+
console.assert(deepEqual(row.opts.map(([, n, selected]) => [n, selected]), [
|
|
457
|
+
['200 json', false],
|
|
458
|
+
[t`Proxied`, true]
|
|
459
|
+
]))
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
deferred(() => Object.values(TestBrokerRowModelOptions).forEach(t => t()))
|
|
463
|
+
|
|
254
464
|
function deepEqual(a, b) {
|
|
255
465
|
return JSON.stringify(a) === JSON.stringify(b)
|
|
256
466
|
}
|