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.
@@ -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
+ }
@@ -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 { AUTOGENERATED_500_COMMENT, DEFAULT_MOCK_COMMENT } from './ApiConstants.js'
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, AUTOGENERATED_500_COMMENT)
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}${AUTOGENERATED_500_COMMENT}.${method}.500.empty`)
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 { AUTOGENERATED_500_COMMENT } from './ApiConstants.js'
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 !== AUTOGENERATED_500_COMMENT)
120
+ .filter(c => c !== AUTO_500_COMMENT)
121
121
  }
122
122
 
123
123
  export function setMocksMatchingComment(comment) {