mockaton 8.5.1 → 8.6.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
@@ -5,50 +5,60 @@
5
5
 
6
6
  ## Mock your APIs, Enhance your Development Workflow
7
7
 
8
- _Mockaton_ is an HTTP mock server built for improving the frontend
9
- development and testing experience.
8
+ Welcome to developer experience tooling! Mockaton is here to help
9
+ make your frontend development and testing easier—and a lot more fun.
10
10
 
11
- With Mockaton you don’t need to write code for wiring your mocks. Instead, it
12
- scans a given directory for filenames following a convention similar to the
13
- URL paths. For example, the following file will be served on `/api/user/1234`
11
+ With Mockaton you don’t need to write code for wiring your mocks. Instead, just
12
+ place your mocks in a directory and let Mockaton do the rest. It will automatically
13
+ scan the directory for filenames that follow a convention similar to the URL paths.
14
+
15
+ For example, for this route `/api/user/1234`, the mock filename would be:
14
16
  ```
15
17
  my-mocks-dir/api/user/[user-id].GET.200.json
16
18
  ```
17
19
 
18
- By the way, you don’t need to mock all your APIs. You can request from
19
- your backend the routes you don’t have mocks for. That’s done with:
20
+ And hey, no need to mock everything. If you don’t have a mock for
21
+ a certain API, Mockaton can fallback to your real backend. Just
22
+ type your backend address in the **Fallback Backend** field.
20
23
 
21
- `config.proxyFallback = 'http://mybackend'`
24
+ And there’s one more cool thing—you can collect those responses
25
+ by clicking the **Save Mocks** checkbox. Those mocks will
26
+ be saved to your `config.mocksDir` following the filename convention.
22
27
 
23
- ## Scraping Mocks
24
- You can save mocks following the filename convention
25
- for the routes that reached your `proxyFallback` with:
26
28
 
27
- `config.collectProxied = true`
29
+ ## Multiple Mock Variants
28
30
 
31
+ You can have many mocks for any route. For example, you might
32
+ want different mocks with different response status codes
33
+ (like triggering errors). Here are a couple of ways to do it:
29
34
 
30
- ## Multiple Mock Variants
31
- Each route can have many mocks, which could either be:
32
- - Different response __status code__. For example, for triggering errors.
33
- - __Comment__ on the filename, which is anything within parentheses.
34
- - e.g. `api/login(locked out user).POST.423.json`
35
+ ### Adding comments in filenames
36
+ Want to mock a locked-out user or an invalid login attempt? You
37
+ can just add a comment to the filename in parentheses. For example:
35
38
 
39
+ `api/login(locked out user).POST.423.json`
40
+
41
+ ### Different response status code
42
+ For instance, you can have mocks with a `4xx` or `5xx` status code for triggering
43
+ error responses. Or with a `204` (No Content) for testing empty collections.
36
44
 
37
- ## Dashboard
38
45
 
39
- In the dashboard you can select a mock variant for a particular
40
- route, among other options. In addition, there’s a programmatic API,
46
+ ## Dashboard
47
+ In the dashboard you can select a mock variant for a particular route, among
48
+ other features such as delaying responses, or triggering an autogenerated
49
+ `500` (Internal Server Error). Nonetheless, there’s a programmatic API,
41
50
  which is handy for setting up tests (see **Commander API** below).
42
51
 
43
52
  <picture>
44
- <source media="(prefers-color-scheme: light)" srcset="./README-dashboard-light.png">
45
- <source media="(prefers-color-scheme: dark)" srcset="./README-dashboard-dark.png">
46
- <img alt="Mockaton Dashboard Demo" src="./README-dashboard-light.png">
53
+ <source media="(prefers-color-scheme: light)" srcset="./pixaton-tests/pic-for-readme.vp860x800.light.gold.png">
54
+ <source media="(prefers-color-scheme: dark)" srcset="./pixaton-tests/pic-for-readme.vp860x800.dark.gold.png">
55
+ <img alt="Mockaton Dashboard" src="./pixaton-tests/pic-for-readme.vp860x800.light.gold.png">
47
56
  </picture>
48
57
 
49
58
 
59
+
50
60
  ## Basic Usage
