mockaton 8.1.0 → 8.1.2

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.
Binary file
Binary file
package/README.md CHANGED
@@ -226,7 +226,7 @@ Also, they take precedence over the `GET` mocks in `Config.mockDir`.
226
226
  For example, if you have two files for `GET /foo/bar.jpg`
227
227
  ```
228
228
  my-static-dir/foo/bar.jpg
229
- my-mocks-dir/foo/bar.jpg.GET.200.jpg // Unreacheable
229
+ my-mocks-dir/foo/bar.jpg.GET.200.jpg // Unreacheable
230
230
  ```
231
231
 
232
232
  Use Case 1: If you have a bunch of static assets you don’t want to add `.GET.200.ext`
@@ -347,17 +347,7 @@ If you don’t want to open a browser, pass a noop, such as
347
347
  Config.onReady = () => {}
348
348
  ```
349
349
 
350
- On Linux, you could pass:
351
- ```js
352
- import { exec } from 'node:child_process'
353
-
354
-
355
- Config.onReady = function openInBrowser(address) {
356
- exec(`xdg-open ${address}`)
357
- }
358
- ```
359
-
360
- Or, for more cross-platform utility, you could `npm install open` and pass it.
350
+ For a more cross-platform utility, you could `npm install open` and pass it.
361
351
  ```js
362
352
  import open from 'open'
363
353
 
@@ -420,5 +410,3 @@ await mockaton.reset()
420
410
 
421
411
  ## TODO
422
412
  - Refactor Tests
423
- - Dashboard. Indicate if some file on `staticDir` is overriding a mock.
424
- - jsonc, json5?
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "mockaton",
3
3
  "description": "A deterministic server-side for developing and testing frontend clients",
4
4
  "type": "module",
5
- "version": "8.1.0",
5
+ "version": "8.1.2",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
package/src/Dashboard.js CHANGED
@@ -36,7 +36,7 @@ const CSS = {
36
36
 
37
37
  const r = createElement
38
38
  const refPayloadViewer = useRef()
39
- const refPayloadFile = useRef()
39
+ const refPayloadViewerFileTitle = useRef()
40
40
 
41
41
  const mockaton = new Commander(window.location.origin)
42
42
 
@@ -53,13 +53,13 @@ function init() {
53
53
  }
54
54
  init()
55
55
 
56
- function App([brokersByMethod, cookies, comments, corsAllowed, staticFiles]) {
56
+ function App(apiResponses) {
57
57
  empty(document.body)
58
- createRoot(document.body).render(
59
- DevPanel(brokersByMethod, cookies, comments, corsAllowed, staticFiles))
58
+ createRoot(document.body)
59
+ .render(DevPanel(apiResponses))
60
60
  }
61
61
 
62
- function DevPanel(brokersByMethod, cookies, comments, corsAllowed, staticFiles) {
62
+ function DevPanel([brokersByMethod, cookies, comments, corsAllowed, staticFiles]) {
63
63
  return (
64
64
  r('div', null,
65
65
  r('menu', null,
@@ -72,33 +72,39 @@ function DevPanel(brokersByMethod, cookies, comments, corsAllowed, staticFiles)
72
72
  r('table', null, Object.entries(brokersByMethod).map(([method, brokers]) =>
73
73
  r(SectionByMethod, { method, brokers }))),
74
74
  r('div', { className: CSS.PayloadViewer },
75
- r('h2', { ref: refPayloadFile }, Strings.mock),
75
+ r('h2', { ref: refPayloadViewerFileTitle }, Strings.mock),
76
76
  r('pre', null,
77
77
  r('code', { ref: refPayloadViewer }, Strings.click_link_to_preview)))),
78
78
  r(StaticFilesList, { staticFiles })))
79
79
  }
80
80
 
81
+
81
82
  function CookieSelector({ list }) {
83
+ function onChange() {
84
+ mockaton.selectCookie(this.value)
85
+ .catch(console.error)
86
+ }
82
87
  const disabled = list.length <= 1
83
88
  return (
84
89
  r('label', null,
85
90
  r('span', null, Strings.cookie),
86
91
  r('select', {
87
- autocomplete: 'off',
88
- disabled,
89
- title: disabled ? Strings.cookie_disabled_title : '',
90
- onChange() {
91
- mockaton.selectCookie(this.value)
92
- .catch(console.error)
93
- }
94
- }, list.map(([key, selected]) =>
95
- r('option', {
96
- value: key,
97
- selected
98
- }, key)))))
92
+ autocomplete: 'off',
93
+ disabled,
94
+ title: disabled ? Strings.cookie_disabled_title : '',
95
+ onChange
96
+ },
97
+ list.map(([key, selected]) =>
98
+ r('option', { value: key, selected }, key)))))
99
99
  }
