mockaton 8.13.0 → 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.13.0",
5
+ "version": "8.13.1",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
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;
@@ -292,7 +297,6 @@ select {
292
297
 
293
298
  span {
294
299
  opacity: 0.5;
295
- filter: saturate(0.5);
296
300
  }
297
301
 
298
302
  &:hover {
@@ -359,7 +363,6 @@ select {
359
363
  .DelayToggler {
360
364
  > input {
361
365
  &:checked ~ svg {
362
- border: 1px solid var(--colorBackground);
363
366
  fill: var(--colorAccent);
364
367
  background: var(--colorAccent);
365
368
  stroke: var(--colorBackground);
@@ -373,11 +376,12 @@ select {
373
376
  }
374
377
 
375
378
  > svg {
376
- width: 18px;
377
- height: 18px;
379
+ width: 19px;
380
+ height: 19px;
378
381
  stroke-width: 2.5px;
379
382
  border-radius: 50%;
380
383
  background: var(--colorSecondaryButtonBackground);
384
+ box-shadow: var(--boxShadow1);
381
385
  }
382
386
  }
383
387
 
@@ -385,10 +389,12 @@ select {
385
389
  padding: 1px 3px;
386
390
  background: var(--colorSecondaryButtonBackground);
387
391
  border-radius: var(--radiusSmall);
392
+ box-shadow: var(--boxShadow1);
388
393
 
389
394
  &:has(input:checked),
390
395
  &:has(input:disabled) {
391
396
  background: transparent;
397
+ box-shadow: none;
392
398
  }
393
399
 
394
400
  > input {
@@ -456,6 +462,7 @@ select {
456
462
  color: var(--colorSecondaryAction);
457
463
  border-radius: var(--radiusSmall);
458
464
  background: var(--colorSecondaryButtonBackground);
465
+ box-shadow: var(--boxShadow1);
459
466
 
460
467
  &:hover {
461
468
  background: var(--colorLightRed);
@@ -510,7 +517,7 @@ select {
510
517
  display: inline-block;
511
518
  padding: 6px;
512
519
  border-radius: var(--radius);
513
- color: var(--colorAccentAlt);
520
+ color: var(--colorAccent);
514
521
  text-decoration: none;
515
522
 
516
523
  &:hover {
@@ -519,7 +526,6 @@ select {
519
526
 
520
527
  span {
521
528
  opacity: 0.5;
522
- filter: saturate(0.5);
523
529
  }
524
530
  }
525
531
  }
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,13 +247,12 @@ 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 }) {
@@ -258,20 +261,20 @@ function SectionByMethod({ method, brokers, canProxy }) {
258
261
  .sort((a, b) => a[0].localeCompare(b[0]))
259
262
 
260
263
  const urlMasks = brokersSorted.map(([urlMask]) => urlMask)
261
- const urlMasksHighlighted = highlightCommonPathPrefixes(urlMasks)
264
+ const urlMasksDittoed = dittoSplitPaths(urlMasks)
262
265
  return (
263
266
  r('tbody', null,
264
267
  r('th', null, method),
265
268
  brokersSorted.map(([urlMask, broker], i) =>
266
269
  r('tr', { 'data-method': method, 'data-urlMask': urlMask },
267
- r('td', null, r(PreviewLink, { method, urlMask, urlMaskHighlighted: urlMasksHighlighted[i] })),
270
+ r('td', null, r(PreviewLink, { method, urlMask, urlMaskDittoed: urlMasksDittoed[i] })),
268
271
  r('td', null, r(MockSelector, { broker })),
269
272
  r('td', null, r(InternalServerErrorToggler, { broker })),
270
273
  r('td', null, r(DelayRouteToggler, { broker })),
271
274
  r('td', null, r(ProxyToggler, { broker, disabled: !canProxy }))))))
272
275
  }
273
276
 
274
- function PreviewLink({ method, urlMask, urlMaskHighlighted }) {
277
+ function PreviewLink({ method, urlMask, urlMaskDittoed }) {
275
278
  async function onClick(event) {
276
279
  event.preventDefault()
277
280
  try {
@@ -283,13 +286,15 @@ function PreviewLink({ method, urlMask, urlMaskHighlighted }) {
283
286
  onError(error)
284
287
  }
285
288
  }
289
+ const [ditto, tail] = urlMaskDittoed
286
290
  return (
287
291
  r('a', {
288
292
  className: CSS.PreviewLink,
289
293
  href: urlMask,
290
- onClick,
291
- innerHTML: urlMaskHighlighted
292
- }))
294
+ onClick
295
+ }, ditto
296
+ ? [r('span', null, ditto), tail]
297
+ : tail))
293
298
  }
294
299
 
295
300
  function MockSelector({ broker }) {
@@ -496,7 +501,9 @@ function mockSelectorFor(method, urlMask) {
496
501
  function StaticFilesList({ staticFiles }) {
497
502
  if (!staticFiles.length)
498
503
  return null
499
- const highlighted = highlightCommonPathPrefixes(staticFiles)
504
+ const paths = dittoSplitPaths(staticFiles).map(([ditto, tail]) => ditto
505
+ ? [r('span', null, ditto), tail]
506
+ : tail)
500
507
  return (
501
508
  r('section', {
502
509
  open: true,
@@ -507,9 +514,8 @@ function StaticFilesList({ staticFiles }) {
507
514
  r('li', null,
508
515
  r('a', {
509
516
  href: f,
510
- target: '_blank',
511
- innerHTML: highlighted[i]
512
- }))))))
517
+ target: '_blank'
518
+ }, paths[i]))))))
513
519
  }
514
520
 
515
521
 
@@ -619,37 +625,59 @@ function useRef() {
619
625
 
620
626
 
621
627
 
622
-
623
- /** This is for styling the repeated paths with a faint style */
624
- function highlightCommonPathPrefixes(paths) {
625
- const seen = []
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) {
626
634
  const result = []
627
- for (const path of paths) {
628
- let longestPrefixSegments = []
629
-
630
- for (const prev of seen) {
631
- const currSegs = path.split('/')
632
- const prevSegs = prev.split('/')
633
-
634
- let i = 0
635
- while (i < currSegs.length && i < prevSegs.length && currSegs[i] === prevSegs[i])
636
- i++
637
-
638
- if (i > longestPrefixSegments.length)
639
- longestPrefixSegments = currSegs.slice(0, i)
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)
640
652
  }
641
653
 
642
- if (longestPrefixSegments.length > 0) {
643
- let prefix = longestPrefixSegments.join('/')
644
- if (!prefix.endsWith('/'))
645
- prefix += '/' // always end with slash for dirs
646
- const suffix = path.slice(prefix.length)
647
- result.push(`<span>${prefix}</span>${suffix}`)
654
+ if (!dittoParts.length)
655
+ result.push(['', path])
656
+ else {
657
+ const ditto = dittoParts.join('/') + '/'
658
+ result.push([ditto, path.slice(ditto.length)])
648
659
  }
649
- else
650
- result.push(path)
651
-
652
- seen.push(path)
653
660
  }
661
+
654
662
  return result
655
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
+ }())