mockaton 8.12.9 → 8.13.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
@@ -2,6 +2,8 @@
2
2
 
3
3
  ![NPM Version](https://img.shields.io/npm/v/mockaton)
4
4
  ![NPM Version](https://img.shields.io/npm/l/mockaton)
5
+ [![Test](https://github.com/ericfortis/mockaton/actions/workflows/test.yml/badge.svg)](https://github.com/ericfortis/mockaton/actions/workflows/test.yml)
6
+ [![CodeQL](https://github.com/ericfortis/mockaton/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/ericfortis/mockaton/actions/workflows/github-code-scanning/codeql)
5
7
 
6
8
  An HTTP mock server for simulating APIs with minimal setup
7
9
  — ideal for triggering difficult to reproduce backend states.
@@ -27,9 +29,9 @@ Nonetheless, there’s a programmatic API, which is handy
27
29
  for setting up tests (see **Commander API** section).
28
30
 
29
31
  <picture>
30
- <source media="(prefers-color-scheme: light)" srcset="./pixaton-tests/pic-for-readme.vp840x800.light.gold.png">
31
- <source media="(prefers-color-scheme: dark)" srcset="./pixaton-tests/pic-for-readme.vp840x800.dark.gold.png">
32
- <img alt="Mockaton Dashboard" src="./pixaton-tests/pic-for-readme.vp840x800.light.gold.png">
32
+ <source media="(prefers-color-scheme: light)" srcset="pixaton-tests/macos/pic-for-readme.vp840x800.light.gold.png">
33
+ <source media="(prefers-color-scheme: dark)" srcset="pixaton-tests/macos/pic-for-readme.vp840x800.dark.gold.png">
34
+ <img alt="Mockaton Dashboard" src="pixaton-tests/macos/pic-for-readme.vp840x800.light.gold.png">
33
35
  </picture>
34
36
 
35
37
 
@@ -74,12 +76,10 @@ They will be saved in your `config.mocksDir` following the filename convention.
74
76
 
75
77
 
76
78
  ## Basic Usage
77
- Mockaton is a Node.js program with no build or runtime NPM dependencies.
78
-
79
- `tsx` is only needed if you want to write mocks in TypeScript.
79
+ Mockaton is a Node.js program.
80
80
 
81
81
  ```sh
82
- npm install mockaton tsx --save-dev
82
+ npm install mockaton --save-dev
83
83
  ```
84
84
 
85
85
  Create a `my-mockaton.js` file
@@ -95,7 +95,7 @@ Mockaton({
95
95
  ```
96
96
 
97
97
  ```sh
98
- node --import=tsx my-mockaton.js
98
+ node my-mockaton.js
99
99
  ```
100
100
 
101
101
  <br/>
@@ -121,7 +121,7 @@ npm run start # in another terminal
121
121
 
122
122
  The app looks like this:
123
123
 
124
- <img src="./demo-app-vite/pixaton-tests/pic-for-readme.vp500x800.light.gold.png" alt="Mockaton Demo App Screenshot" width="500" />
124
+ <img src="./demo-app-vite/pixaton-tests/pic-for-readme.vp740x880.light.gold.png" alt="Mockaton Demo App Screenshot" width="740" />
125
125
 
126
126
  <br/>
127
127
 
@@ -366,17 +366,16 @@ or that you manually picked with the ☁️ **Cloud Checkbox**.
366
366
 
367
367
  ### `collectProxied?: boolean`
368
368
  Defaults to `false`. With this flag you can save mocks that hit
369
- your proxy fallback to `config.mocksDir`. If there are UUIDv4 in the
370
- URL, the filename will have `[id]` in their place. For example,
369
+ your proxy fallback to `config.mocksDir`. If the URL has v4 UUIDs,
370
+ the filename will have `[id]` in their place. For example:
371
371
 
372
- ```
373
- /api/user/d14e09c8-d970-4b07-be42-b2f4ee22f0a6/likes =>
374
- my-mocks-dir/api/user/[id]/likes.GET.200.json
375
- ```
372
+ <pre>
373
+ <b>/api/user/</b>d14e09c8-d970-4b07-be42-b2f4ee22f0a6<b>/likes</b> =>
374
+ my-mocks-dir<b>/api/user/</b>[id]<b>/likes</b>.GET.200.json
375
+ </pre>
376
376
 
377
- Your existing mocks won’t be overwritten. That is, the routes you manually
378
- selected for using your backend with the ☁️ **Cloud Checkbox**, will have
379
- a unique filename comment.
377
+ Your existing mocks won’t be overwritten. In other words, responses of routes with
378
+ the ☁️ **Cloud Checkbox** selected will be saved with unique filename-comments.
380
379
 
381
380
 
382
381
  <details>
@@ -394,8 +393,8 @@ the predefined list. For that, you can add it to <code>config.extraMimes</code>
394
393
 
395
394
 
396
395
  ### `formatCollectedJSON?: boolean`
397
- Defaults to `true`. Saves the mock with the formatting output
398
- of `JSON.stringify(data, null, ' ')` (two spaces indentation).
396
+ Defaults to `true`. Saves the mock with two spaces indentation &mdash;
397
+ the formatting output of `JSON.stringify(data, null, ' ')`
399
398
 
400
399
 
401
400
  <br/>
@@ -412,14 +411,16 @@ config.cookies = {
412
411
  'My JWT': jwtCookie('my-cookie', {
413
412
  name: 'John Doe',
414
413
  picture: 'https://cdn.auth0.com/avatars/jd.png'
415
- })
414
+ }),
415
+ 'None': ''
416
416
  }
417
417
  ```
418
- The selected cookie, which is the first one by default, is sent in every
419
- response in a `Set-Cookie` header.
418
+ The selected cookie, which is the first one by default, is sent in every response in a
419
+ `Set-Cookie` header (as long as its value is not an empty string). The object key is just
420
+ a label for UI display purposes, and also for selecting a cookie via the Commander API.
420
421
 
421
- If you need to send more cookies, you can either inject them globally
422
- in `config.extraHeaders`, or in function `.js` or `.ts` mock.
422
+ If you need to send more than one cookie, you can inject them globally
423
+ in `config.extraHeaders`, or individually in a function `.js` or `.ts` mock.
423
424
 
424
425
  By the way, the `jwtCookie` helper has a hardcoded header and signature.
425
426
  In other words, it’s useful only if you care about its payload.
@@ -480,8 +481,8 @@ import { jsToJsonPlugin } from 'mockaton'
480
481
 
481
482
  config.plugins = [
482
483
 
483
- // Although `jsToJsonPlugin` is set by default, you need to add it to your list if you need it.
484
- // In other words, your plugins array overwrites the default list. This way you can remove it.
484
+ // Although `jsToJsonPlugin` is set by default, you need to include it if you need it.
485
+ // IOW, your plugins array overwrites the default list. This way you can remove it.
485
486
  [/\.(js|ts)$/, jsToJsonPlugin],
486
487
 
487
488
  [/\.yml$/, yamlToJsonPlugin],
@@ -508,6 +509,10 @@ function capitalizePlugin(filePath) {
508
509
 
509
510
  ### `corsAllowed?: boolean`
510
511
  Defaults to `true`. When `true`, these are the default options:
512
+
513
+ <details>
514
+ <summary>CORS Options</summary>
515
+
511
516
  ```js
512
517
  config.corsOrigins = ['*']
513
518
  config.corsMethods = require('node:http').METHODS
@@ -516,6 +521,7 @@ config.corsCredentials = true
516
521
  config.corsMaxAge = 0 // seconds to cache the preflight req
517
522
  config.corsExposedHeaders = [] // headers you need to access in client-side JS
518
523
  ```
524
+ </details>
519
525
 
520
526
  <br/>
521
527
 
@@ -554,9 +560,9 @@ await mockaton.select('api/foo.200.GET.json')
554
560
  ```js
555
561
  await mockaton.bulkSelectByComment('(demo-a)')
556
562
  ```
557
- Parentheses are optional, so you can pass a partial match.
558
- For example, passing `'demo-'` (without the final `a`), selects the
559
- first mock in alphabetical order that matches.
563
+ Parentheses are optional, so you can pass a partial match. For example,
564
+ passing `'demo-'` (without the final `a`) works too. On routes
565
+ with many partial matches, their first mock in alphabetical order wins.
560
566
 
561
567
  <br/>
562
568
 
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.9",
5
+ "version": "8.13.0",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
@@ -19,13 +19,12 @@
19
19
  "test": "node --test \"src/**/*.test.js\"",
20
20
  "coverage": "node --test --test-reporter=lcov --test-reporter-destination=.coverage/lcov.info --experimental-test-coverage \"src/**/*.test.js\"",
21
21
  "start": "node dev-mockaton.js",
22
- "start:ts": "node --import=tsx dev-mockaton.js",
23
22
  "pixaton": "node --test --import=./pixaton-tests/_setup.js --experimental-test-isolation=none \"pixaton-tests/**/*.test.js\"",
24
23
  "outdated": "npm outdated --parseable | awk -F: '{ printf \"npm i %-30s ;# %s\\n\", $4, $2 }'"
25
24
  },
26
25
  "optionalDependencies": {
27
26
  "open": "^10.0.0",
28
- "pixaton": ">=1.0.2",
27
+ "pixaton": ">=1.1.1",
29
28
  "puppeteer": ">=24.1.1"
30
29
  }
31
30
  }
@@ -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
@@ -288,6 +288,12 @@ select {
288
288
  border-radius: var(--radius);
289
289
  color: var(--colorAccent);
290
290
  text-decoration: none;
291
+ word-break: break-word;
292
+
293
+ span {
294
+ opacity: 0.5;
295
+ filter: saturate(0.5);
296
+ }
291
297
 
292
298
  &:hover {
293
299
  background: var(--colorHover);
@@ -510,6 +516,11 @@ select {
510
516
  &:hover {
511
517
  text-decoration: underline;
512
518
  }
519
+
520
+ span {
521
+ opacity: 0.5;
522
+ filter: saturate(0.5);
523
+ }
513
524
  }
514
525
  }
515
526
 
package/src/Dashboard.js CHANGED
@@ -253,22 +253,25 @@ function MockList({ brokersByMethod, canProxy }) {
253
253
  }
254
254
 
255
255
  function SectionByMethod({ method, brokers, canProxy }) {
256
+ const brokersSorted = Object.entries(brokers)
257
+ .filter(([, broker]) => broker.mocks.length > 1) // >1 because of autogen500
258
+ .sort((a, b) => a[0].localeCompare(b[0]))
259
+
260
+ const urlMasks = brokersSorted.map(([urlMask]) => urlMask)
261
+ const urlMasksHighlighted = highlightCommonPathPrefixes(urlMasks)
256
262
  return (
257
263
  r('tbody', null,
258
264
  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 }) {
265
+ brokersSorted.map(([urlMask, broker], i) =>
266
+ r('tr', { 'data-method': method, 'data-urlMask': urlMask },
267
+ r('td', null, r(PreviewLink, { method, urlMask, urlMaskHighlighted: urlMasksHighlighted[i] })),
268
+ r('td', null, r(MockSelector, { broker })),
269
+ r('td', null, r(InternalServerErrorToggler, { broker })),
270
+ r('td', null, r(DelayRouteToggler, { broker })),
271
+ r('td', null, r(ProxyToggler, { broker, disabled: !canProxy }))))))
272
+ }
273
+
274
+ function PreviewLink({ method, urlMask, urlMaskHighlighted }) {
272
275
  async function onClick(event) {
273
276
  event.preventDefault()
274
277
  try {
@@ -284,8 +287,9 @@ function PreviewLink({ method, urlMask }) {
284
287
  r('a', {
285
288
  className: CSS.PreviewLink,
286
289
  href: urlMask,
287
- onClick
288
- }, urlMask))
290
+ onClick,
291
+ innerHTML: urlMaskHighlighted
292
+ }))
289
293
  }
290
294
 
291
295
  function MockSelector({ broker }) {
@@ -492,15 +496,20 @@ function mockSelectorFor(method, urlMask) {
492
496
  function StaticFilesList({ staticFiles }) {
493
497
  if (!staticFiles.length)
494
498
  return null
499
+ const highlighted = highlightCommonPathPrefixes(staticFiles)
495
500
  return (
496
501
  r('section', {
497
502
  open: true,
498
503
  className: CSS.StaticFilesList
499
504
  },
500
505
  r('h2', null, Strings.static_get),
501
- r('ul', null, staticFiles.map(f =>
506
+ r('ul', null, staticFiles.map((f, i) =>
502
507
  r('li', null,
503
- r('a', { href: f, target: '_blank' }, f))))))
508
+ r('a', {
509
+ href: f,
510
+ target: '_blank',
511
+ innerHTML: highlighted[i]
512
+ }))))))
504
513
  }
505
514
 
506
515
 
@@ -607,3 +616,40 @@ function createSvgElement(tagName, props, ...children) {
607
616
  function useRef() {
608
617
  return { current: null }
609
618
  }
619
+
620
+
621
+
622
+
623
+ /** This is for styling the repeated paths with a faint style */
624
+ function highlightCommonPathPrefixes(paths) {
625
+ const seen = []
626
+ 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)
640
+ }
641
+
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}`)
648
+ }
649
+ else
650
+ result.push(path)
651
+
652
+ seen.push(path)
653
+ }
654
+ return result
655
+ }
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))