100
100
 
101
+
101
102
  function BulkSelector({ comments }) {
103
+ function onChange() {
104
+ mockaton.bulkSelectByComment(this.value)
105
+ .then(init)
106
+ .catch(console.error)
107
+ }
102
108
  const disabled = !comments.length
103
109
  const list = disabled
104
110
  ? []
@@ -107,34 +113,32 @@ function BulkSelector({ comments }) {
107
113
  r('label', null,
108
114
  r('span', null, Strings.bulk_select_by_comment),
109
115
  r('select', {
110
- autocomplete: 'off',
111
- disabled,
112
- title: disabled ? Strings.bulk_select_by_comment_disabled_title : '',
113
- onChange() {
114
- mockaton.bulkSelectByComment(this.value)
115
- .then(init)
116
- .catch(console.error)
117
- }
118
- }, list.map(item =>
119
- r('option', {
120
- value: item
121
- }, item)))))
116
+ autocomplete: 'off',
117
+ disabled,
118
+ title: disabled ? Strings.bulk_select_by_comment_disabled_title : '',
119
+ onChange
120
+ },
121
+ list.map(value =>
122
+ r('option', { value }, value)))))
122
123
  }
123
124
 
125
+
124
126
  function CorsCheckbox({ corsAllowed }) {
127
+ function onChange(event) {
128
+ mockaton.setCorsAllowed(event.currentTarget.checked)
129
+ .catch(console.error)
130
+ }
125
131
  return (
126
132
  r('label', { className: CSS.CorsCheckbox },
127
133
  r('input', {
128
134
  type: 'checkbox',
129
135
  checked: corsAllowed,
130
- onChange(event) {
131
- mockaton.setCorsAllowed(event.currentTarget.checked)
132
- .catch(console.error)
133
- }
136
+ onChange
134
137
  }),
135
138
  Strings.allow_cors))
136
139
  }
137
140
 
141
+
138
142
  function ResetButton() {
139
143
  return (
140
144
  r('button', {
@@ -147,6 +151,7 @@ function ResetButton() {
147
151
  )
148
152
  }
149
153
 
154
+
150
155
  function StaticFilesList({ staticFiles }) {
151
156
  if (!staticFiles.length)
152
157
  return null
@@ -177,46 +182,50 @@ function SectionByMethod({ method, brokers }) {
177
182
  r('td', null, r(PreviewLink, { method, urlMask })),
178
183
  r('td', null, r(MockSelector, { broker })),
179
184
  r('td', null, r(DelayRouteToggler, { broker })),
180
- r('td', null, r(InternalServerErrorToggler, { broker }))
181
- ))))
185
+ r('td', null, r(InternalServerErrorToggler, { broker }))))))
182
186
  }
183
187
 
