mockaton 10.6.6 → 10.6.8
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 +4 -2
- package/src/Dashboard.js +63 -150
- package/src/DashboardDom.js +1 -3
- package/src/DashboardStore.js +277 -70
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
|
@@ -78,7 +78,7 @@ body {
|
|
|
78
78
|
|
|
79
79
|
select, a, input, button {
|
|
80
80
|
cursor: pointer;
|
|
81
|
-
|
|
81
|
+
|
|
82
82
|
&:focus-visible {
|
|
83
83
|
outline: 2px solid var(--colorAccent);
|
|
84
84
|
}
|
|
@@ -325,7 +325,7 @@ main {
|
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
.leftSide {
|
|
328
|
-
|
|
328
|
+
width: 50%; /* resizable in js */
|
|
329
329
|
padding: 16px;
|
|
330
330
|
border-right: 1px solid var(--colorSecondaryActionBorder);
|
|
331
331
|
user-select: none;
|
|
@@ -628,6 +628,8 @@ table {
|
|
|
628
628
|
background: var(--colorAccent);
|
|
629
629
|
animation-name: _kfProgress;
|
|
630
630
|
animation-timing-function: linear;
|
|
631
|
+
animation-iteration-count: infinite;
|
|
632
|
+
animation-direction: alternate;
|
|
631
633
|
/* duration in JavaScript */
|
|
632
634
|
}
|
|
633
635
|
}
|
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,18 +55,17 @@ 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
|
|
|
66
65
|
function render() {
|
|
67
66
|
restoreFocus(() => document.body.replaceChildren(...App()))
|
|
68
67
|
if (store.hasChosenLink)
|
|
69
|
-
previewMock(
|
|
68
|
+
previewMock()
|
|
70
69
|
}
|
|
71
70
|
|
|
72
71
|
const t = translation => translation[0]
|
|
@@ -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,58 +279,45 @@ 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
|
-
|
|
331
|
-
urlMask,
|
|
332
|
-
urlMaskDittoed: Row.ditto.get(key),
|
|
333
|
-
broker: store.brokerFor(method, urlMask)
|
|
334
|
-
}))
|
|
335
|
-
previewMock(method, urlMask)
|
|
318
|
+
trFor(Row.key(method, urlMask))
|
|
319
|
+
.replaceWith(Row(store.brokerAsRow(method, urlMask)))
|
|
320
|
+
previewMock()
|
|
336
321
|
})
|
|
337
322
|
|
|
338
323
|
function trFor(key) {
|
|
@@ -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)
|
|
@@ -677,7 +593,9 @@ function PayloadViewerProgressBar() {
|
|
|
677
593
|
r('div', { style: { animationDuration: store.delay - SPINNER_DELAY + 'ms' } })))
|
|
678
594
|
}
|
|
679
595
|
|
|
680
|
-
async function previewMock(
|
|
596
|
+
async function previewMock() {
|
|
597
|
+
const { method, urlMask } = store.chosenLink
|
|
598
|
+
|
|
681
599
|
previewMock.controller?.abort()
|
|
682
600
|
previewMock.controller = new AbortController
|
|
683
601
|
|
|
@@ -696,8 +614,7 @@ async function previewMock(method, urlMask) {
|
|
|
696
614
|
if (proxied || file)
|
|
697
615
|
await updatePayloadViewer(proxied, file, response)
|
|
698
616
|
}
|
|
699
|
-
catch
|
|
700
|
-
onError(err)
|
|
617
|
+
catch {
|
|
701
618
|
payloadViewerCodeRef.current.replaceChildren()
|
|
702
619
|
}
|
|
703
620
|
}
|
|
@@ -717,7 +634,7 @@ async function updatePayloadViewer(proxied, file, response) {
|
|
|
717
634
|
file,
|
|
718
635
|
statusText: response.statusText
|
|
719
636
|
}))
|
|
720
|
-
|
|
637
|
+
|
|
721
638
|
if (mime.startsWith('image/')) // Naively assumes GET.200
|
|
722
639
|
payloadViewerCodeRef.current.replaceChildren(
|
|
723
640
|
r('img', { src: URL.createObjectURL(await response.blob()) }))
|
|
@@ -725,7 +642,7 @@ async function updatePayloadViewer(proxied, file, response) {
|
|
|
725
642
|
const body = await response.text() || t`/* Empty Response Body */`
|
|
726
643
|
if (mime === 'application/json')
|
|
727
644
|
payloadViewerCodeRef.current.replaceChildren(r('span', className(CSS.json), SyntaxJSON(body)))
|
|
728
|
-
else if (isXML(mime))
|
|
645
|
+
else if (isXML(mime))
|
|
729
646
|
payloadViewerCodeRef.current.replaceChildren(SyntaxXML(body))
|
|
730
647
|
else
|
|
731
648
|
payloadViewerCodeRef.current.textContent = body
|
|
@@ -738,24 +655,25 @@ function isXML(mime) {
|
|
|
738
655
|
}
|
|
739
656
|
|
|
740
657
|
|
|
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
|
-
}
|
|
658
|
+
async function onError(_error) {
|
|
659
|
+
let error = _error
|
|
750
660
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
661
|
+
if (_error instanceof Response) {
|
|
662
|
+
if (_error.status === 422)
|
|
663
|
+
error = await _error.text()
|
|
664
|
+
else if (_error.statusText)
|
|
665
|
+
error = _error.statusText
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
if (error?.name === 'AbortError')
|
|
669
|
+
return
|
|
670
|
+
if (error?.message === 'Failed to fetch')
|
|
671
|
+
error = t`Looks like the Mockaton server is not running` // TODO clear Error if comes back in ui-sync
|
|
672
|
+
else
|
|
673
|
+
error = error || t`Unexpected Error`
|
|
674
|
+
}
|
|
675
|
+
showErrorToast(error)
|
|
676
|
+
console.error(_error)
|
|
759
677
|
}
|
|
760
678
|
|
|
761
679
|
function showErrorToast(msg) {
|
|
@@ -801,17 +719,17 @@ function initRealTimeUpdates() {
|
|
|
801
719
|
let oldSyncVersion = -1
|
|
802
720
|
let controller = new AbortController()
|
|
803
721
|
|
|
804
|
-
|
|
722
|
+
longPoll()
|
|
805
723
|
document.addEventListener('visibilitychange', () => {
|
|
806
724
|
if (document.hidden) {
|
|
807
725
|
controller.abort('_hidden_tab_')
|
|
808
726
|
controller = new AbortController()
|
|
809
727
|
}
|
|
810
728
|
else
|
|
811
|
-
|
|
729
|
+
longPoll()
|
|
812
730
|
})
|
|
813
731
|
|
|
814
|
-
async function
|
|
732
|
+
async function longPoll() {
|
|
815
733
|
try {
|
|
816
734
|
const response = await store.getSyncVersion(oldSyncVersion, controller.signal)
|
|
817
735
|
if (response.ok) {
|
|
@@ -820,16 +738,16 @@ function initRealTimeUpdates() {
|
|
|
820
738
|
if (oldSyncVersion !== syncVersion) { // because it could be < or >
|
|
821
739
|
oldSyncVersion = syncVersion
|
|
822
740
|
if (!skipUpdate)
|
|
823
|
-
store.fetchState()
|
|
741
|
+
store.fetchState()
|
|
824
742
|
}
|
|
825
|
-
|
|
743
|
+
longPoll()
|
|
826
744
|
}
|
|
827
745
|
else
|
|
828
746
|
throw response.status
|
|
829
747
|
}
|
|
830
748
|
catch (error) {
|
|
831
749
|
if (error !== '_hidden_tab_')
|
|
832
|
-
setTimeout(
|
|
750
|
+
setTimeout(longPoll, 3000)
|
|
833
751
|
}
|
|
834
752
|
}
|
|
835
753
|
}
|
|
@@ -969,8 +887,3 @@ function SyntaxXML(xml) {
|
|
|
969
887
|
SyntaxXML.regex = /(<\/?|\/?>|\?>)|(?<=<\??\/?)([A-Za-z_:][\w:.-]*)|([A-Za-z_:][\w:.-]*)(?==)|("(?:[^"\\]|\\.)*")/g
|
|
970
888
|
// Capture groups order: [tagPunc, tagName, attrName, attrVal]
|
|
971
889
|
|
|
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,23 +1,19 @@
|
|
|
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
|
|
|
16
|
-
/** @type {State.brokersByMethod} */
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/** @type {State.staticBrokers} */
|
|
20
|
-
staticBrokers: {},
|
|
15
|
+
brokersByMethod: /** @type {State.brokersByMethod} */ {},
|
|
16
|
+
staticBrokers: /** @type {State.staticBrokers} */ {},
|
|
21
17
|
|
|
22
18
|
cookies: [],
|
|
23
19
|
comments: [],
|
|
@@ -31,20 +27,18 @@ export const store = {
|
|
|
31
27
|
|
|
32
28
|
getSyncVersion: mockaton.getSyncVersion,
|
|
33
29
|
|
|
34
|
-
fetchState() {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
})
|
|
43
|
-
})
|
|
30
|
+
async fetchState() {
|
|
31
|
+
try {
|
|
32
|
+
const response = await mockaton.getState()
|
|
33
|
+
if (!response.ok) throw response
|
|
34
|
+
Object.assign(store, await response.json())
|
|
35
|
+
store.render()
|
|
36
|
+
}
|
|
37
|
+
catch (error) { store.onError(error) }
|
|
44
38
|
},
|
|
45
39
|
|
|
46
40
|
|
|
47
|
-
leftSideWidth:
|
|
41
|
+
leftSideWidth: undefined,
|
|
48
42
|
|
|
49
43
|
groupByMethod: initPreference('groupByMethod'),
|
|
50
44
|
toggleGroupByMethod() {
|
|
@@ -54,7 +48,6 @@ export const store = {
|
|
|
54
48
|
},
|
|
55
49
|
|
|
56
50
|
|
|
57
|
-
|
|
58
51
|
chosenLink: {
|
|
59
52
|
method: '',
|
|
60
53
|
urlMask: ''
|
|
@@ -66,38 +59,63 @@ export const store = {
|
|
|
66
59
|
setChosenLink(method, urlMask) {
|
|
67
60
|
store.chosenLink = { method, urlMask }
|
|
68
61
|
},
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async reset() {
|
|
65
|
+
try {
|
|
66
|
+
const response = await mockaton.reset()
|
|
67
|
+
if (!response.ok) throw response
|
|
68
|
+
store.setChosenLink('', '')
|
|
69
|
+
await store.fetchState()
|
|
70
|
+
}
|
|
71
|
+
catch (error) { store.onError(error) }
|
|
74
72
|
},
|
|
75
73
|
|
|
76
|
-
bulkSelectByComment(value) {
|
|
77
|
-
|
|
78
|
-
.
|
|
74
|
+
async bulkSelectByComment(value) {
|
|
75
|
+
try {
|
|
76
|
+
const response = await mockaton.bulkSelectByComment(value)
|
|
77
|
+
if (!response.ok) throw response
|
|
78
|
+
await store.fetchState()
|
|
79
|
+
}
|
|
80
|
+
catch (error) { store.onError(error) }
|
|
79
81
|
},
|
|
80
82
|
|
|
81
|
-
|
|
82
|
-
setGlobalDelay(value) {
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
|
|
84
|
+
async setGlobalDelay(value) {
|
|
85
|
+
try {
|
|
86
|
+
const response = await mockaton.setGlobalDelay(value)
|
|
87
|
+
if (!response.ok) throw response
|
|
88
|
+
store.delay = value
|
|
89
|
+
}
|
|
90
|
+
catch (error) { store.onError(error) }
|
|
85
91
|
},
|
|
86
92
|
|
|
87
|
-
selectCookie(name) {
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
async selectCookie(name) {
|
|
94
|
+
try {
|
|
95
|
+
const response = await mockaton.selectCookie(name)
|
|
96
|
+
if (!response.ok) throw response
|
|
97
|
+
store.cookies = await response.json()
|
|
98
|
+
}
|
|
99
|
+
catch (error) { store.onError(error) }
|
|
90
100
|
},
|
|
91
101
|
|
|
92
|
-
setProxyFallback(value) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
102
|
+
async setProxyFallback(value) {
|
|
103
|
+
try {
|
|
104
|
+
const response = await mockaton.setProxyFallback(value)
|
|
105
|
+
if (!response.ok) throw response
|
|
106
|
+
store.proxyFallback = value
|
|
107
|
+
store.render()
|
|
108
|
+
}
|
|
109
|
+
catch (error) { store.onError(error) }
|
|
96
110
|
},
|
|
97
111
|
|
|
98
|
-
setCollectProxied(checked) {
|
|
99
|
-
|
|
100
|
-
|
|
112
|
+
async setCollectProxied(checked) {
|
|
113
|
+
try {
|
|
114
|
+
const response = await mockaton.setCollectProxied(checked)
|
|
115
|
+
if (!response.ok) throw response
|
|
116
|
+
store.collectProxied = checked
|
|
117
|
+
}
|
|
118
|
+
catch (error) { store.onError(error) }
|
|
101
119
|
},
|
|
102
120
|
|
|
103
121
|
|
|
@@ -105,62 +123,101 @@ export const store = {
|
|
|
105
123
|
return store.brokersByMethod[method]?.[urlMask]
|
|
106
124
|
},
|
|
107
125
|
|
|
108
|
-
|
|
109
|
-
|
|
126
|
+
_dittoCache: new Map(),
|
|
127
|
+
|
|
128
|
+
brokersAsRowsByMethod(method) {
|
|
129
|
+
const rows = store._brokersAsArray(method)
|
|
130
|
+
.map(b => new BrokerRowModel(b, store.canProxy))
|
|
131
|
+
.sort((a, b) => a.urlMask.localeCompare(b.urlMask))
|
|
132
|
+
const urlMasksDittoed = dittoSplitPaths(rows.map(r => r.urlMask))
|
|
133
|
+
rows.forEach((r, i) => {
|
|
134
|
+
store._dittoCache.set(r.method + '::' + r.urlMask, urlMasksDittoed[i])
|
|
135
|
+
r.setUrlMaskDittoed(urlMasksDittoed[i])
|
|
136
|
+
})
|
|
137
|
+
return rows
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
brokerAsRow(method, urlMask) {
|
|
141
|
+
const r = new BrokerRowModel(store.brokerFor(method, urlMask), store.canProxy)
|
|
142
|
+
r.setUrlMaskDittoed(store._dittoCache.get(r.method + '::' + r.urlMask))
|
|
143
|
+
return r
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
_brokersAsArray(byMethod = '*') {
|
|
147
|
+
const arr = []
|
|
110
148
|
for (const [method, brokers] of Object.entries(store.brokersByMethod))
|
|
111
|
-
if (
|
|
112
|
-
for (const
|
|
113
|
-
|
|
114
|
-
return
|
|
149
|
+
if (byMethod === '*' || byMethod === method)
|
|
150
|
+
for (const broker of Object.values(brokers))
|
|
151
|
+
arr.push(broker)
|
|
152
|
+
return arr
|
|
115
153
|
},
|
|
154
|
+
|
|
116
155
|
|
|
117
156
|
previewLink(method, urlMask) {
|
|
118
157
|
store.setChosenLink(method, urlMask)
|
|
119
158
|
store.renderRow(method, urlMask)
|
|
120
159
|
},
|
|
121
160
|
|
|
122
|
-
selectFile(file) {
|
|
123
|
-
|
|
161
|
+
async selectFile(file) {
|
|
162
|
+
try {
|
|
163
|
+
const response = await mockaton.select(file)
|
|
164
|
+
if (!response.ok) throw response
|
|
124
165
|
const { method, urlMask } = parseFilename(file)
|
|
125
166
|
store.brokerFor(method, urlMask).currentMock = await response.json()
|
|
126
167
|
store.setChosenLink(method, urlMask)
|
|
127
168
|
store.renderRow(method, urlMask)
|
|
128
|
-
}
|
|
169
|
+
}
|
|
170
|
+
catch (error) { store.onError(error) }
|
|
129
171
|
},
|
|
130
172
|
|
|
131
|
-
toggle500(method, urlMask) {
|
|
132
|
-
|
|
173
|
+
async toggle500(method, urlMask) {
|
|
174
|
+
try {
|
|
175
|
+
const response = await mockaton.toggle500(method, urlMask)
|
|
176
|
+
if (!response.ok) throw response
|
|
133
177
|
store.brokerFor(method, urlMask).currentMock = await response.json()
|
|
134
178
|
store.setChosenLink(method, urlMask)
|
|
135
179
|
store.renderRow(method, urlMask)
|
|
136
|
-
}
|
|
180
|
+
}
|
|
181
|
+
catch (error) { store.onError(error) }
|
|
137
182
|
},
|
|
138
183
|
|
|
139
|
-
setProxied(method, urlMask, checked) {
|
|
140
|
-
|
|
184
|
+
async setProxied(method, urlMask, checked) {
|
|
185
|
+
try {
|
|
186
|
+
const response = await mockaton.setRouteIsProxied(method, urlMask, checked)
|
|
187
|
+
if (!response.ok) throw response
|
|
141
188
|
store.brokerFor(method, urlMask).currentMock.proxied = checked
|
|
142
189
|
store.setChosenLink(method, urlMask)
|
|
143
190
|
store.renderRow(method, urlMask)
|
|
144
|
-
}
|
|
191
|
+
}
|
|
192
|
+
catch (error) { store.onError(error) }
|
|
145
193
|
},
|
|
146
194
|
|
|
147
|
-
setDelayed(method, urlMask, checked) {
|
|
148
|
-
|
|
195
|
+
async setDelayed(method, urlMask, checked) {
|
|
196
|
+
try {
|
|
197
|
+
const response = await mockaton.setRouteIsDelayed(method, urlMask, checked)
|
|
198
|
+
if (!response.ok) throw response
|
|
149
199
|
store.brokerFor(method, urlMask).currentMock.delayed = checked
|
|
150
|
-
}
|
|
200
|
+
}
|
|
201
|
+
catch (error) { store.onError(error) }
|
|
151
202
|
},
|
|
152
203
|
|
|
153
204
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
205
|
+
async setDelayedStatic(route, checked) {
|
|
206
|
+
try {
|
|
207
|
+
const response = await mockaton.setStaticRouteIsDelayed(route, checked)
|
|
208
|
+
if (!response.ok) throw response
|
|
209
|
+
store.staticBrokers[route].delayed = checked
|
|
210
|
+
}
|
|
211
|
+
catch (error) { store.onError(error) }
|
|
159
212
|
},
|
|
160
213
|
|
|
161
|
-
setStaticRouteStatus(route, status) {
|
|
162
|
-
|
|
163
|
-
|
|
214
|
+
async setStaticRouteStatus(route, status) {
|
|
215
|
+
try {
|
|
216
|
+
const response = await mockaton.setStaticRouteStatus(route, status)
|
|
217
|
+
if (!response.ok) throw response
|
|
218
|
+
store.staticBrokers[route].status = status
|
|
219
|
+
}
|
|
220
|
+
catch (error) { store.onError(error) }
|
|
164
221
|
}
|
|
165
222
|
}
|
|
166
223
|
|
|
@@ -251,6 +308,156 @@ dittoSplitPaths.test = function () {
|
|
|
251
308
|
}
|
|
252
309
|
deferred(dittoSplitPaths.test)
|
|
253
310
|
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
export class BrokerRowModel {
|
|
314
|
+
opts = /** @type {[key:string, label:string, selected:boolean][]} */ []
|
|
315
|
+
method = ''
|
|
316
|
+
urlMask = ''
|
|
317
|
+
urlMaskDittoed = ['', '']
|
|
318
|
+
#broker = /** @type {ClientMockBroker} */ {}
|
|
319
|
+
#canProxy = false
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* @param {ClientMockBroker} broker
|
|
323
|
+
* @param {boolean} canProxy
|
|
324
|
+
*/
|
|
325
|
+
constructor(broker, canProxy) {
|
|
326
|
+
this.#broker = broker
|
|
327
|
+
this.#canProxy = canProxy
|
|
328
|
+
const { method, urlMask } = parseFilename(broker.currentMock.file)
|
|
329
|
+
this.method = method
|
|
330
|
+
this.urlMask = urlMask
|
|
331
|
+
this.opts = this.#makeOptions()
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
setUrlMaskDittoed(urlMaskDittoed) {
|
|
335
|
+
this.urlMaskDittoed = urlMaskDittoed
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
get delayed() {
|
|
339
|
+
return this.#broker.currentMock.delayed
|
|
340
|
+
}
|
|
341
|
+
get proxied() {
|
|
342
|
+
return this.#canProxy && this.#broker.currentMock.proxied
|
|
343
|
+
}
|
|
344
|
+
get selectedIdx() {
|
|
345
|
+
return this.opts.findIndex(([, , selected]) => selected)
|
|
346
|
+
}
|
|
347
|
+
get selectedFile() {
|
|
348
|
+
return this.#broker.currentMock.file
|
|
349
|
+
}
|
|
350
|
+
get selectedFileIs4xx() {
|
|
351
|
+
const { status } = parseFilename(this.selectedFile)
|
|
352
|
+
return status >= 400 && status < 500
|
|
353
|
+
}
|
|
354
|
+
get selectedFileIs500() {
|
|
355
|
+
const { status } = parseFilename(this.selectedFile)
|
|
356
|
+
return status === 500
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
#makeOptions() {
|
|
360
|
+
const proxied = this.proxied
|
|
361
|
+
const selectedIs500 = this.selectedFileIs500
|
|
362
|
+
|
|
363
|
+
const opts = this.#broker.mocks
|
|
364
|
+
.filter(f => selectedIs500 || !f.includes(AUTO_500_COMMENT))
|
|
365
|
+
.map(f => [
|
|
366
|
+
f,
|
|
367
|
+
this.#optionLabelFor(f),
|
|
368
|
+
!proxied && f === this.selectedFile
|
|
369
|
+
])
|
|
370
|
+
|
|
371
|
+
if (proxied)
|
|
372
|
+
opts.push([
|
|
373
|
+
'__PROXIED__',
|
|
374
|
+
t`Proxied`,
|
|
375
|
+
true
|
|
376
|
+
])
|
|
377
|
+
|
|
378
|
+
return opts
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
#optionLabelFor(file) {
|
|
382
|
+
const { status, ext } = parseFilename(file)
|
|
383
|
+
const comments = extractComments(file)
|
|
384
|
+
const isAutogen500 = comments.includes(AUTO_500_COMMENT)
|
|
385
|
+
return [
|
|
386
|
+
isAutogen500 ? '' : status,
|
|
387
|
+
ext === 'empty' || ext === 'unknown' ? '' : ext,
|
|
388
|
+
isAutogen500 ? t`Auto500` : comments.join(' ')
|
|
389
|
+
].filter(Boolean).join(' ')
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const TestBrokerRowModelOptions = {
|
|
394
|
+
'ignores autogen500 when unselected'() {
|
|
395
|
+
const broker = {
|
|
396
|
+
currentMock: { file: 'api/other' },
|
|
397
|
+
mocks: [`api/user${AUTO_500_COMMENT}.GET.500.empty`]
|
|
398
|
+
}
|
|
399
|
+
const row = new BrokerRowModel(broker, false)
|
|
400
|
+
console.assert(row.opts.length === 0)
|
|
401
|
+
},
|
|
402
|
+
|
|
403
|
+
'keeps non-autogen500 when unselected'() {
|
|
404
|
+
const broker = {
|
|
405
|
+
currentMock: { file: 'api/other' },
|
|
406
|
+
mocks: [`api/user.GET.500.txt`]
|
|
407
|
+
}
|
|
408
|
+
const row = new BrokerRowModel(broker, false)
|
|
409
|
+
console.assert(row.opts.length === 1)
|
|
410
|
+
console.assert(row.opts[0][1] === t`500 txt`)
|
|
411
|
+
},
|
|
412
|
+
|
|
413
|
+
'renames autogen file to Auto500'() {
|
|
414
|
+
const broker = {
|
|
415
|
+
currentMock: { file: `api/user${AUTO_500_COMMENT}.GET.500.empty` },
|
|
416
|
+
mocks: [`api/user${AUTO_500_COMMENT}.GET.500.empty`]
|
|
417
|
+
}
|
|
418
|
+
const row = new BrokerRowModel(broker, false)
|
|
419
|
+
console.assert(row.opts.length === 1)
|
|
420
|
+
console.assert(row.opts[0][1] === t`Auto500`)
|
|
421
|
+
},
|
|
422
|
+
|
|
423
|
+
'filename has extension except when empty or unknown'() {
|
|
424
|
+
const broker = {
|
|
425
|
+
currentMock: { file: `api/other` },
|
|
426
|
+
mocks: [
|
|
427
|
+
`api/user0.GET.200.empty`,
|
|
428
|
+
`api/user1.GET.200.unknown`,
|
|
429
|
+
`api/user2.GET.200.json`,
|
|
430
|
+
`api/user3(another json).GET.200.json`,
|
|
431
|
+
]
|
|
432
|
+
}
|
|
433
|
+
const row = new BrokerRowModel(broker, false)
|
|
434
|
+
// Think about, in cases like this, the only option the user has
|
|
435
|
+
// for discerning empty and unknown is on the Previewer Title
|
|
436
|
+
console.assert(deepEqual(row.opts.map(([, n]) => n), [
|
|
437
|
+
'200',
|
|
438
|
+
'200',
|
|
439
|
+
'200 json',
|
|
440
|
+
'200 json (another json)',
|
|
441
|
+
]))
|
|
442
|
+
},
|
|
443
|
+
|
|
444
|
+
'appends "Proxied" label iff current is proxied'() {
|
|
445
|
+
const broker = {
|
|
446
|
+
currentMock: {
|
|
447
|
+
file: 'api/foo',
|
|
448
|
+
proxied: true
|
|
449
|
+
},
|
|
450
|
+
mocks: [`api/foo.GET.200.json`]
|
|
451
|
+
}
|
|
452
|
+
const row = new BrokerRowModel(broker, true)
|
|
453
|
+
console.assert(deepEqual(row.opts.map(([, n, selected]) => [n, selected]), [
|
|
454
|
+
['200 json', false],
|
|
455
|
+
[t`Proxied`, true]
|
|
456
|
+
]))
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
deferred(() => Object.values(TestBrokerRowModelOptions).forEach(t => t()))
|
|
460
|
+
|
|
254
461
|
function deepEqual(a, b) {
|
|
255
462
|
return JSON.stringify(a) === JSON.stringify(b)
|
|
256
463
|
}
|