mockaton 8.12.10 → 8.13.1

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
@@ -75,9 +75,7 @@ They will be saved in your `config.mocksDir` following the filename convention.
75
75
  <br/>
76
76
 
77
77
 
78
- ## Basic Usage
79
- Mockaton is a Node.js program.
80
-
78
+ ## Basic Usage (See below for Node < 23.6)
81
79
  ```sh
82
80
  npm install mockaton --save-dev
83
81
  ```
@@ -98,6 +96,14 @@ Mockaton({
98
96
  node my-mockaton.js
99
97
  ```
100
98
 
99
+ ### Node < 23.6 + TypeScript
100
+ If you want to write mocks in TypeScript in a version older than Node 23.6:
101
+ ```shell
102
+ npm install tsx
103
+ node --import=tsx my-mockaton.js
104
+ ```
105
+
106
+
101
107
  <br/>
102
108
 
103
109
  ## Demo App (Vite)
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "mockaton",
3
3
  "description": "HTTP Mock Server",
4
4
  "type": "module",
5
- "version": "8.12.10",
5
+ "version": "8.13.1",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
@@ -2,17 +2,17 @@ const MOUNT = '/mockaton'
2
2
  export const API = {
3
3
  dashboard: MOUNT,
4
4
  bulkSelect: MOUNT + '/bulk-select-by-comment',
5
+ collectProxied: MOUNT + '/collect-proxied',
5
6
  comments: MOUNT + '/comments',
6
- select: MOUNT + '/select',
7
+ cookies: MOUNT + '/cookies',
8
+ cors: MOUNT + '/cors',
7
9
  delay: MOUNT + '/delay',
10
+ fallback: MOUNT + '/fallback',
8
11
  globalDelay: MOUNT + '/global-delay',
9
12
  mocks: MOUNT + '/mocks',
10
- reset: MOUNT + '/reset',
11
- cookies: MOUNT + '/cookies',
12
- fallback: MOUNT + '/fallback',
13
- collectProxied: MOUNT + '/collect-proxied',
14
13
  proxied: MOUNT + '/proxied',
15
- cors: MOUNT + '/cors',
14
+ reset: MOUNT + '/reset',
15
+ select: MOUNT + '/select',
16
16
  static: MOUNT + '/static',
17
17
  syncVersion: MOUNT + '/sync_version'
18
18
  }
package/src/Dashboard.css CHANGED
@@ -1,19 +1,19 @@
1
1
  :root {
2
2
  --boxShadow1: 0 2px 1px -1px rgba(0, 0, 0, 0.15), 0 1px 1px 0 rgba(0, 0, 0, 0.15), 0 1px 3px 0 rgba(0, 0, 0, 0.1);
3
- --radius: 6px;
3
+ --radius: 4px;
4
4
  --radiusSmall: 4px;
5
5
  }
6
6
 
7
7
  @media (prefers-color-scheme: light) {
8
8
  :root {
9
9
  --color4xxBackground: #ffedd1;
10
- --colorAccent: #0075db;
10
+ --colorAccent: #0170cc;
11
11
  --colorAccentAlt: #068185;
12
12
  --colorBackground: #fff;
13
13
  --colorComboBoxHeaderBackground: #fff;
14
- --colorComboBoxBackground: #f7f7f7;
14
+ --colorComboBoxBackground: #eee;
15
15
  --colorHeaderBackground: #eee;
16
- --colorSecondaryButtonBackground: #f3f3f3;
16
+ --colorSecondaryButtonBackground: #eee;
17
17
  --colorSecondaryAction: #555;
18
18
  --colorDisabledMockSelector: #444;
19
19
  --colorHover: #dfefff;
@@ -27,11 +27,10 @@
27
27
  :root {
28
28
  --color4xxBackground: #403630;
29
29
  --colorAccent: #2495ff;
30
- --colorAccentAlt: #00bf64;
31
30
  --colorBackground: #161616;
32
31
  --colorHeaderBackground: #090909;
33
- --colorComboBoxBackground: #252525;
34
- --colorSecondaryButtonBackground: #282828;
32
+ --colorComboBoxBackground: #2a2a2a;
33
+ --colorSecondaryButtonBackground: #2a2a2a;
35
34
  --colorSecondaryAction: #999;
36
35
  --colorComboBoxHeaderBackground: #222;
37
36
  --colorDisabledMockSelector: #b9b9b9;
@@ -227,6 +226,11 @@ select {
227
226
  }
228
227
 
229
228
 
229
+ .Main {
230
+ display: flex;
231
+ }
232
+
233
+
230
234
  .MockList {
231
235
  display: flex;
232
236
  align-items: flex-start;
@@ -254,6 +258,7 @@ select {
254
258
 
255
259
  .PayloadViewer {
256
260
  position: sticky;
261
+ margin-top: 62px;
257
262
  top: 62px;
258
263
  width: 50%;
259
264
  margin-left: 20px;
@@ -290,6 +295,10 @@ select {
290
295
  text-decoration: none;
291
296
  word-break: break-word;
292
297
 
298
+ span {
299
+ opacity: 0.5;
300
+ }
301
+
293
302
  &:hover {
294
303
  background: var(--colorHover);
295
304
  }
@@ -354,7 +363,6 @@ select {
354
363
  .DelayToggler {
355
364
  > input {
356
365
  &:checked ~ svg {
357
- border: 1px solid var(--colorBackground);
358
366
  fill: var(--colorAccent);
359
367
  background: var(--colorAccent);
360
368
  stroke: var(--colorBackground);
@@ -368,11 +376,12 @@ select {
368
376
  }
369
377
 
370
378
  > svg {
371
- width: 18px;
372
- height: 18px;
379
+ width: 19px;
380
+ height: 19px;
373
381
  stroke-width: 2.5px;
374
382
  border-radius: 50%;
375
383
  background: var(--colorSecondaryButtonBackground);
384
+ box-shadow: var(--boxShadow1);
376
385
  }
377
386
  }
378
387
 
@@ -380,10 +389,12 @@ select {
380
389
  padding: 1px 3px;
381
390
  background: var(--colorSecondaryButtonBackground);
382
391
  border-radius: var(--radiusSmall);
392
+ box-shadow: var(--boxShadow1);
383
393
 
384
394
  &:has(input:checked),
385
395
  &:has(input:disabled) {
386
396
  background: transparent;
397
+ box-shadow: none;
387
398
  }
388
399
 
389
400
  > input {
@@ -451,6 +462,7 @@ select {
451
462
  color: var(--colorSecondaryAction);
452
463
  border-radius: var(--radiusSmall);
453
464
  background: var(--colorSecondaryButtonBackground);
465
+ box-shadow: var(--boxShadow1);
454
466
 
455
467
  &:hover {
456
468
  background: var(--colorLightRed);
@@ -505,12 +517,16 @@ select {
505
517
  display: inline-block;
506
518
  padding: 6px;
507
519
  border-radius: var(--radius);
508
- color: var(--colorAccentAlt);
520
+ color: var(--colorAccent);
509
521
  text-decoration: none;
510
522
 
511
523
  &:hover {
512
524
  text-decoration: underline;
513
525
  }
526
+
527
+ span {
528
+ opacity: 0.5;
529
+ }
514
530
  }
515
531
  }
516
532
 
package/src/Dashboard.js CHANGED
@@ -51,6 +51,7 @@ const CSS = {
51
51
  GlobalDelayField: 'GlobalDelayField',
52
52
  SaveProxiedCheckbox: 'SaveProxiedCheckbox',
53
53
  StaticFilesList: 'StaticFilesList',
54
+ Main: 'Main',
54
55
 
55
56
  red: 'red',
56
57
  empty: 'empty',
@@ -89,8 +90,11 @@ function App([brokersByMethod, cookies, comments, delay, collectProxied, fallbac
89
90
  return (
90
91
  r('div', null,
91
92
  r(Header, { cookies, comments, delay, fallbackAddress, collectProxied }),
92
- r(MockList, { brokersByMethod, canProxy: Boolean(fallbackAddress) }),
93
- r(StaticFilesList, { staticFiles })))
93
+ r('main', { className: CSS.Main },
94
+ r('div', null,
95
+ r(MockList, { brokersByMethod, canProxy: Boolean(fallbackAddress) }),
96
+ r(StaticFilesList, { staticFiles })),
97
+ r(PayloadViewer))))
94
98
  }
95
99
 
96
100
 
@@ -243,32 +247,34 @@ function MockList({ brokersByMethod, canProxy }) {
243
247
  const hasMocks = Object.keys(brokersByMethod).length
244
248
  if (!hasMocks)
245
249
  return (
246
- r('main', { className: cssClass(CSS.MockList, CSS.empty) },
250
+ r('div', { className: cssClass(CSS.MockList, CSS.empty) },
247
251
  Strings.no_mocks_found))
248
252
  return (
249
- r('main', { className: CSS.MockList },
253
+ r('div', { className: CSS.MockList },
250
254
  r('table', null, Object.entries(brokersByMethod).map(([method, brokers]) =>
251
- r(SectionByMethod, { method, brokers, canProxy }))),
252
- r(PayloadViewer)))
255
+ r(SectionByMethod, { method, brokers, canProxy })))))
253
256
  }
254
257
 
255
258
  function SectionByMethod({ method, brokers, canProxy }) {
259
+ const brokersSorted = Object.entries(brokers)
260
+ .filter(([, broker]) => broker.mocks.length > 1) // >1 because of autogen500
261
+ .sort((a, b) => a[0].localeCompare(b[0]))
262
+
263
+ const urlMasks = brokersSorted.map(([urlMask]) => urlMask)
264
+ const urlMasksDittoed = dittoSplitPaths(urlMasks)
256
265
  return (
257
266
  r('tbody', null,
258
267
  r('th', null, method),
259
- Object.entries(brokers)
260
- .filter(([, broker]) => broker.mocks.length > 1) // >1 because of autogen500
261
- .sort((a, b) => a[0].localeCompare(b[0]))
262
- .map(([urlMask, broker]) =>
263
- r('tr', { 'data-method': method, 'data-urlMask': urlMask },
264
- r('td', null, r(PreviewLink, { method, urlMask })),
265
- r('td', null, r(MockSelector, { broker })),
266
- r('td', null, r(InternalServerErrorToggler, { broker })),
267
- r('td', null, r(DelayRouteToggler, { broker })),
268
- r('td', null, r(ProxyToggler, { broker, disabled: !canProxy }))))))
269
- }
270
-
271
- function PreviewLink({ method, urlMask }) {
268
+ brokersSorted.map(([urlMask, broker], i) =>
269
+ r('tr', { 'data-method': method, 'data-urlMask': urlMask },
270
+ r('td', null, r(PreviewLink, { method, urlMask, urlMaskDittoed: urlMasksDittoed[i] })),
271
+ r('td', null, r(MockSelector, { broker })),
272
+ r('td', null, r(InternalServerErrorToggler, { broker })),
273
+ r('td', null, r(DelayRouteToggler, { broker })),
274
+ r('td', null, r(ProxyToggler, { broker, disabled: !canProxy }))))))
275
+ }
276
+
277
+ function PreviewLink({ method, urlMask, urlMaskDittoed }) {
272
278
  async function onClick(event) {
273
279
  event.preventDefault()
274
280
  try {
@@ -280,12 +286,15 @@ function PreviewLink({ method, urlMask }) {
280
286
  onError(error)
281
287
  }
282
288
  }
289
+ const [ditto, tail] = urlMaskDittoed
283
290
  return (
284
291
  r('a', {
285
292
  className: CSS.PreviewLink,
286
293
  href: urlMask,
287
294
  onClick
288
- }, urlMask))
295
+ }, ditto
296
+ ? [r('span', null, ditto), tail]
297
+ : tail))
289
298
  }
290
299
 
291
300
  function MockSelector({ broker }) {
@@ -492,15 +501,21 @@ function mockSelectorFor(method, urlMask) {
492
501
  function StaticFilesList({ staticFiles }) {
493
502
  if (!staticFiles.length)
494
503
  return null
504
+ const paths = dittoSplitPaths(staticFiles).map(([ditto, tail]) => ditto
505
+ ? [r('span', null, ditto), tail]
506
+ : tail)
495
507
  return (
496
508
  r('section', {
497
509
  open: true,
498
510
  className: CSS.StaticFilesList
499
511
  },
500
512
  r('h2', null, Strings.static_get),
501
- r('ul', null, staticFiles.map(f =>
513
+ r('ul', null, staticFiles.map((f, i) =>
502
514
  r('li', null,
503
- r('a', { href: f, target: '_blank' }, f))))))
515
+ r('a', {
516
+ href: f,
517
+ target: '_blank'
518
+ }, paths[i]))))))
504
519
  }
505
520
 
506
521
 
@@ -607,3 +622,62 @@ function createSvgElement(tagName, props, ...children) {
607
622
  function useRef() {
608
623
  return { current: null }
609
624
  }
625
+
626
+
627
+
628
+ /**
629
+ * This is for styling the repeated paths with a faint style.
630
+ * It splits each path into [dittoPrefix, tail], where dittoPrefix is
631
+ * the longest previously-seen common directory prefix.
632
+ */
633
+ function dittoSplitPaths(paths) {
634
+ const result = []
635
+ for (let i = 0; i < paths.length; i++) {
636
+ const path = paths[i]
637
+ const currParts = path.split('/')
638
+
639
+ let dittoParts = []
640
+ for (let j = 0; j < i; j++) {
641
+ const prevParts = paths[j].split('/')
642
+
643
+ let k = 0
644
+ while (
645
+ k < currParts.length &&
646
+ k < prevParts.length &&
647
+ currParts[k] === prevParts[k])
648
+ k++
649
+
650
+ if (k > dittoParts.length)
651
+ dittoParts = currParts.slice(0, k)
652
+ }
653
+
654
+ if (!dittoParts.length)
655
+ result.push(['', path])
656
+ else {
657
+ const ditto = dittoParts.join('/') + '/'
658
+ result.push([ditto, path.slice(ditto.length)])
659
+ }
660
+ }
661
+
662
+ return result
663
+ }
664
+
665
+ (function testDittoSplitPaths() {
666
+ const input = [
667
+ '/api/user',
668
+ '/api/user/avatar',
669
+ '/api/user/friends',
670
+ '/api/vid',
671
+ '/api/video/id',
672
+ '/api/video/stats'
673
+ ]
674
+ const expected = [
675
+ ['', '/api/user'],
676
+ ['/api/user/', 'avatar'],
677
+ ['/api/user/', 'friends'],
678
+ ['/api/', 'vid'],
679
+ ['/api/', 'video/id'],
680
+ ['/api/video/', 'stats']
681
+ ]
682
+ console.assert(JSON.stringify(dittoSplitPaths(input)) === JSON.stringify(expected))
683
+ }())
package/src/Watcher.js CHANGED
@@ -29,7 +29,7 @@ export function watchMocksDir() {
29
29
  if (!file)
30
30
  return
31
31
  if (isFile(join(dir, file))) {
32
- if (mockBrokerCollection.registerMock(file, 'isFromWatcher'))
32
+ if (mockBrokerCollection.registerMock(file, Boolean('isFromWatcher')))
33
33
  uiSyncVersion.increment()
34
34
  }
35
35
  else {
@@ -36,7 +36,7 @@ export function init() {
36
36
  }
37
37
 
38
38
  /** @returns {boolean} registered */
39
- export function registerMock(file, isFromWatcher) {
39
+ export function registerMock(file, isFromWatcher = false) {
40
40
  if (findBrokerByFilename(file)?.hasMock(file)
41
41
  || !isFileAllowed(file)
42
42
  || !filenameIsValid(file))