51
- `tsx` is only needed if you want to write mocks in TypeScript
61
+ `tsx` is only needed if you want to write mocks in TypeScript.
52
62
  ```sh
53
63
  npm install mockaton tsx --save-dev
54
64
  ```
@@ -72,7 +82,12 @@ node --import=tsx my-mockaton.js
72
82
 
73
83
  ## Running the demo app (Vite)
74
84
 
75
- This is a minimal React + Vite + Mockaton app.
85
+ This is a minimal React + Vite + Mockaton app. It’s mainly a list of
86
+ colors, which contains all of their possible states. For example,
87
+ permutations for out-of-stock, new-arrival, and discontinued.
88
+
89
+ Also, if you select the **Admin User** from the Mockaton dashboard,
90
+ the color cards will have a Delete button as well.
76
91
 
77
92
  ```sh
78
93
  git clone https://github.com/ericfortis/mockaton.git
@@ -82,7 +97,11 @@ npm run mockaton
82
97
  npm run start
83
98
  ```
84
99
 
85
- By the way, that directory has a script for opening Mockaton and Vite in one command.
100
+ By the way, that directory has scripts for opening Mockaton and Vite in one command.
101
+
102
+ The app looks like this:
103
+
104
+ <img src="./demo-app-vite/README-screenshot.png" alt="Mockaton Demo App Screenshot" width="580" />
86
105
 
87
106
 
88
107
  ## Use Cases
package/TODO.md CHANGED
@@ -1,5 +1,4 @@
1
1
  # TODO
2
2
 
3
3
  - Refactor tests
4
- - Add Collect Proxied checkbox to the dashboard (remove Allow CORS)
5
- - Dashboard refresh (long polling ?)
4
+ - Dashboard refresh (long polling?)
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "mockaton",
3
3
  "description": "A deterministic server-side for developing and testing frontend clients",
4
4
  "type": "module",
5
- "version": "8.5.1",
5
+ "version": "8.6.1",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
@@ -11,11 +11,11 @@
11
11
  "test": "node --test src/**.test.js",
12
12
  "start": "node dev-mockaton.js",
13
13
  "start:ts": "node --import=tsx dev-mockaton.js",
14
- "test-ui": "node --test --import=./ui-tests/_setup.js --experimental-test-isolation=none \"./ui-tests/**/*.test.js\"",
14
+ "pixaton": "node --test --import=./pixaton-tests/_setup.js --experimental-test-isolation=none \"pixaton-tests/**/*.test.js\"",
15
15
  "outdated": "npm outdated --parseable | awk -F: '{ printf \"npm i %-30s ;# %s\\n\", $4, $2 }'"
16
16
  },
17
17
  "optionalDependencies": {
18
- "pixaton": ">=1.0.1",
19
- "puppeteer": ">=23.10.1"
18
+ "pixaton": ">=1.0.2",
19
+ "puppeteer": ">=24.1.1"
20
20
  }
21
21
  }
package/src/Api.js CHANGED
@@ -26,6 +26,7 @@ export const apiGetRequests = new Map([
26
26
  [API.comments, listComments],
27
27
  [API.fallback, getProxyFallback],
28
28
  [API.cors, getIsCorsAllowed],
29
+ [API.collectProxied, getCollectProxied],
29
30
  [API.static, listStaticFiles]
30
31
  ])
31
32
 