188
+
184
189
  function PreviewLink({ method, urlMask }) {
190
+ async function onClick(event) {
191
+ event.preventDefault()
192
+ try {
193
+ const spinner = setTimeout(() => {
194
+ empty(refPayloadViewer.current)
195
+ refPayloadViewer.current.append(ProgressBar())
196
+ }, 180)
197
+ const res = await fetch(this.href, {
198
+ method: this.getAttribute('data-method')
199
+ })
200
+ document.querySelector(`.${CSS.PreviewLink}.${CSS.chosen}`)?.classList.remove(CSS.chosen)
201
+ this.classList.add(CSS.chosen)
202
+ clearTimeout(spinner)
203
+
204
+ const mime = res.headers.get('content-type') || ''
205
+ if (mime.startsWith('image/')) // naively assumes GET.200
206
+ renderPayloadImage(this.href)
207
+ else
208
+ updatePayloadViewer(await res.text() || Strings.empty_response_body, mime)
209
+
210
+ empty(refPayloadViewerFileTitle.current)
211
+ refPayloadViewerFileTitle.current.append(PayloadViewerTitle({
212
+ file: this.closest('tr').querySelector('select').value
213
+ }))
214
+ }
215
+ catch (error) {
216
+ console.error(error)
217
+ }
218
+ }
185
219
  return (
186
220
  r('a', {
187
221
  className: CSS.PreviewLink,
188
222
  href: urlMask,
189
223
  'data-method': method,
190
- async onClick(event) {
191
- event.preventDefault()
192
- try {
193
- const spinner = setTimeout(() => {
194
- empty(refPayloadViewer.current)
195
- refPayloadViewer.current.append(ProgressBar())
196
- }, 180)
197
- const res = await fetch(this.href, {
198
- method: this.getAttribute('data-method')
199
- })
200
- document.querySelector(`.${CSS.PreviewLink}.${CSS.chosen}`)?.classList.remove(CSS.chosen)
201
- this.classList.add(CSS.chosen)
202
- clearTimeout(spinner)
203
- const mime = res.headers.get('content-type') || ''
204
- if (mime.startsWith('image/')) // naively assumes GET.200
205
- renderPayloadImage(this.href)
206
- else
207
- updatePayloadViewer(await res.text() || Strings.empty_response_body, mime)
208
- empty(refPayloadFile.current)
209
- refPayloadFile.current.append(PayloadViewerTitle({
210
- file: this.closest('tr').querySelector('select').value
211
- }))
212
- }
213
- catch (error) {
214
- console.error(error)
215
- }
216
- }
224
+ onClick
217
225
  }, urlMask))
218
226
  }
219
227
 
228
+
220
229
  function PayloadViewerTitle({ file }) {
221
230
  const { urlMask, method, status, ext } = parseFilename(file)
222
231
  return (
@@ -226,12 +235,14 @@ function PayloadViewerTitle({ file }) {
226
235
  '.' + ext))
227
236
  }
228
237
 
238
+
229
239
  function ProgressBar() {
230
240
  return (
231
241
  r('div', { className: CSS.ProgressBar },
232
242
  r('div', { style: { animationDuration: '1000ms' } }))) // TODO from Config.delay - 180
233
243
  }
234
244
 
245
+
235
246
  function renderPayloadImage(href) {
236
247
  empty(refPayloadViewer.current)
237
248
  refPayloadViewer.current.append(r('img', { src: href }))
@@ -246,46 +257,55 @@ function updatePayloadViewer(body, mime) {
246
257
 
247
258
 
248
259
  function MockSelector({ broker }) {
249
- const className = (defaultIsSelected, status) => cssClass(
250
- CSS.MockSelector,
251
- !defaultIsSelected && CSS.bold,
252
- status >= 400 && status < 500 && CSS.status4xx)
260
+ function onChange() {
261
+ const { status } = parseFilename(this.value)
262
+ this.style.fontWeight = this.value === this.options[0].value // default is selected
263
+ ? 'normal'
264
+ : 'bold'
265
+ mockaton.select(this.value)
266
+ .then(() => {
267
+ this.closest('tr').querySelector('a').click()
268
+ this.closest('tr').querySelector(`.${CSS.InternalServerErrorToggler}>[type=checkbox]`).checked = status === 500
269
+ this.className = className(this.value === this.options[0].value, status)
270
+ })
271
+ .catch(console.error)
272
+ }
253
273
 
254
- const items = broker.mocks
255
- const selected = broker.currentMock.file
256
274
 
275
+ function className(defaultIsSelected, status) {
276
+ return cssClass(
277
+ CSS.MockSelector,
278
+ !defaultIsSelected && CSS.bold,
279
+ status >= 400 && status < 500 && CSS.status4xx)
280
+ }
281
+
282
+ const selected = broker.currentMock.file
257
283
  const { status } = parseFilename(selected)
258
- const files = items.filter(item =>
284
+ const files = broker.mocks.filter(item =>
259
285
  status === 500 ||
260
286
  !item.includes(DEFAULT_500_COMMENT))
261
287
 
262
288
  return (
263
289
  r('select', {
264
- className: className(selected === files[0], status),
265
- autocomplete: 'off',
266
- disabled: files.length <= 1,
267
- onChange() {
268
- const { status } = parseFilename(this.value)
269
- this.style.fontWeight = this.value === this.options[0].value // default is selected
270
- ? 'normal'
271
- : 'bold'
272
- mockaton.select(this.value)
273
- .then(() => {
274
- this.closest('tr').querySelector('a').click()
275
- this.closest('tr').querySelector(`.${CSS.InternalServerErrorToggler}>[type=checkbox]`).checked = status === 500
276
- this.className = className(this.value === this.options[0].value, status)
277
- })
278
- .catch(console.error)
279
- }
280
- }, files.map(file => r('option', {
281
- value: file,
282
- selected: file === selected
283
- }, file))))
290
+ autocomplete: 'off',
291
+ className: className(selected === files[0], status),
292
+ disabled: files.length <= 1,
293
+ onChange
294
+ },
295
+ files.map(file =>
296
+ r('option', {
297
+ value: file,
298
+ selected: file === selected
299
+ }, file))))
284
300
  }
