mockaton 13.3.0 → 13.3.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.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "mockaton",
3
3
  "description": "HTTP Mock Server",
4
4
  "type": "module",
5
- "version": "13.3.0",
5
+ "version": "13.3.2",
6
6
  "exports": {
7
7
  ".": {
8
8
  "import": "./index.js",
@@ -108,7 +108,6 @@ export const store = {
108
108
  },
109
109
 
110
110
  setProxyFallback(value) {
111
- store.skipNextRender = true
112
111
  store._request(() => api.setProxyFallback(value), () => {
113
112
  store.proxyFallback = value
114
113
  })
@@ -175,7 +174,7 @@ export const store = {
175
174
  arr.push(...Object.values(brokers))
176
175
  return arr
177
176
  },
178
-
177
+
179
178
 
180
179
  previewLink(method, urlMask) {
181
180
  store.setChosenLink(method, urlMask)
@@ -13,7 +13,7 @@
13
13
  --colorText: light-dark(#000, #fff);
14
14
 
15
15
  --colorAccent: light-dark(#0059dd, #2495ff);
16
- --colorHover: light-dark(#bbe0ff, #062d59);
16
+ --colorHover: light-dark(#c1e2ff, #062d59);
17
17
 
18
18
  --colorRed: light-dark(#da0f00, #f41606);
19
19
  --colorPink: light-dark(#ed206a, #f92672);
@@ -50,6 +50,11 @@ body {
50
50
  corner-shape: squircle;
51
51
  }
52
52
 
53
+ label,
54
+ button {
55
+ user-select: none;
56
+ }
57
+
53
58
  a:focus-visible,
54
59
  input:focus-visible,
55
60
  select:focus-visible,
@@ -432,7 +437,7 @@ main {
432
437
  }
433
438
 
434
439
  &.canProxy {
435
- margin-left: 100px;
440
+ margin-left: 124px;
436
441
  }
437
442
  }
438
443
 
@@ -449,6 +454,7 @@ main {
449
454
 
450
455
  .FolderGroup {
451
456
  position: relative;
457
+ border-radius: var(--radius);
452
458
 
453
459
  > summary {
454
460
  left: 0;
@@ -465,7 +471,6 @@ main {
465
471
  text-overflow: ellipsis;
466
472
  border-radius: var(--radius);
467
473
 
468
-
469
474
  &:active {
470
475
  cursor: grabbing;
471
476
  }
@@ -475,13 +480,20 @@ main {
475
480
  }
476
481
 
477
482
  &:hover {
478
- color: var(--colorText);
483
+ background: var(--colorHover);
479
484
  }
480
485
 
481
486
  .FolderName {
482
487
  margin-left: 125px;
488
+ &.canProxy {
489
+ margin-left: 155px;
490
+ }
491
+
483
492
  &.groupedByMethod {
484
493
  margin-left: 79px;
494
+ &.canProxy {
495
+ margin-left: 109px;
496
+ }
485
497
  }
486
498
  }
487
499
 
@@ -503,17 +515,23 @@ main {
503
515
  }
504
516
  }
505
517
 
506
- &[open] > summary {
507
- position: absolute;
508
- top: 0;
509
- left: 0;
510
- width: 16px;
511
-
512
- .FolderChevron {
513
- transform: rotate(0deg);
518
+ &[open] {
519
+ &:has(summary:hover) {
520
+ background: linear-gradient(90deg, var(--colorHover), var(--colorBg));
514
521
  }
515
- .FolderName {
516
- display: none;
522
+
523
+ > summary {
524
+ position: absolute;
525
+ top: 0;
526
+ left: 0;
527
+ width: 16px;
528
+
529
+ .FolderChevron {
530
+ transform: rotate(0deg);
531
+ }
532
+ .FolderName {
533
+ display: none;
534
+ }
517
535
  }
518
536
  }
519
537
  }
package/src/client/app.js CHANGED
@@ -106,24 +106,25 @@ function MockList() {
106
106
  }
107
107
 
108
108
  function FolderGroups(groups) {
109
- return groups.map(({ folder, children }) => {
110
- if (children.length === 1)
111
- return Row(children[0], 0)
112
-
113
- return r('details', {
109
+ return groups.map(({ folder, children }) => children.length === 1
110
+ ? Row(children[0], 0)
111
+ : r('details', {
114
112
  className: CSS.FolderGroup,
115
113
  open: !store.collapsedFolders.has(folder),
116
114
  onToggle() {
117
- // TODO alt+click exclusive open
118
115
  store.setFolderCollapsed(folder, !this.open)
119
116
  }
120
117
  },
121
118
  r('summary', null,
122
119
  r('span', { className: CSS.FolderChevron }, ChevronDownIcon()),
123
- r('span', { className: classNames(CSS.FolderName, store.groupByMethod && CSS.groupedByMethod) },
120
+ r('span', {
121
+ className: classNames(
122
+ CSS.FolderName,
123
+ store.groupByMethod && CSS.groupedByMethod,
124
+ store.canProxy && CSS.canProxy)
125
+ },
124
126
  folder + '…')),
125
- children.map(Row))
126
- })
127
+ children.map(Row)))
127
128
  }
128
129
 
129
130
  /**
package/src/server/Api.js CHANGED
@@ -88,6 +88,7 @@ function getState(_, response) {
88
88
 
89
89
  function reset(_, response) {
90
90
  mockBrokersCollection.init()
91
+ cookie.init(config.cookies)
91
92
  response.ok()
92
93
  uiSyncVersion.increment()
93
94
  }
@@ -36,10 +36,10 @@ export class MockBroker {
36
36
 
37
37
  unregister(file) {
38
38
  this.mocks = this.mocks.filter(f => f !== file)
39
- const isEmpty = !this.mocks.length
40
- if (!isEmpty && this.file === file)
39
+ const brokerIsEmpty = !this.mocks.length
40
+ if (!brokerIsEmpty && this.file === file)
41
41
  this.selectDefaultFile()
42
- return isEmpty
42
+ return brokerIsEmpty
43
43
  }
44
44
 
45
45
  hasMock = file => this.mocks.includes(file)
@@ -7,7 +7,7 @@ import { mkdtempSync } from 'node:fs'
7
7
  import { randomUUID } from 'node:crypto'
8
8
  import { equal, deepEqual, match } from 'node:assert/strict'
9
9
  import { describe, test, before, beforeEach, after } from 'node:test'
10
- import { unlink, mkdir, readFile, rename, readdir, writeFile } from 'node:fs/promises'
10
+ import { unlink, mkdir, readFile, rename, readdir, writeFile, rm } from 'node:fs/promises'
11
11
 
12
12
  import { mimeFor } from './utils/mime.js'
13
13
  import { parseFilename } from '../client/Filename.js'
@@ -26,8 +26,15 @@ const proc = spawn(join(import.meta.dirname, 'cli.js'), [
26
26
  '--no-open'
27
27
  ])
28
28
 
29
- proc.stdout.on('data', data => stdout.push(data.toString()))
30
- proc.stderr.on('data', data => stderr.push(data.toString()))
29
+ const DEBUG = false
30
+ proc.stdout.on('data', data => {
31
+ stdout.push(data.toString())
32
+ DEBUG && process.stdout.write(stdout.at(-1))
33
+ })
34
+ proc.stderr.on('data', data => {
35
+ stderr.push(data.toString())
36
+ DEBUG && process.stderr.write(stdout.at(-1))
37
+ })
31
38
 
32
39
  const serverAddr = await new Promise((resolve, reject) => {
33
40
  proc.stdout.once('data', () => {
@@ -41,12 +48,14 @@ after(() => proc.kill('SIGUSR2'))
41
48
 
42
49
 
43
50
  const rmFromMocksDir = f => unlink(join(mocksDir, f))
44
- const listFromMocksDir = d => readdir(join(mocksDir, d))
45
51
  const readFromMocksDir = f => readFile(join(mocksDir, f), 'utf8')
46
52
  const writeInMocksDir = (f, data) => writeFile(join(mocksDir, f), data)
47
- const makeDirInMocks = dir => mkdir(join(mocksDir, dir), { recursive: true })
48
53
  const renameInMocksDir = (src, target) => rename(join(mocksDir, src), join(mocksDir, target))
49
54
 
55
+ const listFromMocksDir = d => readdir(join(mocksDir, d))
56
+ const rmDirFromMocks = d => rm(join(mocksDir, d), { recursive: true })
57
+ const makeDirInMocks = dir => mkdir(join(mocksDir, dir), { recursive: true })
58
+
50
59
 
51
60
  const api = new Commander(serverAddr)
52
61
 
@@ -1120,6 +1129,16 @@ describe('Registering Mocks', () => {
1120
1129
  equal(s.brokersByMethod.GET['/reg1/runtime0'].file, 'reg1/runtime0.GET.200.txt')
1121
1130
  })
1122
1131
  })
1132
+
1133
+ test('deleting a folder unregisters mocks in it', async () => {
1134
+ const fx = new Fixture('api/bulk-delete/bar.GET.200.json')
1135
+ await fx.write()
1136
+ await sleep(0)
1137
+ const nextVerPromise = resolveOnNextSyncVersion()
1138
+ await rmDirFromMocks('api/bulk-delete')
1139
+ await nextVerPromise
1140
+ equal(await fx.fetchBroker(), undefined)
1141
+ })
1123
1142
  })
1124
1143
 
1125
1144
 
@@ -1,6 +1,5 @@
1
1
  import { basename } from 'node:path'
2
2
 
3
- import { cookie } from './cookie.js'
4
3
  import { MockBroker } from './MockBroker.js'
5
4
  import { parseFilename } from '../client/Filename.js'
6
5
  import { listFilesRecursively } from './utils/fs.js'
@@ -28,26 +27,22 @@ export const all = () => collection
28
27
 
29
28
  export function init() {
30
29
  collection = {}
31
- cookie.init(config.cookies)
32
-
33
30
  listFilesRecursively(config.mocksDir)
34
31
  .sort()
35
32
  .forEach(f => registerMock(f))
36
-
37
33
  forEachBroker(b => b.selectDefaultFile())
38
34
  }
39
35
 
40
36
  /** @returns {boolean} registered */
41
37
  export function registerMock(file, isFromWatcher = false) {
42
- if (brokerByFilename(file)?.hasMock(file)
43
- || !isFileAllowed(basename(file)))
38
+ if (brokerByFilename(file)?.hasMock(file) ||
39
+ !isFileAllowed(basename(file)))
44
40
  return false
45
41
 
46
42
  const { method, urlMask } = parseFilename(file)
47
43
  collection[method] ??= {}
48
44
 
49
45
  let broker = collection[method][urlMask]
50
-
51
46
  if (!broker)
52
47
  broker = collection[method][urlMask] = new MockBroker(file)
53
48
  else
@@ -61,15 +56,26 @@ export function registerMock(file, isFromWatcher = false) {
61
56
 
62
57
  export function unregisterMock(file) {
63
58
  const broker = brokerByFilename(file)
64
- const methodHasNoMoreMocks = broker?.unregister(file) // TODO or it was a directory of many mocks
65
- if (methodHasNoMoreMocks) {
66
- const { method, urlMask } = parseFilename(file)
67
- delete collection[method][urlMask]
68
- if (!Object.keys(collection[method]).length)
69
- delete collection[method]
59
+ if (broker) {
60
+ const brokerIsEmpty = broker.unregister(file)
61
+ if (brokerIsEmpty) {
62
+ const { method, urlMask } = parseFilename(file)
63
+ delete collection[method][urlMask]
64
+ if (!Object.keys(collection[method]).length)
65
+ delete collection[method]
66
+ }
70
67
  }
68
+ else for (const f of filesInDir(file)) // maybe it was a dir
69
+ unregisterMock(f)
71
70
  }
72
71
 
72
+ function filesInDir(dir) {
73
+ const files = []
74
+ forEachBroker(b => {
75
+ files.push(...(b.mocks.filter(m => m.startsWith(dir + '/'))))
76
+ })
77
+ return files
78
+ }
73
79
 
74
80
  /** @returns {MockBroker | undefined} */
75
81
  export function brokerByFilename(file) {