@@ -50,6 +51,7 @@ function listComments(_, response) { sendJSON(response, mockBrokersCollection.ex
50
51
  function listMockBrokers(_, response) { sendJSON(response, mockBrokersCollection.getAll()) }
51
52
  function getProxyFallback(_, response) { sendJSON(response, config.proxyFallback) }
52
53
  function getIsCorsAllowed(_, response) { sendJSON(response, config.corsAllowed) }
54
+ function getCollectProxied(_, response) { sendJSON(response, config.collectProxied) }
53
55
 
54
56
  function listStaticFiles(req, response) {
55
57
  try {
package/src/Commander.js CHANGED
@@ -54,14 +54,13 @@ export class Commander {
54
54
  return this.#patch(API.fallback, proxyAddr)
55
55
  }
56
56
 
57
+ getCollectProxied() {
58
+ return this.#get(API.collectProxied)
59
+ }
57
60
  setCollectProxied(shouldCollect) {
58
61
  return this.#patch(API.collectProxied, shouldCollect)
59
62
  }
60
63
 
61
- reset() {
62
- return this.#patch(API.reset)
63
- }
64
-
65
64
  getCorsAllowed() {
66
65
  return this.#get(API.cors)
67
66
  }
@@ -72,4 +71,8 @@ export class Commander {
72
71
  listStaticFiles() {
73
72
  return this.#get(API.static)
74
73
  }
74
+
75
+ reset() {
76
+ return this.#patch(API.reset)
77
+ }
75
78
  }
package/src/Dashboard.css CHANGED
@@ -9,14 +9,14 @@
9
9
  --colorAccentAlt: #009c71;
10
10
  --colorBackground: #fff;
11
11
  --colorHeaderBackground: #f7f7f7;
12
- --colorComboBoxBackground: #fafafa;
12
+ --colorComboBoxBackground: #f7f7f7;
13
+ --colorSecondaryButtonBackground: #f5f5f5;
13
14
  --colorComboBoxHeaderBackground: #fff;
14
15
  --colorDisabled: #444;
15
16
  --colorHover: #dfefff;
16
17
  --colorLabel: #444;
17
18
  --colorLightRed: #ffe4ee;
18
19
  --colorRed: #da0f00;
19
- --colorSecondaryButtonBackground: #fafafa;
20
20
  --colorText: #000;
21
21
  }
22
22
  }
@@ -28,13 +28,13 @@
28
28
  --colorBackground: #161616;
29
29
  --colorHeaderBackground: #090909;
30
30
  --colorComboBoxBackground: #252525;
31
+ --colorSecondaryButtonBackground: #444;
31
32
  --colorComboBoxHeaderBackground: #222;
32
33
  --colorDisabled: #bbb;
33
34
  --colorHover: #023661;
34
35
  --colorLabel: #aaa;
35
36
  --colorLightRed: #ffe4ee;
36
37
  --colorRed: #f41606;
37
- --colorSecondaryButtonBackground: #444;
38
38
  --colorText: #fff;
39
39
  }
40
40
  }
@@ -49,12 +49,13 @@ body {
49
49
  color: var(--colorText);
50
50
  }
51
51
  * {
52
+ box-sizing: border-box;
52
53
  padding: 0;
53
54
  border: 0;
54
55
  margin: 0;
55
56
  font-family: system-ui, sans-serif;
56
57
  font-size: 100%;
57
- outline: 0
58
+ outline: 0;
58
59
  }
59
60
 
