mockaton 10.6.5 → 10.6.6
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 +7 -4
- package/src/ApiConstants.js +1 -1
- package/src/Dashboard.css +41 -51
- package/src/Dashboard.js +165 -432
- package/src/DashboardDom.js +83 -0
- package/src/DashboardHtml.js +2 -0
- package/src/DashboardStore.js +257 -0
- package/src/MockBroker.js +3 -3
- package/src/mockBrokersCollection.js +2 -2
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export function className(...args) {
|
|
2
|
+
return {
|
|
3
|
+
className: args.filter(Boolean).join(' ')
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function createElement(tag, props, ...children) {
|
|
8
|
+
const node = document.createElement(tag)
|
|
9
|
+
for (const [k, v] of Object.entries(props || {}))
|
|
10
|
+
if (k === 'ref') v.current = node
|
|
11
|
+
else if (k === 'style') Object.assign(node.style, v)
|
|
12
|
+
else if (k.startsWith('on')) node.addEventListener(k.slice(2).toLowerCase(), ...[v].flat())
|
|
13
|
+
else if (k in node) node[k] = v
|
|
14
|
+
else node.setAttribute(k, v)
|
|
15
|
+
node.append(...children.flat().filter(Boolean))
|
|
16
|
+
return node
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createSvgElement(tagName, props, ...children) {
|
|
20
|
+
const elem = document.createElementNS('http://www.w3.org/2000/svg', tagName)
|
|
21
|
+
for (const [k, v] of Object.entries(props))
|
|
22
|
+
elem.setAttribute(k, v)
|
|
23
|
+
elem.append(...children.flat().filter(Boolean))
|
|
24
|
+
return elem
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function useRef() {
|
|
28
|
+
return { current: null }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function Fragment(...args) {
|
|
32
|
+
const frag = new DocumentFragment()
|
|
33
|
+
for (const arg of args)
|
|
34
|
+
if (Array.isArray(arg))
|
|
35
|
+
frag.append(...arg)
|
|
36
|
+
else
|
|
37
|
+
frag.appendChild(arg)
|
|
38
|
+
return frag
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
export function Defer(cb) {
|
|
43
|
+
const placeholder = document.createComment('')
|
|
44
|
+
deferred(() => placeholder.replaceWith(cb()))
|
|
45
|
+
return placeholder
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function deferred(cb) {
|
|
49
|
+
return window.requestIdleCallback
|
|
50
|
+
? requestIdleCallback(cb)
|
|
51
|
+
: setTimeout(cb, 100) // Safari
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
export function restoreFocus(cb) {
|
|
56
|
+
const focusQuery = selectorFor(document.activeElement)
|
|
57
|
+
cb()
|
|
58
|
+
if (focusQuery)
|
|
59
|
+
document.querySelector(focusQuery)?.focus()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function selectorFor(elem) {
|
|
63
|
+
if (!(elem instanceof Element))
|
|
64
|
+
return
|
|
65
|
+
const path = []
|
|
66
|
+
while (elem) {
|
|
67
|
+
let qualifier = ''
|
|
68
|
+
if (elem.hasAttribute('key'))
|
|
69
|
+
qualifier = `[key="${elem.getAttribute('key')}"]`
|
|
70
|
+
else {
|
|
71
|
+
let i = 0
|
|
72
|
+
let sib = elem
|
|
73
|
+
while ((sib = sib.previousElementSibling))
|
|
74
|
+
if (sib.tagName === elem.tagName)
|
|
75
|
+
i++
|
|
76
|
+
if (i)
|
|
77
|
+
qualifier = `:nth-of-type(${i + 1})`
|
|
78
|
+
}
|
|
79
|
+
path.push(elem.tagName + qualifier)
|
|
80
|
+
elem = elem.parentElement
|
|
81
|
+
}
|
|
82
|
+
return path.reverse().join('>')
|
|
83
|
+
}
|
package/src/DashboardHtml.js
CHANGED
|
@@ -20,6 +20,8 @@ export const DashboardHtml = `<!DOCTYPE html>
|
|
|
20
20
|
<link rel="modulepreload" href="ApiConstants.js">
|
|
21
21
|
<link rel="modulepreload" href="ApiCommander.js">
|
|
22
22
|
<link rel="modulepreload" href="Filename.js">
|
|
23
|
+
<link rel="modulepreload" href="DashboardStore.js">
|
|
24
|
+
<link rel="modulepreload" href="DashboardDom.js">
|
|
23
25
|
<link rel="preload" href="Logo.svg" as="image">
|
|
24
26
|
|
|
25
27
|
<link rel="icon" href="data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m235 33.7v202c0 9.19-5.81 14-17.4 14-11.6 0-17.4-4.83-17.4-14v-151c-0.115-4.49-6.72-5.88-8.46-0.87l-48.3 155c-2.22 7.01-7.72 10.1-16 9.9-3.63-0.191-7.01-1.14-9.66-2.89-2.89-1.72-4.83-4.34-5.57-7.72-11.1-37-22.6-74.3-34.1-111-4.34-14-8.95-31.4-14-48.3-1.82-4.83-8.16-5.32-8.46 1.16v156c0 9.19-5.81 14-17.4 14-11.6 0-17.4-4.83-17.4-14v-207c0-5.74 2.62-13.2 9.39-16.3 7.5-3.14 15-4.05 21.8-3.8 3.14 0 6.03 0.686 8.95 1.46 3.14 0.797 6.03 1.98 8.7 3.63 2.65 1.38 5.32 3.14 7.5 5.57 2.22 2.22 3.87 4.83 5.07 7.72l45.8 157c4.63-15.9 32.4-117 33.3-121 4.12-13.8 7.72-26.5 10.9-38.7 1.16-2.65 2.89-5.32 5.07-7.5 2.15-2.15 4.58-4.12 7.5-5.32 2.65-1.57 5.57-2.89 8.46-3.63 3.14-0.797 9.44-0.988 12.1-0.988 11.6 1.07 29.4 9.14 29.4 27z' fill='%23808080'/%3E%3C/svg%3E">
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { deferred } from './DashboardDom.js'
|
|
2
|
+
import { Commander } from './ApiCommander.js'
|
|
3
|
+
import { parseFilename } from './Filename.js'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const mockaton = new Commander(location.origin)
|
|
7
|
+
|
|
8
|
+
export const store = {
|
|
9
|
+
setupPatchCallbacks(_then, _catch) {
|
|
10
|
+
mockaton.setupPatchCallbacks(_then, _catch)
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
render() {},
|
|
14
|
+
renderRow(method, urlMask) {},
|
|
15
|
+
|
|
16
|
+
/** @type {State.brokersByMethod} */
|
|
17
|
+
brokersByMethod: {},
|
|
18
|
+
|
|
19
|
+
/** @type {State.staticBrokers} */
|
|
20
|
+
staticBrokers: {},
|
|
21
|
+
|
|
22
|
+
cookies: [],
|
|
23
|
+
comments: [],
|
|
24
|
+
delay: 0,
|
|
25
|
+
|
|
26
|
+
collectProxied: false,
|
|
27
|
+
proxyFallback: '',
|
|
28
|
+
get canProxy() {
|
|
29
|
+
return Boolean(store.proxyFallback)
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
getSyncVersion: mockaton.getSyncVersion,
|
|
33
|
+
|
|
34
|
+
fetchState() {
|
|
35
|
+
return mockaton.getState().then(response => {
|
|
36
|
+
if (!response.ok)
|
|
37
|
+
throw response.status
|
|
38
|
+
|
|
39
|
+
response.json().then(state => {
|
|
40
|
+
Object.assign(store, state)
|
|
41
|
+
store.render()
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
leftSideWidth: window.innerWidth / 2,
|
|
48
|
+
|
|
49
|
+
groupByMethod: initPreference('groupByMethod'),
|
|
50
|
+
toggleGroupByMethod() {
|
|
51
|
+
store.groupByMethod = !store.groupByMethod
|
|
52
|
+
togglePreference('groupByMethod', store.groupByMethod)
|
|
53
|
+
store.render()
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
chosenLink: {
|
|
59
|
+
method: '',
|
|
60
|
+
urlMask: ''
|
|
61
|
+
},
|
|
62
|
+
get hasChosenLink() {
|
|
63
|
+
return store.chosenLink.method
|
|
64
|
+
&& store.chosenLink.urlMask
|
|
65
|
+
},
|
|
66
|
+
setChosenLink(method, urlMask) {
|
|
67
|
+
store.chosenLink = { method, urlMask }
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
reset() {
|
|
71
|
+
store.setChosenLink('', '')
|
|
72
|
+
mockaton.reset()
|
|
73
|
+
.then(store.fetchState)
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
bulkSelectByComment(value) {
|
|
77
|
+
mockaton.bulkSelectByComment(value)
|
|
78
|
+
.then(store.fetchState)
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
setGlobalDelay(value) {
|
|
83
|
+
store.delay = value
|
|
84
|
+
mockaton.setGlobalDelay(value)
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
selectCookie(name) {
|
|
88
|
+
store.cookies = store.cookies.map(([n]) => [n, n === name])
|
|
89
|
+
mockaton.selectCookie(name)
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
setProxyFallback(value) {
|
|
93
|
+
store.proxyFallback = value
|
|
94
|
+
mockaton.setProxyFallback(value)
|
|
95
|
+
.then(store.render)
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
setCollectProxied(checked) {
|
|
99
|
+
store.collectProxied = checked
|
|
100
|
+
mockaton.setCollectProxied(checked)
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
brokerFor(method, urlMask) {
|
|
105
|
+
return store.brokersByMethod[method]?.[urlMask]
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
brokersByMethodAsArray(targetMethod = '*') {
|
|
109
|
+
const rows = []
|
|
110
|
+
for (const [method, brokers] of Object.entries(store.brokersByMethod))
|
|
111
|
+
if (targetMethod === '*' || targetMethod === method)
|
|
112
|
+
for (const [urlMask, broker] of Object.entries(brokers))
|
|
113
|
+
rows.push({ method, urlMask, broker })
|
|
114
|
+
return rows.sort((a, b) => a.urlMask.localeCompare(b.urlMask))
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
previewLink(method, urlMask) {
|
|
118
|
+
store.setChosenLink(method, urlMask)
|
|
119
|
+
store.renderRow(method, urlMask)
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
selectFile(file) {
|
|
123
|
+
mockaton.select(file).then(async response => {
|
|
124
|
+
const { method, urlMask } = parseFilename(file)
|
|
125
|
+
store.brokerFor(method, urlMask).currentMock = await response.json()
|
|
126
|
+
store.setChosenLink(method, urlMask)
|
|
127
|
+
store.renderRow(method, urlMask)
|
|
128
|
+
})
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
toggle500(method, urlMask) {
|
|
132
|
+
mockaton.toggle500(method, urlMask).then(async response => {
|
|
133
|
+
store.brokerFor(method, urlMask).currentMock = await response.json()
|
|
134
|
+
store.setChosenLink(method, urlMask)
|
|
135
|
+
store.renderRow(method, urlMask)
|
|
136
|
+
})
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
setProxied(method, urlMask, checked) {
|
|
140
|
+
mockaton.setRouteIsProxied(method, urlMask, checked).then(() => {
|
|
141
|
+
store.brokerFor(method, urlMask).currentMock.proxied = checked
|
|
142
|
+
store.setChosenLink(method, urlMask)
|
|
143
|
+
store.renderRow(method, urlMask)
|
|
144
|
+
})
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
setDelayed(method, urlMask, checked) {
|
|
148
|
+
mockaton.setRouteIsDelayed(method, urlMask, checked).then(() => {
|
|
149
|
+
store.brokerFor(method, urlMask).currentMock.delayed = checked
|
|
150
|
+
})
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
staticBrokerFor(route) { return store.staticBrokers[route] },
|
|
155
|
+
|
|
156
|
+
setDelayedStatic(route, checked) {
|
|
157
|
+
store.staticBrokerFor(route).delayed = checked
|
|
158
|
+
mockaton.setStaticRouteIsDelayed(route, checked)
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
setStaticRouteStatus(route, status) {
|
|
162
|
+
store.staticBrokerFor(route).status = status
|
|
163
|
+
mockaton.setStaticRouteStatus(route, status)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
// When false, the URL will be updated with param=false
|
|
169
|
+
function initPreference(param) {
|
|
170
|
+
const qs = new URLSearchParams(location.search)
|
|
171
|
+
if (!qs.has(param)) {
|
|
172
|
+
const group = localStorage.getItem(param) !== 'false'
|
|
173
|
+
if (!group) {
|
|
174
|
+
const url = new URL(location.href)
|
|
175
|
+
url.searchParams.set(param, false)
|
|
176
|
+
history.replaceState(null, '', url)
|
|
177
|
+
}
|
|
178
|
+
return group
|
|
179
|
+
}
|
|
180
|
+
return qs.get(param) !== 'false'
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// When false, the URL and localStorage will have param=false
|
|
184
|
+
function togglePreference(param, nextVal) {
|
|
185
|
+
if (nextVal)
|
|
186
|
+
localStorage.removeItem(param)
|
|
187
|
+
else
|
|
188
|
+
localStorage.setItem(param, nextVal)
|
|
189
|
+
|
|
190
|
+
const url = new URL(location.href)
|
|
191
|
+
if (nextVal)
|
|
192
|
+
url.searchParams.delete(param)
|
|
193
|
+
else
|
|
194
|
+
url.searchParams.set(param, false)
|
|
195
|
+
history.replaceState(null, '', url)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Think of this as a way of printing a directory tree in which
|
|
202
|
+
* the repeated folder paths are kept but styled differently.
|
|
203
|
+
* @param {string[]} paths - sorted
|
|
204
|
+
*/
|
|
205
|
+
export function dittoSplitPaths(paths) {
|
|
206
|
+
const result = [['', paths[0]]]
|
|
207
|
+
const pathsInParts = paths.map(p => p.split('/').filter(Boolean))
|
|
208
|
+
|
|
209
|
+
for (let i = 1; i < paths.length; i++) {
|
|
210
|
+
const prevParts = pathsInParts[i - 1]
|
|
211
|
+
const currParts = pathsInParts[i]
|
|
212
|
+
|
|
213
|
+
let j = 0
|
|
214
|
+
while (
|
|
215
|
+
j < currParts.length &&
|
|
216
|
+
j < prevParts.length &&
|
|
217
|
+
currParts[j] === prevParts[j])
|
|
218
|
+
j++
|
|
219
|
+
|
|
220
|
+
if (!j) // no common dirs
|
|
221
|
+
result.push(['', paths[i]])
|
|
222
|
+
else {
|
|
223
|
+
const ditto = '/' + currParts.slice(0, j).join('/') + '/'
|
|
224
|
+
result.push([ditto, paths[i].slice(ditto.length)])
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return result
|
|
228
|
+
}
|
|
229
|
+
dittoSplitPaths.test = function () {
|
|
230
|
+
const input = [
|
|
231
|
+
'/api/user',
|
|
232
|
+
'/api/user/avatar',
|
|
233
|
+
'/api/user/friends',
|
|
234
|
+
'/api/vid',
|
|
235
|
+
'/api/video/id',
|
|
236
|
+
'/api/video/stats',
|
|
237
|
+
'/v2/foo',
|
|
238
|
+
'/v2/foo/bar'
|
|
239
|
+
]
|
|
240
|
+
const expected = [
|
|
241
|
+
['', '/api/user'],
|
|
242
|
+
['/api/user/', 'avatar'],
|
|
243
|
+
['/api/user/', 'friends'],
|
|
244
|
+
['/api/', 'vid'],
|
|
245
|
+
['/api/', 'video/id'],
|
|
246
|
+
['/api/video/', 'stats'],
|
|
247
|
+
['', '/v2/foo'],
|
|
248
|
+
['/v2/foo/', 'bar']
|
|
249
|
+
]
|
|
250
|
+
console.assert(deepEqual(dittoSplitPaths(input), expected))
|
|
251
|
+
}
|
|
252
|
+
deferred(dittoSplitPaths.test)
|
|
253
|
+
|
|
254
|
+
function deepEqual(a, b) {
|
|
255
|
+
return JSON.stringify(a) === JSON.stringify(b)
|
|
256
|
+
}
|
|
257
|
+
|
package/src/MockBroker.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { includesComment, extractComments, parseFilename } from './Filename.js'
|
|
2
|
-
import {
|
|
2
|
+
import { AUTO_500_COMMENT, DEFAULT_MOCK_COMMENT } from './ApiConstants.js'
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
/** MockBroker is a state for a particular route. It knows the available mock files
|
|
@@ -44,7 +44,7 @@ export class MockBroker {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
#isTemp500(file) {
|
|
47
|
-
return includesComment(file,
|
|
47
|
+
return includesComment(file, AUTO_500_COMMENT)
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
#sortMocks() {
|
|
@@ -65,7 +65,7 @@ export class MockBroker {
|
|
|
65
65
|
#registerTemp500() {
|
|
66
66
|
const { urlMask, method } = parseFilename(this.mocks[0])
|
|
67
67
|
const file = urlMask.replace(/^\//, '') // Removes leading slash
|
|
68
|
-
this.mocks.push(`${file}${
|
|
68
|
+
this.mocks.push(`${file}${AUTO_500_COMMENT}.${method}.500.empty`)
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
unregister(file) {
|
|
@@ -6,7 +6,7 @@ import { MockBroker } from './MockBroker.js'
|
|
|
6
6
|
import { listFilesRecursively } from './utils/fs.js'
|
|
7
7
|
import { config, isFileAllowed } from './config.js'
|
|
8
8
|
import { parseFilename, validateFilename } from './Filename.js'
|
|
9
|
-
import {
|
|
9
|
+
import { AUTO_500_COMMENT } from './ApiConstants.js'
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -117,7 +117,7 @@ export function extractAllComments() {
|
|
|
117
117
|
comments.add(c)
|
|
118
118
|
})
|
|
119
119
|
return Array.from(comments)
|
|
120
|
-
.filter(c => c !==
|
|
120
|
+
.filter(c => c !== AUTO_500_COMMENT)
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
export function setMocksMatchingComment(comment) {
|