mockaton 12.0.0 → 12.1.0

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/README.md CHANGED
@@ -27,9 +27,9 @@ For example, for [/api/company/123](#), the filename could be:
27
27
  ## Dashboard
28
28
 
29
29
  <picture>
30
- <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/ericfortis/mockaton/refs/heads/main/pixaton-tests/tests/macos/pic-for-readme.vp832x740.light.gold.png">
31
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ericfortis/mockaton/refs/heads/main/pixaton-tests/tests/macos/pic-for-readme.vp832x740.dark.gold.png">
32
- <img alt="Mockaton Dashboard" src="https://raw.githubusercontent.com/ericfortis/mockaton/refs/heads/main/pixaton-tests/tests/macos/pic-for-readme.vp832x740.dark.gold.png">
30
+ <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/ericfortis/mockaton/refs/heads/main/pixaton-tests/tests/macos/pic-for-readme.vp762x762.light.gold.png">
31
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ericfortis/mockaton/refs/heads/main/pixaton-tests/tests/macos/pic-for-readme.vp762x762.dark.gold.png">
32
+ <img alt="Mockaton Dashboard" src="https://raw.githubusercontent.com/ericfortis/mockaton/refs/heads/main/pixaton-tests/tests/macos/pic-for-readme.vp762x762.dark.gold.png">
33
33
  </picture>
34
34
 
35
35
  On the dashboard you can:
@@ -38,8 +38,8 @@ On the dashboard you can:
38
38
  - Trigger an autogenerated `500` error
39
39
  - …and cycle it off (for testing retries)
40
40
 
41
- Nonetheless, there’s a programmatic API, which is handy for
42
- setting up tests (see **Commander&nbsp;API** section below).
41
+ Nonetheless, there’s a [Control API ↗](https://mockaton.com/api), which is handy for
42
+ setting up tests.
43
43
 
44
44
  <br/>
45
45
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "mockaton",
3
3
  "description": "HTTP Mock Server",
4
4
  "type": "module",
5
- "version": "12.0.0",
5
+ "version": "12.1.0",
6
6
  "exports": {
7
7
  ".": {
8
8
  "import": "./index.js",
package/src/client/app.js CHANGED
@@ -1,8 +1,4 @@
1
- import {
2
- createElement as r,
3
- createSvgElement as s,
4
- className, restoreFocus, Defer, Fragment, adoptCSS
5
- } from './dom-utils.js'
1
+ import { createElement as r, createSvgElement as s, className, restoreFocus, Defer, Fragment, adoptCSS } from './dom-utils.js'
6
2
 
7
3
  import { store } from './app-store.js'
8
4
  import { parseFilename } from './Filename.js'
@@ -47,6 +43,9 @@ function App() {
47
43
  style: { width: leftSideRef.width },
48
44
  className: CSS.leftSide
49
45
  },
46
+ r('div', className(CSS.SubToolbar),
47
+ GroupByMethod(),
48
+ BulkSelector()),
50
49
  r('div', className(CSS.Table),
51
50
  MockList(),
52
51
  StaticFilesList())),
@@ -68,7 +67,6 @@ function Header() {
68
67
  r('div', className(CSS.GlobalDelayWrap),
69
68
  GlobalDelayField(),
70
69
  GlobalDelayJitterField()),
71
- BulkSelector(),
72
70
  CookieSelector(),
73
71
  ProxyFallbackField(),
74
72
  ResetButton(),
@@ -136,30 +134,6 @@ function GlobalDelayJitterField() {
136
134
  }
137
135
 
138
136
 
139
- function BulkSelector() {
140
- const { comments } = store
141
- const firstOption = t`Pick Comment…`
142
- function onChange() {
143
- const value = this.value
144
- this.value = firstOption // hack so it’s always selected
145
- store.bulkSelectByComment(value)
146
- }
147
- const disabled = !comments.length
148
- return (
149
- r('label', className(CSS.Field),
150
- r('span', null, t`Bulk Select`),
151
- r('select', {
152
- className: CSS.BulkSelector,
153
- autocomplete: 'off',
154
- disabled,
155
- title: disabled ? t`No mock files have comments which are anything within parentheses on the filename.` : '',
156
- onChange
157
- },
158
- r('option', { value: firstOption }, firstOption),
159
- r('hr'),
160
- comments.map(value => r('option', { value }, value)))))
161
- // TODO For a11y, use `menu` instead of `select`
162
- }
163
137
 
164
138
  function CookieSelector() {
165
139
  const { cookies } = store
@@ -234,34 +208,19 @@ function SettingsMenuTrigger() {
234
208
  popovertarget: id,
235
209
  className: CSS.MenuTrigger
236
210
  },
237
- SettingsIcon(),
211
+ InfoIcon(),
238
212
  Defer(() => SettingsMenu(id))))
239
213
  }
240
214
 
241
215
  function SettingsMenu(id) {
242
- const firstInputRef = {}
243
216
  return (
244
217
  r('menu', {
245
218
  id,
246
219
  popover: '',
247
- className: CSS.SettingsMenu,
248
- onToggle(event) {
249
- if (event.newState === 'open')
250
- firstInputRef.elem.focus()
251
- }
220
+ className: CSS.SettingsMenu
252
221
  },
253
222
 
254
223
  r('div', null,
255
- r('label', className(CSS.GroupByMethod),
256
- r('input', {
257
- name: 'group-by-method',
258
- ref: firstInputRef,
259
- type: 'checkbox',
260
- checked: store.groupByMethod,
261
- onChange: store.toggleGroupByMethod
262
- }),
263
- r('span', className(CSS.checkboxBody), t`Group by Method`)),
264
-
265
224
  r('a', {
266
225
  href: 'https://mockaton.com',
267
226
  target: '_blank',
@@ -280,6 +239,47 @@ function SettingsMenu(id) {
280
239
 
281
240
 
282
241
 
242
+ /** # Left Side */
243
+
244
+
245
+ function BulkSelector() {
246
+ const { comments } = store
247
+ const firstOption = t`Pick Comment…`
248
+ function onChange() {
249
+ const value = this.value
250
+ this.value = firstOption // hack so it’s always selected
251
+ store.bulkSelectByComment(value)
252
+ }
253
+ const disabled = !comments.length
254
+ return (
255
+ r('label', className(CSS.BulkSelector),
256
+ r('span', null, t`Bulk Select`),
257
+ r('select', {
258
+ autocomplete: 'off',
259
+ disabled,
260
+ title: disabled ? t`No mock files have comments which are anything within parentheses on the filename.` : '',
261
+ onChange
262
+ },
263
+ r('option', { value: firstOption }, firstOption),
264
+ r('hr'),
265
+ comments.map(value => r('option', { value }, value)))))
266
+ // TODO For a11y, use `menu` instead of `select`
267
+ }
268
+
269
+
270
+ function GroupByMethod() {
271
+ return (
272
+ r('label', className(CSS.GroupByMethod),
273
+ r('input', {
274
+ name: 'group-by-method',
275
+ type: 'checkbox',
276
+ checked: store.groupByMethod,
277
+ onChange: store.toggleGroupByMethod
278
+ }),
279
+ r('span', className(CSS.checkboxBody), t`Group by Method`)))
280
+ }
281
+
282
+
283
283
  /** # MockList */
284
284
 
285
285
  function MockList() {
@@ -540,7 +540,7 @@ function Resizer(ref) {
540
540
  }
541
541
 
542
542
  function onMove(event) {
543
- const MIN_LEFT_WIDTH = 340
543
+ const MIN_LEFT_WIDTH = 350
544
544
  raf = raf || requestAnimationFrame(() => {
545
545
  ref.width = Math.max(initialWidth - (initialX - event.clientX), MIN_LEFT_WIDTH) + 'px'
546
546
  ref.elem.style.width = ref.width
@@ -575,13 +575,19 @@ const payloadViewerCodeRef = {}
575
575
  function PayloadViewer() {
576
576
  return (
577
577
  r('div', className(CSS.PayloadViewer),
578
- r('h2', { ref: payloadViewerTitleRef },
579
- !store.hasChosenLink && t`Preview`),
578
+ RightToolbar(),
580
579
  r('pre', null,
581
580
  r('code', { ref: payloadViewerCodeRef },
582
581
  !store.hasChosenLink && t`Click a link to preview it`))))
583
582
  }
584
583
 
584
+ function RightToolbar() {
585
+ return r('div', className(CSS.SubToolbar),
586
+ r('h2', { ref: payloadViewerTitleRef },
587
+ !store.hasChosenLink && t`Preview`))
588
+ }
589
+
590
+
585
591
  function PayloadViewerTitle(file, statusText) {
586
592
  const { method, status, ext } = parseFilename(file)
587
593
  const fileNameWithComments = file.split('.').slice(0, -3).join('.')
@@ -731,10 +737,10 @@ function CloudIcon() {
731
737
  s('path', { d: 'm6.1 9.1c2.8 0 5 2.3 5 5' })))
732
738
  }
733
739
 
734
- function SettingsIcon() {
740
+ function InfoIcon() {
735
741
  return (
736
742
  s('svg', { viewBox: '0 0 24 24' },
737
- s('path', { d: 'M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6' })))
743
+ s('path', { d: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m1 15h-2v-6h2zm0-8h-2V7h2z' })))
738
744
  }
739
745
 
740
746
 
@@ -5,14 +5,15 @@
5
5
 
6
6
  @media (prefers-color-scheme: light) {
7
7
  :root {
8
+ color-scheme: light;
8
9
  --color4xxBackground: #ffedd1;
9
10
  --colorAccent: #0b61ec;
10
11
  --colorBackground: #fff;
11
12
  --colorComboBoxHeaderBackground: #fff;
12
13
  --colorComboBoxBackground: #eee;
13
- --colorHeaderBackground: #efefef;
14
+ --colorHeaderBackground: #f5f5f5;
14
15
  --colorSecondaryButtonBackground: #fcfcfc;
15
- --colorSecondaryActionBorder: #ddd;
16
+ --colorSecondaryActionBorder: #e0e0e0;
16
17
  --colorSecondaryAction: #666;
17
18
  --colorDisabledMockSelector: #444;
18
19
  --colorHover: rgba(119, 193, 255, 0.5);
@@ -27,6 +28,7 @@
27
28
  }
28
29
  @media (prefers-color-scheme: dark) {
29
30
  :root {
31
+ color-scheme: dark;
30
32
  --color4xxBackground: #68554a;
31
33
  --colorAccent: #2495ff;
32
34
  --colorBackground: #181818;
@@ -52,9 +54,6 @@ html,
52
54
  body {
53
55
  overflow: hidden;
54
56
  height: 100%;
55
- padding: 0;
56
- margin: 0;
57
- background: var(--colorHeaderBackground);
58
57
  font-size: 12px;
59
58
  }
60
59
  body {
@@ -72,7 +71,6 @@ body {
72
71
  margin: 0;
73
72
  font-family: inherit;
74
73
  font-size: 100%;
75
- outline: 0;
76
74
  scrollbar-width: thin;
77
75
  }
78
76
 
@@ -88,6 +86,7 @@ a,
88
86
  select,
89
87
  button,
90
88
  input[type=checkbox] {
89
+ outline: 0;
91
90
  cursor: pointer;
92
91
  }
93
92
 
@@ -100,6 +99,7 @@ input[type=checkbox]:active {
100
99
 
101
100
  a {
102
101
  text-decoration: none;
102
+ color: var(--colorAccent);
103
103
  }
104
104
 
105
105
  select {
@@ -122,13 +122,13 @@ select {
122
122
  }
123
123
  &:disabled {
124
124
  cursor: not-allowed;
125
- opacity: 0.5;
125
+ opacity: 50%;
126
126
  }
127
127
  }
128
128
 
129
129
  header {
130
130
  display: flex;
131
- width: 100vw;
131
+ flex: 0 0 100%;
132
132
  padding: 16px;
133
133
  border-bottom: 1px solid var(--colorSecondaryActionBorder);
134
134
  background: var(--colorHeaderBackground);
@@ -137,6 +137,16 @@ header {
137
137
  align-self: end;
138
138
  margin-right: 22px;
139
139
  margin-bottom: 3px;
140
+ opacity: 85%;
141
+ transition: opacity 240ms ease-in-out;
142
+
143
+ &:hover {
144
+ opacity: 1;
145
+ }
146
+
147
+ @media (max-width: 730px) {
148
+ display: none;
149
+ }
140
150
 
141
151
  svg {
142
152
  width: 120px;
@@ -150,17 +160,12 @@ header {
150
160
  width: 100%;
151
161
  flex-wrap: wrap;
152
162
  align-items: flex-end;
153
- gap: 16px 8px;
154
- }
163
+ gap: 16px 12px;
155
164
 
156
- @media (max-width: 830px) {
157
- .Logo {
158
- display: none;
159
- }
160
- }
161
- @media (max-width: 690px) {
162
- > div .MenuTrigger {
163
- margin-left: unset;
165
+ @media (max-width: 590px) {
166
+ .MenuTrigger {
167
+ margin-left: unset;
168
+ }
164
169
  }
165
170
  }
166
171
 
@@ -168,112 +173,6 @@ header {
168
173
  display: flex;
169
174
  }
170
175
 
171
- .Field {
172
- width: 116px;
173
-
174
- span {
175
- display: flex;
176
- align-items: center;
177
- margin-left: 8px;
178
- color: var(--colorLabel);
179
- font-size: 11px;
180
- gap: 4px;
181
- }
182
-
183
- input[type=url],
184
- input[type=number],
185
- select {
186
- width: 100%;
187
- height: 28px;
188
- padding: 4px 8px;
189
- border: 1px solid var(--colorSecondaryActionBorder);
190
- margin-top: 2px;
191
- color: var(--colorText);
192
- font-size: 11px;
193
- background-color: var(--colorComboBoxHeaderBackground);
194
- border-radius: var(--radius);
195
- }
196
-
197
- select:enabled:hover {
198
- border-color: var(--colorSecondaryActionBorder);
199
- background: var(--colorHover);
200
- }
201
-
202
- &.GlobalDelayField {
203
- position: relative;
204
- width: 76px;
205
-
206
- input {
207
- padding-right: 6px;
208
- border-bottom-right-radius: 0;
209
- border-top-right-radius: 0;
210
- }
211
-
212
- svg {
213
- width: 12px;
214
- height: 12px;
215
- border: 1px solid var(--colorLabel);
216
- stroke-width: 3px;
217
- border-radius: 50%;
218
- }
219
-
220
- &:focus-within {
221
- z-index: 100;
222
- }
223
- }
224
-
225
- &.GlobalDelayJitterField {
226
- width: 80px;
227
- span {
228
- margin-left: 4px;
229
- }
230
- input {
231
- border-left: 2px solid transparent;
232
- border-bottom-left-radius: 0;
233
- border-top-left-radius: 0;
234
- }
235
- &:focus-within {
236
- z-index: 100;
237
- }
238
- }
239
-
240
- &.CookieSelector {
241
- width: 100px;
242
- }
243
-
244
- &.FallbackBackend {
245
- position: relative;
246
- width: 150px;
247
-
248
- .SaveProxiedCheckbox {
249
- position: absolute;
250
- top: 0;
251
- right: 0;
252
- display: flex;
253
- width: auto;
254
- min-width: unset;
255
- align-items: center;
256
- font-size: 11px;
257
- gap: 4px;
258
-
259
- input + .checkboxBody {
260
- margin: 0;
261
- margin-right: 4px;
262
- }
263
- input:enabled + .checkboxBody {
264
- cursor: pointer;
265
- }
266
- input:disabled + .checkboxBody {
267
- opacity: 0.8;
268
- }
269
- }
270
- }
271
- }
272
-
273
- .BulkSelector {
274
- background-image: none;
275
- text-align-last: center;
276
- }
277
176
 
278
177
  .ResetButton {
279
178
  padding: 6px 12px;
@@ -310,6 +209,107 @@ header {
310
209
  }
311
210
  }
312
211
 
212
+ .Field {
213
+ width: 116px;
214
+
215
+ span {
216
+ display: flex;
217
+ align-items: center;
218
+ margin-left: 8px;
219
+ color: var(--colorLabel);
220
+ font-size: 11px;
221
+ gap: 4px;
222
+ }
223
+
224
+ input[type=url],
225
+ input[type=number],
226
+ select {
227
+ width: 100%;
228
+ height: 28px;
229
+ padding: 4px 8px;
230
+ border: 1px solid var(--colorSecondaryActionBorder);
231
+ margin-top: 2px;
232
+ color: var(--colorText);
233
+ font-size: 11px;
234
+ background-color: var(--colorComboBoxHeaderBackground);
235
+ border-radius: var(--radius);
236
+ }
237
+
238
+ &.GlobalDelayField {
239
+ position: relative;
240
+ width: 76px;
241
+
242
+ input {
243
+ padding-right: 6px;
244
+ border-bottom-right-radius: 0;
245
+ border-top-right-radius: 0;
246
+ }
247
+
248
+ svg {
249
+ width: 12px;
250
+ height: 12px;
251
+ border: 1px solid var(--colorLabel);
252
+ stroke-width: 3px;
253
+ border-radius: 50%;
254
+ }
255
+
256
+ &:focus-within {
257
+ z-index: 100;
258
+ }
259
+ }
260
+
261
+ &.GlobalDelayJitterField {
262
+ width: 80px;
263
+ span {
264
+ margin-left: 4px;
265
+ }
266
+ input {
267
+ border-left: 2px solid transparent;
268
+ border-bottom-left-radius: 0;
269
+ border-top-left-radius: 0;
270
+ }
271
+ &:focus-within {
272
+ z-index: 100;
273
+ }
274
+ }
275
+
276
+ &.CookieSelector {
277
+ width: 100px;
278
+ }
279
+
280
+ &.FallbackBackend {
281
+ position: relative;
282
+ width: 150px;
283
+
284
+ .SaveProxiedCheckbox {
285
+ position: absolute;
286
+ top: 0;
287
+ right: 0;
288
+ display: flex;
289
+ width: auto;
290
+ min-width: unset;
291
+ align-items: center;
292
+ font-size: 11px;
293
+ gap: 4px;
294
+
295
+ input + .checkboxBody {
296
+ margin: 0;
297
+ margin-right: 4px;
298
+ }
299
+ input:enabled + .checkboxBody {
300
+ cursor: pointer;
301
+ }
302
+ input:disabled {
303
+ cursor: not-allowed;
304
+ }
305
+ input:disabled + .checkboxBody {
306
+ opacity: 0.8;
307
+ cursor: not-allowed;
308
+ }
309
+ }
310
+ }
311
+ }
312
+
313
313
  .SettingsMenu {
314
314
  position: absolute;
315
315
  top: 62px;
@@ -329,20 +329,9 @@ header {
329
329
  gap: 12px;
330
330
  text-align: left;
331
331
 
332
- a {
333
- color: var(--colorAccent);
334
- }
335
-
336
- .GroupByMethod {
337
- display: flex;
338
- align-items: center;
339
- gap: 6px;
340
- cursor: pointer;
341
- }
342
332
  }
343
333
  }
344
334
 
345
-
346
335
  main {
347
336
  display: flex;
348
337
  min-width: 0;
@@ -356,19 +345,15 @@ main {
356
345
  }
357
346
 
358
347
  .leftSide {
359
- min-width: 100%;
360
- min-height: 50%;
361
- max-height: 50%;
362
- border: 0;
348
+ width: 100% !important;
349
+ height: 50%;
350
+ border-right: 0;
363
351
  }
364
352
  }
365
353
 
366
354
  .leftSide {
367
355
  width: 50%; /* resizable in js */
368
- padding: 16px;
369
356
  border-right: 1px solid var(--colorSecondaryActionBorder);
370
- user-select: none;
371
- overflow-y: auto;
372
357
  box-shadow: var(--boxShadow1);
373
358
  }
374
359
 
@@ -394,10 +379,47 @@ main {
394
379
  }
395
380
  }
396
381
 
382
+ .SubToolbar {
383
+ display: flex;
384
+ height: 42px;
385
+ align-items: center;
386
+ justify-content: space-between;
387
+ padding: 0 16px;
388
+ border-bottom: 1px solid var(--colorSecondaryActionBorder);
389
+ background: var(--colorHeaderBackground);
390
+ }
391
+
392
+ .GroupByMethod {
393
+ display: flex;
394
+ align-items: center;
395
+ gap: 6px;
396
+ cursor: pointer;
397
+ }
398
+
399
+ .BulkSelector {
400
+ select {
401
+ width: 110px;
402
+ padding: 6px 8px;
403
+ border: 1px solid var(--colorSecondaryActionBorder);
404
+ margin-left: 4px;
405
+ background-image: none;
406
+ text-align-last: center;
407
+ color: var(--colorText);
408
+ font-size: 11px;
409
+ background-color: var(--colorComboBoxHeaderBackground);
410
+ border-radius: var(--radius);
411
+ }
412
+ }
413
+
397
414
  .Table {
415
+ height: 100%;
416
+ padding: 16px;
417
+ user-select: none;
418
+ overflow-y: auto;
419
+
398
420
  .TableHeading {
399
421
  padding-bottom: 4px;
400
- padding-left: 4px;
422
+ padding-left: 1px;
401
423
  border-top: 24px solid transparent;
402
424
  margin-left: 74px;
403
425
  font-weight: bold;
@@ -430,7 +452,6 @@ main {
430
452
  }
431
453
  }
432
454
  }
433
-
434
455
  @keyframes _kfRowIn {
435
456
  to {
436
457
  opacity: 1;
@@ -459,7 +480,6 @@ main {
459
480
  padding: 6px 8px;
460
481
  margin-left: 4px;
461
482
  border-radius: var(--radius);
462
- color: var(--colorAccent);
463
483
  word-break: break-word;
464
484
 
465
485
 
@@ -651,15 +671,7 @@ main {
651
671
 
652
672
 
653
673
  .PayloadViewer {
654
- display: flex;
655
674
  height: 100%;
656
- flex-direction: column;
657
- padding-top: 16px;
658
-
659
- > h2 {
660
- padding-bottom: 4px;
661
- padding-left: 16px;
662
- }
663
675
 
664
676
  > pre {
665
677
  overflow: auto;
@@ -750,7 +762,6 @@ main {
750
762
  background: rgba(0, 0, 0, 0.5);
751
763
  }
752
764
  }
753
-
754
765
  @keyframes _kfToastIn {
755
766
  to {
756
767
  opacity: 1;