285
301
 
302
+
286
303
  function DelayRouteToggler({ broker }) {
287
- const name = broker.currentMock.file
288
- const checked = Boolean(broker.currentMock.delay)
304
+ function onChange(event) {
305
+ const { method, urlMask } = parseFilename(this.name)
306
+ mockaton.setRouteIsDelayed(method, urlMask, event.currentTarget.checked)
307
+ .catch(console.error)
308
+ }
289
309
  return (
290
310
  r('label', {
291
311
  className: CSS.DelayToggler,
@@ -293,28 +313,29 @@ function DelayRouteToggler({ broker }) {
293
313
  },
294
314
  r('input', {
295
315
  type: 'checkbox',
296
- autocomplete: 'off',
297
- name,
298
- checked,
299
- onChange(event) {
300
- const { method, urlMask } = parseFilename(this.name)
301
- mockaton.setRouteIsDelayed(method, urlMask, event.currentTarget.checked)
302
- .catch(console.error)
303
- }
316
+ name: broker.currentMock.file,
317
+ checked: Boolean(broker.currentMock.delay),
318
+ onChange
304
319
  }),
305
320
  TimerIcon()))
306
321
  }
307
322
 
323
+
308
324
  function TimerIcon() {
309
325
  return (
310
326
  r('svg', { viewBox: '0 0 24 24' },
311
327
  r('path', { d: 'M12 7H11v6l5 3.2.75-1.23-4.5-3z' })))
312
328
  }
313
329
 
330
+
314
331
  function InternalServerErrorToggler({ broker }) {
315
- const items = broker.mocks
316
- const name = broker.currentMock.file
317
- const checked = parseFilename(broker.currentMock.file).status === 500
332
+ function onChange(event) {
333
+ mockaton.select(event.currentTarget.checked
334
+ ? broker.mocks.find(f => parseFilename(f).status === 500)
335
+ : broker.mocks[0])
336
+ .then(init)
337
+ .catch(console.error)
338
+ }
318
339
  return (
319
340
  r('label', {
320
341
  className: CSS.InternalServerErrorToggler,
@@ -322,16 +343,9 @@ function InternalServerErrorToggler({ broker }) {
322
343
  },
323
344
  r('input', {
324
345
  type: 'checkbox',
325
- autocomplete: 'off',
326
- name,
327
- checked,
328
- onChange(event) {
329
- mockaton.select(event.currentTarget.checked
330
- ? items.find(f => parseFilename(f).status === 500)
331
- : items[0])
332
- .then(init)
333
- .catch(console.error)
334
- }
346
+ name: broker.currentMock.file,
347
+ checked: parseFilename(broker.currentMock.file).status === 500,
348
+ onChange
335
349
  }),
336
350
  r('span', null, '500')
337
351
  )
package/src/Mockaton.js CHANGED
@@ -38,7 +38,7 @@ async function onRequest(req, response) {
38
38
  methods: Config.corsMethods,
39
39
  maxAge: Config.corsMaxAge,
40
40
  credentials: Config.corsCredentials,
41
- exposedHeaders: Config.extraHeaders
41
+ exposedHeaders: Config.corsExposedHeaders
42
42
  })
43
43
 
44
44
  const { url, method } = req
@@ -62,6 +62,7 @@ function setPreflightSpecificHeaders(req, response, methods, headers, maxAge) {
62
62
  }
63
63
 
64
64
 
65
+ // TESTME
65
66
  function setActualRequestHeaders(response, exposedHeaders) {
66
67
  // Exposed means the client-side JavaScript can read them
67
68
  if (exposedHeaders.length)