60
61
  select, a, input, button, summary {
@@ -91,7 +92,7 @@ menu {
91
92
  display: flex;
92
93
  width: 100%;
93
94
  align-items: flex-end;
94
- padding: 16px;
95
+ padding: 15px 16px;
95
96
  border-bottom: 1px solid rgba(127, 127, 127, 0.1);
96
97
  background: var(--colorHeaderBackground);
97
98
  box-shadow: var(--boxShadow1);
@@ -103,7 +104,9 @@ menu {
103
104
  margin-right: 18px;
104
105
  }
105
106
 
106
- label {
107
+ .Field {
108
+ min-width: 150px;
109
+
107
110
  span {
108
111
  display: block;
109
112
  color: var(--colorLabel);
@@ -112,27 +115,48 @@ menu {
112
115
 
113
116
  input[type=url],
114
117
  select {
118
+ width: 100%;
115
119
  height: 28px;
116
- width: 150px;
117
120
  padding: 4px 2px;
118
121
  border-right: 3px solid transparent;
119
- margin-top: 2px;
122
+ margin-top: 4px;
120
123
  font-size: 11px;
121
124
  background: var(--colorComboBoxHeaderBackground);
122
125
  border-radius: 6px;
123
126
  }
124
127
 
125
- input[type=url] {
126
- padding: 0 6px;
127
- box-shadow: var(--boxShadow1);
128
- color: var(--colorText);
128
+ &.FallbackBackend {
129
+ position: relative;
130
+ width: 194px;
131
+
132
+ input[type=url] {
133
+ padding: 0 6px;
134
+ box-shadow: var(--boxShadow1);
135
+ color: var(--colorText);
136
+ }
137
+
138
+ .SaveProxiedCheckbox {
139
+ position: absolute;
140
+ top: 0;
141
+ right: 0;
142
+ display: flex;
143
+ width: auto;
144
+ min-width: unset;
145
+ align-items: center;
146
+ font-size: 11px;
147
+ gap: 4px;
148
+
149
+ input:disabled + span {
150
+ opacity: 0.7;
151
+ }
152
+ }
129
153
  }
130
154
  }
131
155
 
132
156
  .ResetButton {
133
157
  padding: 4px 12px;
134
- margin-bottom: 4px;
135
158
  border: 1px solid var(--colorRed);
159
+ margin-bottom: 4px;
136
160
  background: transparent;
137
161
  color: var(--colorRed);
138
162
  border-radius: 50px;
@@ -144,13 +168,6 @@ menu {
144
168
  box-shadow: var(--boxShadow1);
145
169
  }
146
170
  }
147
-
148
- .CorsCheckbox {
149
- display: flex;
150
- align-items: center;
151
- margin-bottom: 8px;
152
- gap: 4px;
153
- }
154
171
  }
155
172
 
156
173
 
@@ -368,8 +385,8 @@ main {
368
385
 
369
386
  a {
370
387
  display: inline-block;
371
- border-radius: 6px;
372
388
  padding: 6px;
389
+ border-radius: 6px;
373
390
  color: var(--colorAccentAlt);
374
391
  text-decoration: none;
375
392
 
package/src/Dashboard.js CHANGED
@@ -4,7 +4,6 @@ import { DEFAULT_500_COMMENT } from '/ApiConstants.js'
4
4
 
5
5
 
6
6
  const Strings = {
7
- allow_cors: 'Allow CORS',
8
7
  bulk_select_by_comment: 'Bulk Select by Comment',
9
8
  bulk_select_by_comment_disabled_title: 'No mock files have comments, which are anything within parentheses on the filename.',
10
9
  click_link_to_preview: 'Click a link to preview it',
@@ -18,19 +17,22 @@ const Strings = {
18
17
  mock: 'Mock',
19
18
  no_mocks_found: 'No mocks found',
20
19
  reset: 'Reset',
20
+ save_proxied: 'Save Mocks',
21
21
  select_one: 'Select One',
22
22
  static: 'Static'
23
23
  }
24
24
 
25
25
  const CSS = {
26
- ResetButton: 'ResetButton',
27
- CorsCheckbox: 'CorsCheckbox',
28
26
  DelayToggler: 'DelayToggler',
27
+ FallbackBackend: 'FallbackBackend',
28
+ Field: 'Field',
29
29
  InternalServerErrorToggler: 'InternalServerErrorToggler',
30
30
  MockSelector: 'MockSelector',
31
31
  PayloadViewer: 'PayloadViewer',
32
32
  PreviewLink: 'PreviewLink',
33
33
  ProgressBar: 'ProgressBar',
34
+ ResetButton: 'ResetButton',
35
+ SaveProxiedCheckbox: 'SaveProxiedCheckbox',
34
36
  StaticFilesList: 'StaticFilesList',
35
37
 
36
38
  bold: 'bold',
@@ -49,7 +51,7 @@ function init() {
49
51
  mockaton.listMocks(),
50
52
  mockaton.listCookies(),
51
53
  mockaton.listComments(),
52
- mockaton.getCorsAllowed(),
54
+ mockaton.getCollectProxied(),
53
55
  mockaton.getProxyFallback(),
54
56
  mockaton.listStaticFiles()
55
57
  ].map(api => api.then(response => response.ok && response.json())))
@@ -63,7 +65,7 @@ function App(apiResponses) {
63
65
  document.body.appendChild(DevPanel(apiResponses))
64
66
  }
65
67
 
66
- function DevPanel([brokersByMethod, cookies, comments, corsAllowed, fallbackAddress, staticFiles]) {
68
+ function DevPanel([brokersByMethod, cookies, comments, collectProxied, fallbackAddress, staticFiles]) {
67
69
  const isEmpty = Object.keys(brokersByMethod).length === 0
68
70
  return (
69
71
  r('div', null,
@@ -71,8 +73,7 @@ function DevPanel([brokersByMethod, cookies, comments, corsAllowed, fallbackAddr
71
73
  r('img', { src: '/mockaton-logo.svg', width: 160, alt: Strings.title }),
72
74
  r(CookieSelector, { list: cookies }),
73
75
  r(BulkSelector, { comments }),
74
- r(ProxyFallbackField, { fallbackAddress }),
75
- r(CorsCheckbox, { corsAllowed }),
76
+ r(ProxyFallbackField, { fallbackAddress, collectProxied }),
76
77
  r(ResetButton)),
77
78
  isEmpty
78
79
  ? r('main', null, Strings.no_mocks_found)
@@ -94,7 +95,7 @@ function CookieSelector({ list }) {
94
95
  }
95
96
  const disabled = list.length <= 1
96
97
  return (
97
- r('label', null,
98
+ r('label', { className: CSS.Field },
98
99
  r('span', null, Strings.cookie),
99
100
  r('select', {
100
101
  autocomplete: 'off',
@@ -118,7 +119,7 @@ function BulkSelector({ comments }) {
118
119
  ? []
119
120
  : [Strings.select_one].concat(comments)
120
121
  return (
121
- r('label', null,
122
+ r('label', { className: CSS.Field },
122
123
  r('span', null, Strings.bulk_select_by_comment),
123
124
  r('select', {
124
125
  'data-qaid': 'BulkSelector',
@@ -132,41 +133,51 @@ function BulkSelector({ comments }) {
132
133
  }
133
134
 
134
135
 
135
- function ProxyFallbackField({ fallbackAddress = '' }) {
136
+ function ProxyFallbackField({ fallbackAddress = '', collectProxied }) {
137
+ const refSaveProxiedCheckbox = useRef()
136
138
  function onChange(event) {
137
139
  const input = event.currentTarget
140
+ refSaveProxiedCheckbox.current.disabled = !input.validity.valid || !input.value.trim()
138
141
  if (!input.validity.valid)
139
142
  input.reportValidity()
140
143
  else
141
- mockaton.setProxyFallback(input.value)
144
+ mockaton.setProxyFallback(input.value.trim())
142
145
  .catch(onError)
143
146
  }
144
147
  return (
145
- r('label', null,
146
- r('span', null, Strings.fallback_server),
147
- r('input', {
148
- type: 'url',
149
- autocomplete: 'none',
150
- placeholder: Strings.fallback_server_placeholder,
151
- value: fallbackAddress,
152
- onChange
148
+ r('div', { className: CSS.Field + ' ' + CSS.FallbackBackend },
149
+ r('label', null,
150
+ r('span', null, Strings.fallback_server),
151
+ r('input', {
152
+ type: 'url',
153
+ autocomplete: 'none',
154
+ placeholder: Strings.fallback_server_placeholder,
155
+ value: fallbackAddress,
156
+ onChange
157
+ })),
158
+ r(SaveProxiedCheckbox, {
159
+ collectProxied,
160
+ disabled: !fallbackAddress,
161
+ ref: refSaveProxiedCheckbox
153
162
  })))
154
163
  }
155
164
 
156
165
 
157
- function CorsCheckbox({ corsAllowed }) {
166
+ function SaveProxiedCheckbox({ ref, disabled, collectProxied }) {
158
167
  function onChange(event) {
159
- mockaton.setCorsAllowed(event.currentTarget.checked)
168
+ mockaton.setCollectProxied(event.currentTarget.checked)
160
169
  .catch(onError)
161
170
  }
162
171
  return (
163
- r('label', { className: CSS.CorsCheckbox },
172
+ r('label', { className: CSS.SaveProxiedCheckbox },
164
173
  r('input', {
174
+ ref,
165
175
  type: 'checkbox',
166
- checked: corsAllowed,
176
+ disabled,
177
+ checked: collectProxied,
167
178
  onChange
168
179
  }),
169
- Strings.allow_cors))
180
+ r('span', null, Strings.save_proxied)))
170
181
  }
171
182
 
172
183