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 +9 -3
- package/package.json +1 -1
- package/src/ApiConstants.js +6 -6
- package/src/Dashboard.css +27 -11
- package/src/Dashboard.js +96 -22
- package/src/Watcher.js +1 -1
- package/src/mockBrokersCollection.js +1 -1
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
package/src/ApiConstants.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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:
|
|
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: #
|
|
10
|
+
--colorAccent: #0170cc;
|
|
11
11
|
--colorAccentAlt: #068185;
|
|
12
12
|
--colorBackground: #fff;
|
|
13
13
|
--colorComboBoxHeaderBackground: #fff;
|
|
14
|
-
--colorComboBoxBackground: #
|
|
14
|
+
--colorComboBoxBackground: #eee;
|
|
15
15
|
--colorHeaderBackground: #eee;
|
|
16
|
-
--colorSecondaryButtonBackground: #
|
|
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: #
|
|
34
|
-
--colorSecondaryButtonBackground: #
|
|
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:
|
|
372
|
-
height:
|
|
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(--
|
|
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(
|
|
93
|
-
|
|
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('
|
|
250
|
+
r('div', { className: cssClass(CSS.MockList, CSS.empty) },
|
|
247
251
|
Strings.no_mocks_found))
|
|
248
252
|
return (
|
|
249
|
-
r('
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
r('
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
},
|
|
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', {
|
|
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))
|