mockaton 8.2.22 → 8.2.23
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 +17 -11
- package/package.json +1 -1
- package/src/Api.js +16 -22
- package/src/Commander.js +10 -11
- package/src/Dashboard.js +2 -2
- package/src/MockDispatcherPlugins.js +0 -4
- package/src/utils/fs.js +6 -3
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
_Mockaton_ is a mock server for improving the frontend development and testing experience.
|
|
8
8
|
|
|
9
|
-
With Mockaton
|
|
9
|
+
With Mockaton you don’t need to write code for wiring your mocks. Instead, it
|
|
10
10
|
scans a given directory for filenames following a convention similar to the
|
|
11
11
|
URL paths. For example, the following file will be served on `/api/user/1234`
|
|
12
12
|
```
|
|
@@ -37,7 +37,7 @@ which is handy for setting up tests (see **Commander API** below).
|
|
|
37
37
|
<picture>
|
|
38
38
|
<source media="(prefers-color-scheme: light)" srcset="./README-dashboard-light.png">
|
|
39
39
|
<source media="(prefers-color-scheme: dark)" srcset="./README-dashboard-dark.png">
|
|
40
|
-
<img alt="Mockaton Dashboard Demo" src="./README-dashboard-light.png"
|
|
40
|
+
<img alt="Mockaton Dashboard Demo" src="./README-dashboard-light.png">
|
|
41
41
|
</picture>
|
|
42
42
|
|
|
43
43
|
|
|
@@ -93,7 +93,7 @@ The _Reset_ button is for registering newly added, removed, or renamed mocks.
|
|
|
93
93
|
- Polled resources (for triggering their different states)
|
|
94
94
|
- alerts
|
|
95
95
|
- notifications
|
|
96
|
-
- slow to build
|
|
96
|
+
- slow to build resources
|
|
97
97
|
|
|
98
98
|
### Time Travel
|
|
99
99
|
If you commit the mocks to your repo, it’s straightforward to bisect bugs and
|
|
@@ -103,8 +103,8 @@ backends to old API contracts or databases.
|
|
|
103
103
|
### Deterministic Standalone Demo Server
|
|
104
104
|
Perhaps you need to demo your app, but the ideal flow is too complex to
|
|
105
105
|
simulate from the actual backend. In this case, compile your frontend app and
|
|
106
|
-
put its built assets in `config.staticDir`. Then,
|
|
107
|
-
|
|
106
|
+
put its built assets in `config.staticDir`. Then, on the dashboard
|
|
107
|
+
"Bulk Select" mocks to simulate the complete states you want to demo.
|
|
108
108
|
For bulk-selecting, you just need to add a comment to the mock
|
|
109
109
|
filename, such as `(demo-part1)`, `(demo-part2)`.
|
|
110
110
|
|
|
@@ -113,7 +113,7 @@ filename, such as `(demo-part1)`, `(demo-part2)`.
|
|
|
113
113
|
- Avoids spinning up and maintaining hefty backends when developing UIs.
|
|
114
114
|
- For a deterministic, comprehensive, and consistent backend state. For example, having
|
|
115
115
|
a collection with all the possible state variants helps for spotting inadvertent bugs.
|
|
116
|
-
- Sometimes
|
|
116
|
+
- Sometimes frontend progress is blocked waiting for some backend API. Similarly,
|
|
117
117
|
it’s often delayed due to missing data or inconvenient contracts. Therefore,
|
|
118
118
|
many meetings can be saved by prototyping frontend features with mocks, and
|
|
119
119
|
then showing those contracts to the backend team.
|
|
@@ -271,8 +271,9 @@ Defaults to `0`, which means auto-assigned
|
|
|
271
271
|
Defaults to `/(\.DS_Store|~)$/`
|
|
272
272
|
|
|
273
273
|
|
|
274
|
-
### `delay?: number`
|
|
275
|
-
|
|
274
|
+
### `delay?: number`
|
|
275
|
+
Routes can individually be delayed with the 🕓 checkbox. On the other hand,
|
|
276
|
+
the amount is globally configurable. It defaults to `config.delay=1200` milliseconds.
|
|
276
277
|
|
|
277
278
|
|
|
278
279
|
### `proxyFallback?: string`
|
|
@@ -349,9 +350,10 @@ type Plugin = (
|
|
|
349
350
|
body: string | Uint8Array
|
|
350
351
|
}>
|
|
351
352
|
```
|
|
352
|
-
Plugins are for processing mocks before sending them.
|
|
353
|
+
Plugins are for processing mocks before sending them. If no regex matches the filename,
|
|
354
|
+
it fallbacks to reading the file from disk and computing the MIME from the extension.
|
|
353
355
|
|
|
354
|
-
Note: don’t call `response.end()` on
|
|
356
|
+
Note: don’t call `response.end()` on any plugin.
|
|
355
357
|
|
|
356
358
|
<details>
|
|
357
359
|
<summary><b> See Plugin Examples </b></summary>
|
|
@@ -365,7 +367,11 @@ import { readFileSync } from 'node:js'
|
|
|
365
367
|
import { jsToJsonPlugin } from 'mockaton'
|
|
366
368
|
|
|
367
369
|
config.plugins = [
|
|
368
|
-
|
|
370
|
+
|
|
371
|
+
// Although `jsToJsonPlugin` is set by default, you need to add it to your list if you need it.
|
|
372
|
+
// In other words, your plugins array overwrites the default list. This way you can remove it.
|
|
373
|
+
[/\.(js|ts)$/, jsToJsonPlugin],
|
|
374
|
+
|
|
369
375
|
[/\.yml$/, yamlToJsonPlugin],
|
|
370
376
|
[/foo\.GET\.200\.txt$/, capitalizePlugin], // e.g. GET /api/foo would be capitalized
|
|
371
377
|
]
|
package/package.json
CHANGED
package/src/Api.js
CHANGED
|
@@ -39,6 +39,8 @@ export const apiPatchRequests = new Map([
|
|
|
39
39
|
[API.cors, setCorsAllowed]
|
|
40
40
|
])
|
|
41
41
|
|
|
42
|
+
/* GET */
|
|
43
|
+
|
|
42
44
|
function serveDashboard(_, response) { sendFile(response, join(import.meta.dirname, 'Dashboard.html')) }
|
|
43
45
|
function serveDashboardAsset(req, response) { sendFile(response, join(import.meta.dirname, req.url)) }
|
|
44
46
|
|
|
@@ -48,13 +50,26 @@ function listMockBrokers(_, response) { sendJSON(response, mockBrokersCollection
|
|
|
48
50
|
function getProxyFallback(_, response) { sendJSON(response, Config.proxyFallback) }
|
|
49
51
|
function getIsCorsAllowed(_, response) { sendJSON(response, Config.corsAllowed) }
|
|
50
52
|
|
|
53
|
+
async function listStaticFiles(req, response) { // TESTME
|
|
54
|
+
try {
|
|
55
|
+
const files = Config.staticDir
|
|
56
|
+
? listFilesRecursively(Config.staticDir).filter(f => !Config.ignore.test(f))
|
|
57
|
+
: []
|
|
58
|
+
sendJSON(response, files)
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
sendBadRequest(response, error)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
/* PATCH */
|
|
51
67
|
|
|
52
68
|
function reinitialize(_, response) {
|
|
53
69
|
mockBrokersCollection.init()
|
|
54
70
|
sendOK(response)
|
|
55
71
|
}
|
|
56
72
|
|
|
57
|
-
|
|
58
73
|
async function selectCookie(req, response) {
|
|
59
74
|
try {
|
|
60
75
|
cookie.setCurrent(await parseJSON(req))
|
|
@@ -65,14 +80,12 @@ async function selectCookie(req, response) {
|
|
|
65
80
|
}
|
|
66
81
|
}
|
|
67
82
|
|
|
68
|
-
|
|
69
83
|
async function selectMock(req, response) {
|
|
70
84
|
try {
|
|
71
85
|
const file = await parseJSON(req)
|
|
72
86
|
const broker = mockBrokersCollection.getBrokerByFilename(file)
|
|
73
87
|
if (!broker || !broker.mockExists(file))
|
|
74
88
|
throw `Missing Mock: ${file}`
|
|
75
|
-
|
|
76
89
|
broker.updateFile(file)
|
|
77
90
|
sendOK(response)
|
|
78
91
|
}
|
|
@@ -81,17 +94,14 @@ async function selectMock(req, response) {
|
|
|
81
94
|
}
|
|
82
95
|
}
|
|
83
96
|
|
|
84
|
-
|
|
85
97
|
async function setRouteIsDelayed(req, response) {
|
|
86
98
|
try {
|
|
87
99
|
const body = await parseJSON(req)
|
|
88
100
|
const broker = mockBrokersCollection.getBrokerForUrl(
|
|
89
101
|
body[DF.routeMethod],
|
|
90
102
|
body[DF.routeUrlMask])
|
|
91
|
-
|
|
92
103
|
if (!broker)
|
|
93
104
|
throw `Route does not exist: ${body[DF.routeUrlMask]} ${body[DF.routeUrlMask]}`
|
|
94
|
-
|
|
95
105
|
broker.updateDelay(body[DF.delayed])
|
|
96
106
|
sendOK(response)
|
|
97
107
|
}
|
|
@@ -100,7 +110,6 @@ async function setRouteIsDelayed(req, response) {
|
|
|
100
110
|
}
|
|
101
111
|
}
|
|
102
112
|
|
|
103
|
-
|
|
104
113
|
async function updateProxyFallback(req, response) {
|
|
105
114
|
try {
|
|
106
115
|
const fallback = await parseJSON(req)
|
|
@@ -116,7 +125,6 @@ async function updateProxyFallback(req, response) {
|
|
|
116
125
|
}
|
|
117
126
|
}
|
|
118
127
|
|
|
119
|
-
|
|
120
128
|
async function bulkUpdateBrokersByCommentTag(req, response) {
|
|
121
129
|
try {
|
|
122
130
|
mockBrokersCollection.setMocksMatchingComment(await parseJSON(req))
|
|
@@ -127,7 +135,6 @@ async function bulkUpdateBrokersByCommentTag(req, response) {
|
|
|
127
135
|
}
|
|
128
136
|
}
|
|
129
137
|
|
|
130
|
-
|
|
131
138
|
async function setCorsAllowed(req, response) {
|
|
132
139
|
try {
|
|
133
140
|
Config.corsAllowed = await parseJSON(req)
|
|
@@ -138,16 +145,3 @@ async function setCorsAllowed(req, response) {
|
|
|
138
145
|
}
|
|
139
146
|
}
|
|
140
147
|
|
|
141
|
-
|
|
142
|
-
// TESTME
|
|
143
|
-
async function listStaticFiles(req, response) {
|
|
144
|
-
try {
|
|
145
|
-
const files = Config.staticDir
|
|
146
|
-
? listFilesRecursively(Config.staticDir).filter(f => !Config.ignore.test(f))
|
|
147
|
-
: []
|
|
148
|
-
sendJSON(response, files)
|
|
149
|
-
}
|
|
150
|
-
catch (error) {
|
|
151
|
-
sendBadRequest(response, error)
|
|
152
|
-
}
|
|
153
|
-
}
|
package/src/Commander.js
CHANGED
|
@@ -7,6 +7,16 @@ export class Commander {
|
|
|
7
7
|
this.#addr = addr
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
#get(api) {
|
|
11
|
+
return fetch(this.#addr + api)
|
|
12
|
+
}
|
|
13
|
+
#patch(api, body) {
|
|
14
|
+
return fetch(this.#addr + api, {
|
|
15
|
+
method: 'PATCH',
|
|
16
|
+
body: JSON.stringify(body)
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
10
20
|
listMocks() {
|
|
11
21
|
return this.#get(API.mocks)
|
|
12
22
|
}
|
|
@@ -58,15 +68,4 @@ export class Commander {
|
|
|
58
68
|
listStaticFiles() {
|
|
59
69
|
return this.#get(API.static)
|
|
60
70
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
#get(api) {
|
|
64
|
-
return fetch(this.#addr + api)
|
|
65
|
-
}
|
|
66
|
-
#patch(api, body) {
|
|
67
|
-
return fetch(this.#addr + api, {
|
|
68
|
-
method: 'PATCH',
|
|
69
|
-
body: JSON.stringify(body)
|
|
70
|
-
})
|
|
71
|
-
}
|
|
72
71
|
}
|
package/src/Dashboard.js
CHANGED
|
@@ -423,7 +423,7 @@ function createElement(elem, props = null, ...children) {
|
|
|
423
423
|
node[key] = value
|
|
424
424
|
else
|
|
425
425
|
node.setAttribute(key, value)
|
|
426
|
-
node.append(...children.flat())
|
|
426
|
+
node.append(...children.flat().filter(a => a))
|
|
427
427
|
return node
|
|
428
428
|
}
|
|
429
429
|
|
|
@@ -431,7 +431,7 @@ function createSvgElement(tagName, props, ...children) {
|
|
|
431
431
|
const elem = document.createElementNS('http://www.w3.org/2000/svg', tagName)
|
|
432
432
|
for (const [key, value] of Object.entries(props))
|
|
433
433
|
elem.setAttribute(key, value)
|
|
434
|
-
elem.append(...children.flat())
|
|
434
|
+
elem.append(...children.flat().filter(a => a))
|
|
435
435
|
return elem
|
|
436
436
|
}
|
|
437
437
|
|
|
@@ -7,10 +7,6 @@ export async function applyPlugins(filePath, req, response) {
|
|
|
7
7
|
for (const [regex, plugin] of Config.plugins) // TESTME capitalizePlugin
|
|
8
8
|
if (regex.test(filePath))
|
|
9
9
|
return await plugin(filePath, req, response)
|
|
10
|
-
return defaultPlugin(filePath)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function defaultPlugin(filePath) {
|
|
14
10
|
return {
|
|
15
11
|
mime: mimeFor(filePath),
|
|
16
12
|
body: read(filePath)
|
package/src/utils/fs.js
CHANGED
|
@@ -8,6 +8,9 @@ export const isDirectory = path => lstatSync(path, { throwIfNoEntry: false })?.i
|
|
|
8
8
|
export const read = path => readFileSync(path)
|
|
9
9
|
|
|
10
10
|
/** @returns {Array<string>} paths relative to `dir` */
|
|
11
|
-
export const listFilesRecursively = dir =>
|
|
12
|
-
.
|
|
13
|
-
.
|
|
11
|
+
export const listFilesRecursively = dir => {
|
|
12
|
+
const files = readdirSync(dir, { recursive: true }).filter(f => isFile(join(dir, f)))
|
|
13
|
+
return process.platform === 'win32'
|
|
14
|
+
? files.map(f => f.replaceAll(path.sep, path.posix.sep)) // TESTME
|
|
15
|
+
: files
|
|
16
|
+
}
|