mockaton 10.6.3 → 10.6.5
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/index.d.ts +1 -0
- package/package.json +1 -1
- package/src/Api.js +2 -4
- package/src/ApiCommander.js +9 -2
- package/src/Dashboard.css +1 -2
- package/src/Dashboard.js +325 -290
- package/src/MockBroker.js +9 -14
- package/src/MockDispatcher.js +10 -6
- package/src/mockBrokersCollection.js +0 -7
package/index.d.ts
CHANGED
package/package.json
CHANGED
package/src/Api.js
CHANGED
|
@@ -117,7 +117,7 @@ async function selectMock(req, response) {
|
|
|
117
117
|
sendUnprocessableContent(response, `Missing Mock: ${file}`)
|
|
118
118
|
else {
|
|
119
119
|
broker.selectFile(file)
|
|
120
|
-
|
|
120
|
+
sendJSON(response, broker.currentMock)
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -130,7 +130,7 @@ async function toggle500(req, response) {
|
|
|
130
130
|
sendUnprocessableContent(response, `Route does not exist: ${body[DF.routeMethod]} ${body[DF.routeUrlMask]}`)
|
|
131
131
|
else {
|
|
132
132
|
broker.toggle500()
|
|
133
|
-
|
|
133
|
+
sendJSON(response, broker.currentMock)
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
@@ -176,8 +176,6 @@ async function updateProxyFallback(req, response) {
|
|
|
176
176
|
sendUnprocessableContent(response, `Invalid Proxy Fallback URL`)
|
|
177
177
|
return
|
|
178
178
|
}
|
|
179
|
-
if (!fallback)
|
|
180
|
-
mockBrokersCollection.ensureAllRoutesHaveSelectedMock()
|
|
181
179
|
config.proxyFallback = fallback
|
|
182
180
|
sendOK(response)
|
|
183
181
|
}
|
package/src/ApiCommander.js
CHANGED
|
@@ -4,8 +4,13 @@ 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
|
-
|
|
7
|
+
#then = a => a
|
|
8
|
+
#catch = e => { throw e }
|
|
9
|
+
|
|
10
|
+
constructor(addr, _then = undefined, _catch = undefined) {
|
|
8
11
|
this.#addr = addr
|
|
12
|
+
if (_then) this.#then = _then
|
|
13
|
+
if (_catch) this.#catch = _catch
|
|
9
14
|
}
|
|
10
15
|
|
|
11
16
|
#patch(api, body) {
|
|
@@ -13,6 +18,8 @@ export class Commander {
|
|
|
13
18
|
method: 'PATCH',
|
|
14
19
|
body: JSON.stringify(body)
|
|
15
20
|
})
|
|
21
|
+
.then(this.#then)
|
|
22
|
+
.catch(this.#catch)
|
|
16
23
|
}
|
|
17
24
|
|
|
18
25
|
/** @returns {JsonPromise<State>} */
|
|
@@ -42,7 +49,7 @@ export class Commander {
|
|
|
42
49
|
toggle500(routeMethod, routeUrlMask) {
|
|
43
50
|
return this.#patch(API.toggle500, {
|
|
44
51
|
[DF.routeMethod]: routeMethod,
|
|
45
|
-
[DF.routeUrlMask]: routeUrlMask
|
|
52
|
+
[DF.routeUrlMask]: routeUrlMask
|
|
46
53
|
})
|
|
47
54
|
}
|
|
48
55
|
|
package/src/Dashboard.css
CHANGED
package/src/Dashboard.js
CHANGED
|
@@ -56,7 +56,9 @@ const FocusGroup = {
|
|
|
56
56
|
PreviewLink: 3
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
const
|
|
59
|
+
const mockaton = new Commander(location.origin, parseError, onError)
|
|
60
|
+
|
|
61
|
+
const store = /** @type {State} */ {
|
|
60
62
|
brokersByMethod: {},
|
|
61
63
|
staticBrokers: {},
|
|
62
64
|
cookies: [],
|
|
@@ -66,57 +68,145 @@ const state = /** @type {State} */ {
|
|
|
66
68
|
collectProxied: false,
|
|
67
69
|
proxyFallback: '',
|
|
68
70
|
get canProxy() {
|
|
69
|
-
return Boolean(
|
|
70
|
-
},
|
|
71
|
-
|
|
72
|
-
fileFor(method, urlMask) {
|
|
73
|
-
return state.brokersByMethod[method]?.[urlMask]?.currentMock.file
|
|
71
|
+
return Boolean(store.proxyFallback)
|
|
74
72
|
},
|
|
75
73
|
|
|
76
74
|
leftSideWidth: window.innerWidth / 2,
|
|
77
75
|
|
|
78
76
|
groupByMethod: initPreference('groupByMethod'),
|
|
79
77
|
toggleGroupByMethod() {
|
|
80
|
-
|
|
81
|
-
togglePreference('groupByMethod',
|
|
82
|
-
|
|
78
|
+
store.groupByMethod = !store.groupByMethod
|
|
79
|
+
togglePreference('groupByMethod', store.groupByMethod)
|
|
80
|
+
render()
|
|
83
81
|
},
|
|
84
82
|
|
|
85
|
-
chosenLink: {
|
|
83
|
+
chosenLink: {
|
|
84
|
+
method: '',
|
|
85
|
+
urlMask: ''
|
|
86
|
+
},
|
|
87
|
+
get hasChosenLink() {
|
|
88
|
+
return store.chosenLink.method
|
|
89
|
+
&& store.chosenLink.urlMask
|
|
90
|
+
},
|
|
86
91
|
setChosenLink(method, urlMask) {
|
|
87
|
-
|
|
92
|
+
store.chosenLink = { method, urlMask }
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
reset() {
|
|
96
|
+
store.setChosenLink('', '')
|
|
97
|
+
mockaton.reset()
|
|
98
|
+
.then(fetchState)
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
bulkSelectByComment(value) {
|
|
102
|
+
mockaton.bulkSelectByComment(value)
|
|
103
|
+
.then(fetchState)
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
setGlobalDelay(value) {
|
|
108
|
+
store.delay = value
|
|
109
|
+
mockaton.setGlobalDelay(value)
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
selectCookie(name) {
|
|
113
|
+
store.cookies = store.cookies.map(([n]) => [n, n === name])
|
|
114
|
+
mockaton.selectCookie(name)
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
setProxyFallback(value) {
|
|
118
|
+
store.proxyFallback = value
|
|
119
|
+
mockaton.setProxyFallback(value)
|
|
120
|
+
.then(render)
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
setCollectProxied(checked) {
|
|
124
|
+
store.collectProxied = checked
|
|
125
|
+
mockaton.setCollectProxied(checked)
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
brokerFor(method, urlMask) { return store.brokersByMethod[method]?.[urlMask] },
|
|
129
|
+
staticBrokerFor(route) { return store.staticBrokers[route] },
|
|
130
|
+
|
|
131
|
+
previewLink(method, urlMask) {
|
|
132
|
+
store.setChosenLink(method, urlMask)
|
|
133
|
+
renderRow(method, urlMask)
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
selectFile(file) {
|
|
137
|
+
mockaton.select(file).then(async response => {
|
|
138
|
+
const { method, urlMask } = parseFilename(file)
|
|
139
|
+
store.brokerFor(method, urlMask).currentMock = await response.json()
|
|
140
|
+
store.setChosenLink(method, urlMask)
|
|
141
|
+
renderRow(method, urlMask)
|
|
142
|
+
})
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
toggle500(method, urlMask) {
|
|
146
|
+
mockaton.toggle500(method, urlMask).then(async response => {
|
|
147
|
+
store.brokerFor(method, urlMask).currentMock = await response.json()
|
|
148
|
+
store.setChosenLink(method, urlMask)
|
|
149
|
+
renderRow(method, urlMask)
|
|
150
|
+
})
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
toggleProxied(method, urlMask, checked) {
|
|
154
|
+
mockaton.setRouteIsProxied(method, urlMask, checked).then(() => {
|
|
155
|
+
store.brokerFor(method, urlMask).currentMock.proxied = checked
|
|
156
|
+
store.setChosenLink(method, urlMask)
|
|
157
|
+
renderRow(method, urlMask)
|
|
158
|
+
})
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
setDelayed(method, urlMask, checked) {
|
|
162
|
+
mockaton.setRouteIsDelayed(method, urlMask, checked).then(() => {
|
|
163
|
+
store.brokerFor(method, urlMask).currentMock.delayed = checked
|
|
164
|
+
})
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
setDelayedStatic(route, checked) {
|
|
169
|
+
store.staticBrokerFor(route).delayed = checked
|
|
170
|
+
mockaton.setStaticRouteIsDelayed(route, checked)
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
setStaticRouteStatus(route, status) {
|
|
174
|
+
store.staticBrokerFor(route).status = status
|
|
175
|
+
mockaton.setStaticRouteStatus(route, status)
|
|
88
176
|
}
|
|
89
177
|
}
|
|
90
178
|
|
|
91
179
|
|
|
92
|
-
|
|
93
|
-
updateState()
|
|
180
|
+
fetchState()
|
|
94
181
|
initRealTimeUpdates()
|
|
95
182
|
initKeyboardNavigation()
|
|
96
183
|
|
|
97
|
-
async function
|
|
184
|
+
async function fetchState() {
|
|
98
185
|
try {
|
|
99
186
|
const response = await mockaton.getState()
|
|
100
187
|
if (!response.ok)
|
|
101
188
|
throw response.status
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const focusedElem = selectorFor(document.activeElement)
|
|
106
|
-
document.body.replaceChildren(...App())
|
|
107
|
-
if (focusedElem)
|
|
108
|
-
document.querySelector(focusedElem)?.focus()
|
|
109
|
-
|
|
110
|
-
const { method, urlMask } = state.chosenLink
|
|
111
|
-
if (method && urlMask)
|
|
112
|
-
await previewMock(method, urlMask)
|
|
113
|
-
|
|
189
|
+
Object.assign(store, await response.json())
|
|
190
|
+
render()
|
|
114
191
|
}
|
|
115
192
|
catch (error) {
|
|
116
193
|
onError(error)
|
|
117
194
|
}
|
|
118
195
|
}
|
|
119
196
|
|
|
197
|
+
function render() {
|
|
198
|
+
restoreFocus(() => document.body.replaceChildren(...App()))
|
|
199
|
+
if (store.hasChosenLink)
|
|
200
|
+
previewMock(store.chosenLink.method, store.chosenLink.urlMask)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function restoreFocus(cb) {
|
|
204
|
+
const focusQuery = selectorFor(document.activeElement)
|
|
205
|
+
cb()
|
|
206
|
+
if (focusQuery)
|
|
207
|
+
document.querySelector(focusQuery)?.focus()
|
|
208
|
+
}
|
|
209
|
+
|
|
120
210
|
const r = createElement
|
|
121
211
|
const s = createSvgElement
|
|
122
212
|
const t = translation => translation[0]
|
|
@@ -124,7 +214,7 @@ const t = translation => translation[0]
|
|
|
124
214
|
const leftSideRef = useRef()
|
|
125
215
|
|
|
126
216
|
function App() {
|
|
127
|
-
const { leftSideWidth } =
|
|
217
|
+
const { leftSideWidth } = store
|
|
128
218
|
return [
|
|
129
219
|
Header(),
|
|
130
220
|
r('main', null,
|
|
@@ -161,84 +251,41 @@ function Header() {
|
|
|
161
251
|
SettingsMenuTrigger())))
|
|
162
252
|
}
|
|
163
253
|
|
|
164
|
-
function
|
|
165
|
-
const id = '_settings_menu_'
|
|
166
|
-
return (
|
|
167
|
-
r('button', {
|
|
168
|
-
title: t`Settings`,
|
|
169
|
-
popovertarget: id,
|
|
170
|
-
className: CSS.MenuTrigger
|
|
171
|
-
},
|
|
172
|
-
SettingsIcon(),
|
|
173
|
-
Defer(() => SettingsMenu(id))))
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function SettingsMenu(id) {
|
|
177
|
-
const { groupByMethod, toggleGroupByMethod } = state
|
|
178
|
-
|
|
179
|
-
const firstInputRef = useRef()
|
|
180
|
-
function onToggle(event) {
|
|
181
|
-
if (event.newState === 'open')
|
|
182
|
-
firstInputRef.current.focus()
|
|
183
|
-
}
|
|
184
|
-
return (
|
|
185
|
-
r('menu', {
|
|
186
|
-
id,
|
|
187
|
-
popover: '',
|
|
188
|
-
className: CSS.SettingsMenu,
|
|
189
|
-
onToggle
|
|
190
|
-
},
|
|
191
|
-
|
|
192
|
-
r('label', className(CSS.GroupByMethod),
|
|
193
|
-
r('input', {
|
|
194
|
-
ref: firstInputRef,
|
|
195
|
-
type: 'checkbox',
|
|
196
|
-
checked: groupByMethod,
|
|
197
|
-
onChange: toggleGroupByMethod
|
|
198
|
-
}),
|
|
199
|
-
r('span', null, t`Group by Method`)),
|
|
200
|
-
|
|
201
|
-
r('a', {
|
|
202
|
-
href: 'https://github.com/ericfortis/mockaton',
|
|
203
|
-
target: '_blank',
|
|
204
|
-
rel: 'noopener noreferrer'
|
|
205
|
-
}, t`Documentation`)))
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
function CookieSelector() {
|
|
210
|
-
const { cookies } = state
|
|
254
|
+
function GlobalDelayField() {
|
|
211
255
|
function onChange() {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
256
|
+
store.setGlobalDelay(this.valueAsNumber)
|
|
257
|
+
}
|
|
258
|
+
function onWheel(event) {
|
|
259
|
+
if (event.deltaY > 0)
|
|
260
|
+
this.stepUp()
|
|
261
|
+
else
|
|
262
|
+
this.stepDown()
|
|
263
|
+
clearTimeout(onWheel.timer)
|
|
264
|
+
onWheel.timer = setTimeout(onChange.bind(this), 300)
|
|
215
265
|
}
|
|
216
|
-
const disabled = cookies.length <= 1
|
|
217
|
-
const list = cookies.length ? cookies : [[t`None`, true]]
|
|
218
266
|
return (
|
|
219
|
-
r('label', className(CSS.Field, CSS.
|
|
220
|
-
r('span', null, t`
|
|
221
|
-
r('
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
267
|
+
r('label', className(CSS.Field, CSS.GlobalDelayField),
|
|
268
|
+
r('span', null, t`Delay (ms)`),
|
|
269
|
+
r('input', {
|
|
270
|
+
type: 'number',
|
|
271
|
+
min: 0,
|
|
272
|
+
step: 100,
|
|
273
|
+
autocomplete: 'none',
|
|
274
|
+
value: store.delay,
|
|
275
|
+
onChange,
|
|
276
|
+
onWheel: [onWheel, { passive: true }]
|
|
277
|
+
})))
|
|
228
278
|
}
|
|
229
279
|
|
|
230
280
|
function BulkSelector() {
|
|
231
|
-
const { comments } =
|
|
281
|
+
const { comments } = store
|
|
232
282
|
// UX wise this should be a menu instead of this `select`.
|
|
233
283
|
// But this way is easier to implement, with a few hacks.
|
|
234
284
|
const firstOption = t`Pick Comment…`
|
|
235
285
|
function onChange() {
|
|
236
286
|
const value = this.value
|
|
237
287
|
this.value = firstOption // Hack
|
|
238
|
-
|
|
239
|
-
.then(parseError)
|
|
240
|
-
.then(updateState)
|
|
241
|
-
.catch(onError)
|
|
288
|
+
store.bulkSelectByComment(value)
|
|
242
289
|
}
|
|
243
290
|
const disabled = !comments.length
|
|
244
291
|
return (
|
|
@@ -253,45 +300,29 @@ function BulkSelector() {
|
|
|
253
300
|
},
|
|
254
301
|
r('option', { value: firstOption }, firstOption),
|
|
255
302
|
r('hr'),
|
|
256
|
-
comments.map(value =>
|
|
257
|
-
r('option', { value }, value)),
|
|
303
|
+
comments.map(value => r('option', { value }, value)),
|
|
258
304
|
r('hr'),
|
|
259
305
|
r('option', { value: AUTOGENERATED_500_COMMENT }, t`Auto500`)
|
|
260
306
|
)))
|
|
261
307
|
}
|
|
262
308
|
|
|
263
|
-
function
|
|
264
|
-
const {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
mockaton.setGlobalDelay(state.delay)
|
|
268
|
-
.then(parseError)
|
|
269
|
-
.catch(onError)
|
|
270
|
-
}
|
|
271
|
-
function onWheel(event) {
|
|
272
|
-
if (event.deltaY > 0)
|
|
273
|
-
this.stepUp()
|
|
274
|
-
else
|
|
275
|
-
this.stepDown()
|
|
276
|
-
clearTimeout(onWheel.timer)
|
|
277
|
-
onWheel.timer = setTimeout(onChange.bind(this), 300)
|
|
278
|
-
}
|
|
309
|
+
function CookieSelector() {
|
|
310
|
+
const { cookies } = store
|
|
311
|
+
const disabled = cookies.length <= 1
|
|
312
|
+
const list = cookies.length ? cookies : [[t`None`, true]]
|
|
279
313
|
return (
|
|
280
|
-
r('label', className(CSS.Field, CSS.
|
|
281
|
-
r('span', null, t`
|
|
282
|
-
r('
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
onWheel: [onWheel, { passive: true }]
|
|
290
|
-
})))
|
|
314
|
+
r('label', className(CSS.Field, CSS.CookieSelector),
|
|
315
|
+
r('span', null, t`Cookie`),
|
|
316
|
+
r('select', {
|
|
317
|
+
autocomplete: 'off',
|
|
318
|
+
disabled,
|
|
319
|
+
title: disabled ? t`No cookies specified in config.cookies` : '',
|
|
320
|
+
onChange() { store.selectCookie(this.value) }
|
|
321
|
+
}, list.map(([value, selected]) =>
|
|
322
|
+
r('option', { value, selected }, value)))))
|
|
291
323
|
}
|
|
292
324
|
|
|
293
325
|
function ProxyFallbackField() {
|
|
294
|
-
const { proxyFallback } = state
|
|
295
326
|
const checkboxRef = useRef()
|
|
296
327
|
function onChange() {
|
|
297
328
|
checkboxRef.current.disabled = !this.validity.valid || !this.value.trim()
|
|
@@ -299,10 +330,7 @@ function ProxyFallbackField() {
|
|
|
299
330
|
if (!this.validity.valid)
|
|
300
331
|
this.reportValidity()
|
|
301
332
|
else
|
|
302
|
-
|
|
303
|
-
.then(parseError)
|
|
304
|
-
.then(updateState)
|
|
305
|
-
.catch(onError)
|
|
333
|
+
store.setProxyFallback(this.value.trim())
|
|
306
334
|
}
|
|
307
335
|
return (
|
|
308
336
|
r('div', className(CSS.Field, CSS.FallbackBackend),
|
|
@@ -312,92 +340,103 @@ function ProxyFallbackField() {
|
|
|
312
340
|
type: 'url',
|
|
313
341
|
autocomplete: 'none',
|
|
314
342
|
placeholder: t`Type backend address`,
|
|
315
|
-
value: proxyFallback,
|
|
343
|
+
value: store.proxyFallback,
|
|
316
344
|
onChange
|
|
317
345
|
})),
|
|
318
346
|
SaveProxiedCheckbox(checkboxRef)))
|
|
319
347
|
}
|
|
320
348
|
|
|
321
349
|
function SaveProxiedCheckbox(ref) {
|
|
322
|
-
const { collectProxied, canProxy } = state
|
|
323
|
-
function onChange() {
|
|
324
|
-
mockaton.setCollectProxied(this.checked)
|
|
325
|
-
.then(parseError)
|
|
326
|
-
.catch(onError)
|
|
327
|
-
}
|
|
328
350
|
return (
|
|
329
351
|
r('label', className(CSS.SaveProxiedCheckbox),
|
|
330
352
|
r('input', {
|
|
331
353
|
ref,
|
|
332
354
|
type: 'checkbox',
|
|
333
|
-
disabled: !canProxy,
|
|
334
|
-
checked: collectProxied,
|
|
335
|
-
onChange
|
|
355
|
+
disabled: !store.canProxy,
|
|
356
|
+
checked: store.collectProxied,
|
|
357
|
+
onChange() { store.setCollectProxied(this.checked) }
|
|
336
358
|
}),
|
|
337
359
|
r('span', null, t`Save Mocks`)))
|
|
338
360
|
}
|
|
339
361
|
|
|
340
362
|
function ResetButton() {
|
|
341
|
-
function onClick() {
|
|
342
|
-
state.setChosenLink('', '')
|
|
343
|
-
mockaton.reset()
|
|
344
|
-
.then(parseError)
|
|
345
|
-
.then(updateState)
|
|
346
|
-
.catch(onError)
|
|
347
|
-
}
|
|
348
363
|
return (
|
|
349
364
|
r('button', {
|
|
350
365
|
className: CSS.ResetButton,
|
|
351
|
-
onClick
|
|
366
|
+
onClick: store.reset
|
|
352
367
|
}, t`Reset`))
|
|
353
368
|
}
|
|
354
369
|
|
|
370
|
+
function SettingsMenuTrigger() {
|
|
371
|
+
const id = '_settings_menu_'
|
|
372
|
+
return (
|
|
373
|
+
r('button', {
|
|
374
|
+
title: t`Settings`,
|
|
375
|
+
popovertarget: id,
|
|
376
|
+
className: CSS.MenuTrigger
|
|
377
|
+
},
|
|
378
|
+
SettingsIcon(),
|
|
379
|
+
Defer(() => SettingsMenu(id))))
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function SettingsMenu(id) {
|
|
383
|
+
const firstInputRef = useRef()
|
|
384
|
+
function onToggle(event) {
|
|
385
|
+
if (event.newState === 'open')
|
|
386
|
+
firstInputRef.current.focus()
|
|
387
|
+
}
|
|
388
|
+
return (
|
|
389
|
+
r('menu', {
|
|
390
|
+
id,
|
|
391
|
+
popover: '',
|
|
392
|
+
className: CSS.SettingsMenu,
|
|
393
|
+
onToggle
|
|
394
|
+
},
|
|
395
|
+
|
|
396
|
+
r('label', className(CSS.GroupByMethod),
|
|
397
|
+
r('input', {
|
|
398
|
+
ref: firstInputRef,
|
|
399
|
+
type: 'checkbox',
|
|
400
|
+
checked: store.groupByMethod,
|
|
401
|
+
onChange: store.toggleGroupByMethod
|
|
402
|
+
}),
|
|
403
|
+
r('span', null, t`Group by Method`)),
|
|
404
|
+
|
|
405
|
+
r('a', {
|
|
406
|
+
href: 'https://github.com/ericfortis/mockaton',
|
|
407
|
+
target: '_blank',
|
|
408
|
+
rel: 'noopener noreferrer'
|
|
409
|
+
}, t`Documentation`)))
|
|
410
|
+
}
|
|
411
|
+
|
|
355
412
|
|
|
356
413
|
|
|
357
414
|
/** # MockList */
|
|
358
415
|
|
|
359
416
|
function MockList() {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if (!Object.keys(brokersByMethod).length)
|
|
417
|
+
if (!Object.keys(store.brokersByMethod).length)
|
|
363
418
|
return (
|
|
364
419
|
r('div', className(CSS.empty),
|
|
365
420
|
t`No mocks found`))
|
|
366
421
|
|
|
367
|
-
if (groupByMethod)
|
|
368
|
-
return Object.keys(brokersByMethod).map(method => Fragment(
|
|
422
|
+
if (store.groupByMethod)
|
|
423
|
+
return Object.keys(store.brokersByMethod).map(method => Fragment(
|
|
369
424
|
r('tr', null,
|
|
370
|
-
r('th', { colspan: 2 + Number(canProxy) }),
|
|
425
|
+
r('th', { colspan: 2 + Number(store.canProxy) }),
|
|
371
426
|
r('th', null, method)),
|
|
372
427
|
rowsFor(method).map(Row)))
|
|
373
428
|
|
|
374
429
|
return rowsFor('*').map(Row)
|
|
375
430
|
}
|
|
376
431
|
|
|
377
|
-
|
|
378
|
-
function Row({ method, urlMask, urlMaskDittoed, broker }, i) {
|
|
379
|
-
const { canProxy, groupByMethod } = state
|
|
380
|
-
return (
|
|
381
|
-
r('tr', { key: method + '::' + urlMask },
|
|
382
|
-
canProxy && r('td', null, ProxyToggler(broker)),
|
|
383
|
-
r('td', null, DelayRouteToggler(broker)),
|
|
384
|
-
r('td', null, InternalServerErrorToggler(broker)),
|
|
385
|
-
!groupByMethod && r('td', className(CSS.Method), method),
|
|
386
|
-
r('td', null, PreviewLink(method, urlMask, urlMaskDittoed, i === 0)),
|
|
387
|
-
r('td', null, MockSelector(broker))))
|
|
388
|
-
}
|
|
389
|
-
|
|
390
432
|
function rowsFor(targetMethod) {
|
|
391
|
-
const { brokersByMethod } = state
|
|
392
|
-
|
|
393
433
|
const rows = []
|
|
394
|
-
for (const [method, brokers] of Object.entries(brokersByMethod))
|
|
434
|
+
for (const [method, brokers] of Object.entries(store.brokersByMethod))
|
|
395
435
|
if (targetMethod === '*' || targetMethod === method)
|
|
396
436
|
for (const [urlMask, broker] of Object.entries(brokers))
|
|
397
437
|
rows.push({ method, urlMask, broker })
|
|
398
438
|
|
|
399
439
|
const sorted = rows.sort((a, b) => a.urlMask.localeCompare(b.urlMask))
|
|
400
|
-
|
|
401
440
|
const urlMasksDittoed = dittoSplitPaths(sorted.map(r => r.urlMask))
|
|
402
441
|
return sorted.map((r, i) => ({
|
|
403
442
|
...r,
|
|
@@ -405,22 +444,50 @@ function rowsFor(targetMethod) {
|
|
|
405
444
|
}))
|
|
406
445
|
}
|
|
407
446
|
|
|
447
|
+
function Row({ method, urlMask, urlMaskDittoed, broker }, i) {
|
|
448
|
+
const key = Row.key(method, urlMask)
|
|
449
|
+
Row.ditto.set(key, urlMaskDittoed)
|
|
450
|
+
return (
|
|
451
|
+
r('tr', { key },
|
|
452
|
+
store.canProxy && r('td', null, ProxyToggler(broker)),
|
|
453
|
+
r('td', null, DelayRouteToggler(broker)),
|
|
454
|
+
r('td', null, InternalServerErrorToggler(broker)),
|
|
455
|
+
!store.groupByMethod && r('td', className(CSS.Method), method),
|
|
456
|
+
r('td', null, PreviewLink(method, urlMask, urlMaskDittoed, i === 0)),
|
|
457
|
+
r('td', null, MockSelector(broker))))
|
|
458
|
+
}
|
|
459
|
+
Row.key = (method, urlMask) => method + '::' + urlMask
|
|
460
|
+
Row.ditto = new Map()
|
|
461
|
+
|
|
462
|
+
function renderRow(method, urlMask) {
|
|
463
|
+
restoreFocus(() => {
|
|
464
|
+
unChooseOld()
|
|
465
|
+
const key = Row.key(method, urlMask)
|
|
466
|
+
trFor(key).replaceWith(Row({
|
|
467
|
+
method,
|
|
468
|
+
urlMask,
|
|
469
|
+
urlMaskDittoed: Row.ditto.get(key),
|
|
470
|
+
broker: store.brokerFor(method, urlMask)
|
|
471
|
+
}))
|
|
472
|
+
previewMock(method, urlMask)
|
|
473
|
+
})
|
|
474
|
+
|
|
475
|
+
function trFor(key) {
|
|
476
|
+
return document.querySelector(`body > main > .${CSS.leftSide} tr[key="${key}"]`)
|
|
477
|
+
}
|
|
478
|
+
function unChooseOld() {
|
|
479
|
+
return document.querySelector(`body > main > .${CSS.leftSide} tr .${CSS.chosen}`)
|
|
480
|
+
?.classList.remove(CSS.chosen)
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
408
484
|
|
|
409
485
|
function PreviewLink(method, urlMask, urlMaskDittoed, autofocus) {
|
|
410
|
-
|
|
486
|
+
function onClick(event) {
|
|
411
487
|
event.preventDefault()
|
|
412
|
-
|
|
413
|
-
findChosenLink()?.classList.remove(CSS.chosen)
|
|
414
|
-
this.classList.add(CSS.chosen)
|
|
415
|
-
state.setChosenLink(method, urlMask)
|
|
416
|
-
await previewMock(method, urlMask)
|
|
417
|
-
}
|
|
418
|
-
catch (error) {
|
|
419
|
-
onError(error)
|
|
420
|
-
}
|
|
488
|
+
store.previewLink(method, urlMask)
|
|
421
489
|
}
|
|
422
|
-
const
|
|
423
|
-
const isChosen = chosenLink.method === method && chosenLink.urlMask === urlMask
|
|
490
|
+
const isChosen = store.chosenLink.method === method && store.chosenLink.urlMask === urlMask
|
|
424
491
|
const [ditto, tail] = urlMaskDittoed
|
|
425
492
|
return (
|
|
426
493
|
r('a', {
|
|
@@ -434,30 +501,18 @@ function PreviewLink(method, urlMask, urlMaskDittoed, autofocus) {
|
|
|
434
501
|
: tail))
|
|
435
502
|
}
|
|
436
503
|
|
|
437
|
-
function findChosenLink() {
|
|
438
|
-
return document.querySelector(
|
|
439
|
-
`body > main > .${CSS.leftSide} .${CSS.PreviewLink}.${CSS.chosen}`)
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
const STR_PROXIED = t`Proxied`
|
|
443
504
|
|
|
444
505
|
/** @param {ClientMockBroker} broker */
|
|
445
506
|
function MockSelector(broker) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
state.setChosenLink(method, urlMask)
|
|
449
|
-
mockaton.select(this.value)
|
|
450
|
-
.then(parseError)
|
|
451
|
-
.then(updateState)
|
|
452
|
-
.catch(onError)
|
|
453
|
-
}
|
|
454
|
-
|
|
507
|
+
const STR_PROXIED = t`Proxied`
|
|
508
|
+
|
|
455
509
|
let selected = broker.currentMock.file
|
|
456
510
|
const { status } = parseFilename(selected)
|
|
457
511
|
const files = broker.mocks.filter(item =>
|
|
458
512
|
status === 500 ||
|
|
459
513
|
!item.includes(AUTOGENERATED_500_COMMENT))
|
|
460
|
-
|
|
514
|
+
|
|
515
|
+
if (store.canProxy && broker.currentMock.proxied) {
|
|
461
516
|
selected = STR_PROXIED
|
|
462
517
|
files.push(selected)
|
|
463
518
|
}
|
|
@@ -477,7 +532,7 @@ function MockSelector(broker) {
|
|
|
477
532
|
|
|
478
533
|
return (
|
|
479
534
|
r('select', {
|
|
480
|
-
onChange,
|
|
535
|
+
onChange() { store.selectFile(this.value) },
|
|
481
536
|
autocomplete: 'off',
|
|
482
537
|
'aria-label': t`Mock Selector`,
|
|
483
538
|
disabled: files.length <= 1,
|
|
@@ -496,9 +551,7 @@ function MockSelector(broker) {
|
|
|
496
551
|
function DelayRouteToggler(broker) {
|
|
497
552
|
function commit(checked) {
|
|
498
553
|
const { method, urlMask } = parseFilename(broker.mocks[0])
|
|
499
|
-
|
|
500
|
-
.then(parseError)
|
|
501
|
-
.catch(onError)
|
|
554
|
+
store.setDelayed(method, urlMask, checked)
|
|
502
555
|
}
|
|
503
556
|
return ClickDragToggler({
|
|
504
557
|
checked: broker.currentMock.delayed,
|
|
@@ -512,11 +565,7 @@ function DelayRouteToggler(broker) {
|
|
|
512
565
|
function InternalServerErrorToggler(broker) {
|
|
513
566
|
function onChange() {
|
|
514
567
|
const { method, urlMask } = parseFilename(broker.mocks[0])
|
|
515
|
-
|
|
516
|
-
mockaton.toggle500(method, urlMask)
|
|
517
|
-
.then(parseError)
|
|
518
|
-
.then(updateState)
|
|
519
|
-
.catch(onError)
|
|
568
|
+
store.toggle500(method, urlMask)
|
|
520
569
|
}
|
|
521
570
|
return (
|
|
522
571
|
r('label', {
|
|
@@ -526,7 +575,6 @@ function InternalServerErrorToggler(broker) {
|
|
|
526
575
|
r('input', {
|
|
527
576
|
type: 'checkbox',
|
|
528
577
|
'data-focus-group': FocusGroup.StatusToggler,
|
|
529
|
-
name: broker.currentMock.file,
|
|
530
578
|
checked: parseFilename(broker.currentMock.file).status === 500,
|
|
531
579
|
onChange
|
|
532
580
|
}),
|
|
@@ -537,11 +585,7 @@ function InternalServerErrorToggler(broker) {
|
|
|
537
585
|
function ProxyToggler(broker) {
|
|
538
586
|
function onChange() {
|
|
539
587
|
const { urlMask, method } = parseFilename(broker.mocks[0])
|
|
540
|
-
|
|
541
|
-
mockaton.setRouteIsProxied(method, urlMask, this.checked)
|
|
542
|
-
.then(parseError)
|
|
543
|
-
.then(updateState)
|
|
544
|
-
.catch(onError)
|
|
588
|
+
store.toggleProxied(method, urlMask, this.checked)
|
|
545
589
|
}
|
|
546
590
|
return (
|
|
547
591
|
r('label', {
|
|
@@ -550,7 +594,7 @@ function ProxyToggler(broker) {
|
|
|
550
594
|
},
|
|
551
595
|
r('input', {
|
|
552
596
|
type: 'checkbox',
|
|
553
|
-
checked:
|
|
597
|
+
checked: broker.currentMock.proxied,
|
|
554
598
|
onChange,
|
|
555
599
|
'data-focus-group': FocusGroup.ProxyToggler
|
|
556
600
|
}),
|
|
@@ -562,7 +606,7 @@ function ProxyToggler(broker) {
|
|
|
562
606
|
/** # StaticFilesList */
|
|
563
607
|
|
|
564
608
|
function StaticFilesList() {
|
|
565
|
-
const { staticBrokers, canProxy, groupByMethod } =
|
|
609
|
+
const { staticBrokers, canProxy, groupByMethod } = store
|
|
566
610
|
if (!Object.keys(staticBrokers).length)
|
|
567
611
|
return null
|
|
568
612
|
const dp = dittoSplitPaths(Object.keys(staticBrokers)).map(([ditto, tail]) => ditto
|
|
@@ -590,25 +634,17 @@ function StaticFilesList() {
|
|
|
590
634
|
|
|
591
635
|
/** @param {ClientStaticBroker} broker */
|
|
592
636
|
function DelayStaticRouteToggler(broker) {
|
|
593
|
-
function commit(checked) {
|
|
594
|
-
mockaton.setStaticRouteIsDelayed(broker.route, checked)
|
|
595
|
-
.then(parseError)
|
|
596
|
-
.catch(onError)
|
|
597
|
-
}
|
|
598
637
|
return ClickDragToggler({
|
|
599
638
|
checked: broker.delayed,
|
|
600
|
-
|
|
601
|
-
|
|
639
|
+
focusGroup: FocusGroup.DelayToggler,
|
|
640
|
+
commit(checked) {
|
|
641
|
+
store.setDelayedStatic(broker.route, checked)
|
|
642
|
+
}
|
|
602
643
|
})
|
|
603
644
|
}
|
|
604
645
|
|
|
605
646
|
/** @param {ClientStaticBroker} broker */
|
|
606
647
|
function NotFoundToggler(broker) {
|
|
607
|
-
function onChange() {
|
|
608
|
-
mockaton.setStaticRouteStatus(broker.route, this.checked ? 404 : 200)
|
|
609
|
-
.then(parseError)
|
|
610
|
-
.catch(onError)
|
|
611
|
-
}
|
|
612
648
|
return (
|
|
613
649
|
r('label', {
|
|
614
650
|
className: CSS.NotFoundToggler,
|
|
@@ -617,8 +653,10 @@ function NotFoundToggler(broker) {
|
|
|
617
653
|
r('input', {
|
|
618
654
|
type: 'checkbox',
|
|
619
655
|
checked: broker.status === 404,
|
|
620
|
-
|
|
621
|
-
|
|
656
|
+
'data-focus-group': FocusGroup.StatusToggler,
|
|
657
|
+
onChange() {
|
|
658
|
+
store.setStaticRouteStatus(broker.route, this.checked ? 404 : 200)
|
|
659
|
+
}
|
|
622
660
|
}),
|
|
623
661
|
r('span', null, t`404`)))
|
|
624
662
|
}
|
|
@@ -658,7 +696,6 @@ function ClickDragToggler({ checked, commit, focusGroup }) {
|
|
|
658
696
|
TimerIcon()))
|
|
659
697
|
}
|
|
660
698
|
|
|
661
|
-
|
|
662
699
|
function Resizer() {
|
|
663
700
|
let raf = 0
|
|
664
701
|
let initialX = 0
|
|
@@ -679,8 +716,8 @@ function Resizer() {
|
|
|
679
716
|
function onMove(event) {
|
|
680
717
|
const MIN_LEFT_WIDTH = 380
|
|
681
718
|
raf = raf || requestAnimationFrame(() => {
|
|
682
|
-
|
|
683
|
-
leftSideRef.current.style.width =
|
|
719
|
+
store.leftSideWidth = Math.max(panelWidth - (initialX - event.clientX), MIN_LEFT_WIDTH)
|
|
720
|
+
leftSideRef.current.style.width = store.leftSideWidth + 'px'
|
|
684
721
|
raf = 0
|
|
685
722
|
})
|
|
686
723
|
}
|
|
@@ -712,9 +749,11 @@ const payloadViewerCodeRef = useRef()
|
|
|
712
749
|
function PayloadViewer() {
|
|
713
750
|
return (
|
|
714
751
|
r('div', className(CSS.PayloadViewer),
|
|
715
|
-
r('h2', { ref: payloadViewerTitleRef },
|
|
752
|
+
r('h2', { ref: payloadViewerTitleRef },
|
|
753
|
+
!store.hasChosenLink && t`Preview`),
|
|
716
754
|
r('pre', null,
|
|
717
|
-
r('code', { ref: payloadViewerCodeRef },
|
|
755
|
+
r('code', { ref: payloadViewerCodeRef },
|
|
756
|
+
!store.hasChosenLink && t`Click a link to preview it`))))
|
|
718
757
|
}
|
|
719
758
|
|
|
720
759
|
function PayloadViewerTitle({ file, statusText }) {
|
|
@@ -741,7 +780,7 @@ const SPINNER_DELAY = 80
|
|
|
741
780
|
function PayloadViewerProgressBar() {
|
|
742
781
|
return (
|
|
743
782
|
r('div', className(CSS.ProgressBar),
|
|
744
|
-
r('div', { style: { animationDuration:
|
|
783
|
+
r('div', { style: { animationDuration: store.delay - SPINNER_DELAY + 'ms' } })))
|
|
745
784
|
}
|
|
746
785
|
|
|
747
786
|
async function previewMock(method, urlMask) {
|
|
@@ -759,11 +798,11 @@ async function previewMock(method, urlMask) {
|
|
|
759
798
|
signal: previewMock.controller.signal
|
|
760
799
|
})
|
|
761
800
|
clearTimeout(spinnerTimer)
|
|
762
|
-
const file =
|
|
763
|
-
if (
|
|
764
|
-
await updatePayloadViewer(
|
|
801
|
+
const { proxied, file } = store.brokerFor(method, urlMask).currentMock
|
|
802
|
+
if (proxied)
|
|
803
|
+
await updatePayloadViewer(true, '', response)
|
|
765
804
|
else if (file)
|
|
766
|
-
await updatePayloadViewer(file, response)
|
|
805
|
+
await updatePayloadViewer(false, file, response)
|
|
767
806
|
else {/* e.g. selected was deleted */}
|
|
768
807
|
}
|
|
769
808
|
catch (err) {
|
|
@@ -772,11 +811,11 @@ async function previewMock(method, urlMask) {
|
|
|
772
811
|
}
|
|
773
812
|
}
|
|
774
813
|
|
|
775
|
-
async function updatePayloadViewer(file, response) {
|
|
814
|
+
async function updatePayloadViewer(proxied, file, response) {
|
|
776
815
|
const mime = response.headers.get('content-type') || ''
|
|
777
816
|
|
|
778
817
|
payloadViewerTitleRef.current.replaceChildren(
|
|
779
|
-
|
|
818
|
+
proxied
|
|
780
819
|
? PayloadViewerTitleWhenProxied({
|
|
781
820
|
mime,
|
|
782
821
|
status: response.status,
|
|
@@ -790,9 +829,7 @@ async function updatePayloadViewer(file, response) {
|
|
|
790
829
|
|
|
791
830
|
if (mime.startsWith('image/')) // Naively assumes GET.200
|
|
792
831
|
payloadViewerCodeRef.current.replaceChildren(
|
|
793
|
-
r('img', {
|
|
794
|
-
src: URL.createObjectURL(await response.blob())
|
|
795
|
-
}))
|
|
832
|
+
r('img', { src: URL.createObjectURL(await response.blob()) }))
|
|
796
833
|
else {
|
|
797
834
|
const body = await response.text() || t`/* Empty Response Body */`
|
|
798
835
|
if (mime === 'application/json')
|
|
@@ -810,55 +847,11 @@ function isXML(mime) {
|
|
|
810
847
|
}
|
|
811
848
|
|
|
812
849
|
|
|
813
|
-
function initKeyboardNavigation() {
|
|
814
|
-
addEventListener('keydown', onKeyDown)
|
|
815
|
-
|
|
816
|
-
function onKeyDown(event) {
|
|
817
|
-
const pivot = document.activeElement
|
|
818
|
-
switch (event.key) {
|
|
819
|
-
case 'ArrowDown':
|
|
820
|
-
case 'ArrowUp': {
|
|
821
|
-
let fg = pivot.getAttribute('data-focus-group')
|
|
822
|
-
if (fg !== null) {
|
|
823
|
-
const offset = event.key === 'ArrowDown' ? +1 : -1
|
|
824
|
-
circularAdjacent(offset, allInFocusGroup(+fg), pivot).focus()
|
|
825
|
-
}
|
|
826
|
-
break
|
|
827
|
-
}
|
|
828
|
-
case 'ArrowRight':
|
|
829
|
-
case 'ArrowLeft': {
|
|
830
|
-
if (pivot.hasAttribute('data-focus-group') || pivot.classList.contains(CSS.MockSelector)) {
|
|
831
|
-
const offset = event.key === 'ArrowRight' ? +1 : -1
|
|
832
|
-
rowFocusable(pivot, offset).focus()
|
|
833
|
-
}
|
|
834
|
-
break
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
function rowFocusable(el, step) {
|
|
840
|
-
const row = el.closest('tr')
|
|
841
|
-
if (row) {
|
|
842
|
-
const focusables = Array.from(row.querySelectorAll('a, input, select:not(:disabled)'))
|
|
843
|
-
return circularAdjacent(step, focusables, el)
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
function allInFocusGroup(focusGroup) {
|
|
848
|
-
return Array.from(document.querySelectorAll(
|
|
849
|
-
`body > main > .${CSS.leftSide} table > tr > td [data-focus-group="${focusGroup}"]:is(input, a)`))
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
function circularAdjacent(step = 1, arr, pivot) {
|
|
853
|
-
return arr[(arr.indexOf(pivot) + step + arr.length) % arr.length]
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
|
|
857
850
|
/** # Error */
|
|
858
851
|
|
|
859
852
|
async function parseError(response) {
|
|
860
853
|
if (response.ok)
|
|
861
|
-
return
|
|
854
|
+
return response
|
|
862
855
|
if (response.status === 422)
|
|
863
856
|
throw await response.text()
|
|
864
857
|
throw response.statusText
|
|
@@ -935,7 +928,7 @@ function initRealTimeUpdates() {
|
|
|
935
928
|
if (oldSyncVersion !== syncVersion) { // because it could be < or >
|
|
936
929
|
oldSyncVersion = syncVersion
|
|
937
930
|
if (!skipUpdate)
|
|
938
|
-
await
|
|
931
|
+
await fetchState()
|
|
939
932
|
}
|
|
940
933
|
poll()
|
|
941
934
|
}
|
|
@@ -949,6 +942,50 @@ function initRealTimeUpdates() {
|
|
|
949
942
|
}
|
|
950
943
|
}
|
|
951
944
|
|
|
945
|
+
function initKeyboardNavigation() {
|
|
946
|
+
addEventListener('keydown', onKeyDown)
|
|
947
|
+
|
|
948
|
+
function onKeyDown(event) {
|
|
949
|
+
const pivot = document.activeElement
|
|
950
|
+
switch (event.key) {
|
|
951
|
+
case 'ArrowDown':
|
|
952
|
+
case 'ArrowUp': {
|
|
953
|
+
let fg = pivot.getAttribute('data-focus-group')
|
|
954
|
+
if (fg !== null) {
|
|
955
|
+
const offset = event.key === 'ArrowDown' ? +1 : -1
|
|
956
|
+
circularAdjacent(offset, allInFocusGroup(+fg), pivot).focus()
|
|
957
|
+
}
|
|
958
|
+
break
|
|
959
|
+
}
|
|
960
|
+
case 'ArrowRight':
|
|
961
|
+
case 'ArrowLeft': {
|
|
962
|
+
if (pivot.hasAttribute('data-focus-group') || pivot.classList.contains(CSS.MockSelector)) {
|
|
963
|
+
const offset = event.key === 'ArrowRight' ? +1 : -1
|
|
964
|
+
rowFocusable(pivot, offset).focus()
|
|
965
|
+
}
|
|
966
|
+
break
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
function rowFocusable(el, step) {
|
|
972
|
+
const row = el.closest('tr')
|
|
973
|
+
if (row) {
|
|
974
|
+
const focusables = Array.from(row.querySelectorAll('a, input, select:not(:disabled)'))
|
|
975
|
+
return circularAdjacent(step, focusables, el)
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
function allInFocusGroup(focusGroup) {
|
|
980
|
+
return Array.from(document.querySelectorAll(
|
|
981
|
+
`body > main > .${CSS.leftSide} table > tr > td [data-focus-group="${focusGroup}"]:is(input, a)`))
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
function circularAdjacent(step = 1, arr, pivot) {
|
|
985
|
+
return arr[(arr.indexOf(pivot) + step + arr.length) % arr.length]
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
952
989
|
|
|
953
990
|
/** # Utils */
|
|
954
991
|
|
|
@@ -1115,7 +1152,6 @@ dittoSplitPaths.test = function () {
|
|
|
1115
1152
|
}
|
|
1116
1153
|
|
|
1117
1154
|
|
|
1118
|
-
|
|
1119
1155
|
function syntaxJSON(json) {
|
|
1120
1156
|
const MAX_NODES = 50_000
|
|
1121
1157
|
let nNodes = 0
|
|
@@ -1163,7 +1199,6 @@ syntaxJSON.regex = /("(?:\\u[a-fA-F0-9]{4}|\\[^u]|[^\\"])*")(\s*:)?|([{}\[\],:\s
|
|
|
1163
1199
|
// Capture group order: [string, optional colon, punc]
|
|
1164
1200
|
|
|
1165
1201
|
|
|
1166
|
-
|
|
1167
1202
|
function syntaxXML(xml) {
|
|
1168
1203
|
const MAX_NODES = 50_000
|
|
1169
1204
|
let nNodes = 0
|
package/src/MockBroker.js
CHANGED
|
@@ -10,15 +10,17 @@ export class MockBroker {
|
|
|
10
10
|
this.mocks = []
|
|
11
11
|
this.currentMock = {
|
|
12
12
|
file: '',
|
|
13
|
-
delayed: false
|
|
13
|
+
delayed: false,
|
|
14
|
+
proxied: false
|
|
14
15
|
}
|
|
15
16
|
this.register(file)
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
get file() { return this.currentMock.file }
|
|
19
|
-
get status() { return parseFilename(this.file).status }
|
|
20
20
|
get delayed() { return this.currentMock.delayed }
|
|
21
|
-
get proxied() { return
|
|
21
|
+
get proxied() { return this.currentMock.proxied }
|
|
22
|
+
|
|
23
|
+
get status() { return parseFilename(this.file).status }
|
|
22
24
|
get temp500IsSelected() { return this.#isTemp500(this.file) }
|
|
23
25
|
|
|
24
26
|
hasMock(file) { return this.mocks.includes(file) }
|
|
@@ -80,25 +82,18 @@ export class MockBroker {
|
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
selectFile(filename) {
|
|
85
|
+
this.currentMock.proxied = false
|
|
83
86
|
this.currentMock.file = filename
|
|
84
87
|
}
|
|
85
|
-
|
|
88
|
+
|
|
86
89
|
toggle500() {
|
|
87
90
|
this.#is500(this.currentMock.file)
|
|
88
91
|
? this.selectDefaultFile()
|
|
89
92
|
: this.selectFile(this.mocks.find(this.#is500))
|
|
90
93
|
}
|
|
91
94
|
|
|
92
|
-
setDelayed(delayed) {
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
setProxied(proxied) {
|
|
97
|
-
if (proxied)
|
|
98
|
-
this.selectFile('')
|
|
99
|
-
else
|
|
100
|
-
this.selectDefaultFile()
|
|
101
|
-
}
|
|
95
|
+
setDelayed(delayed) { this.currentMock.delayed = delayed }
|
|
96
|
+
setProxied(proxied) { this.currentMock.proxied = proxied }
|
|
102
97
|
|
|
103
98
|
setByMatchingComment(comment) {
|
|
104
99
|
for (const file of this.mocks)
|
package/src/MockDispatcher.js
CHANGED
|
@@ -13,15 +13,19 @@ import { sendInternalServerError, sendMockNotFound } from './utils/http-response
|
|
|
13
13
|
|
|
14
14
|
export async function dispatchMock(req, response) {
|
|
15
15
|
try {
|
|
16
|
-
let broker = mockBrokerCollection.brokerByRoute(req.method, req.url)
|
|
17
16
|
const isHead = req.method === 'HEAD'
|
|
17
|
+
|
|
18
|
+
let broker = mockBrokerCollection.brokerByRoute(req.method, req.url)
|
|
18
19
|
if (!broker && isHead)
|
|
19
20
|
broker = mockBrokerCollection.brokerByRoute('GET', req.url)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
|
|
22
|
+
if (config.proxyFallback && (!broker || broker.proxied)) {
|
|
23
|
+
await proxy(req, response, broker?.delayed ? calcDelay() : 0)
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!broker) {
|
|
28
|
+
sendMockNotFound(response)
|
|
25
29
|
return
|
|
26
30
|
}
|
|
27
31
|
|
|
@@ -125,13 +125,6 @@ export function setMocksMatchingComment(comment) {
|
|
|
125
125
|
broker.setByMatchingComment(comment))
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
export function ensureAllRoutesHaveSelectedMock() {
|
|
129
|
-
forEachBroker(broker => {
|
|
130
|
-
if (broker.proxied)
|
|
131
|
-
broker.selectDefaultFile()
|
|
132
|
-
})
|
|
133
|
-
}
|
|
134
|
-
|
|
135
128
|
function forEachBroker(fn) {
|
|
136
129
|
for (const brokers of Object.values(collection))
|
|
137
130
|
Object.values(brokers).forEach(fn)
|