mockaton 10.6.8 → 11.0.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/Makefile ADDED
@@ -0,0 +1,33 @@
1
+ start:
2
+ @node src/cli.js
3
+
4
+ watch:
5
+ @node --watch src/cli.js
6
+
7
+
8
+ test:
9
+ node --test 'src/**/*.test.js'
10
+
11
+ test-docker:
12
+ docker run --rm -it \
13
+ -v "$$PWD":/app \
14
+ -w /app \
15
+ node:24 \
16
+ make test
17
+
18
+ coverage:
19
+ node --test --experimental-test-coverage \
20
+ --test-reporter=spec --test-reporter-destination=stdout \
21
+ --test-reporter=lcov --test-reporter-destination=lcov.info \
22
+ 'src/**/*.test.js'
23
+
24
+ pixaton:
25
+ node --test --experimental-test-isolation=none \
26
+ --import=./pixaton-tests/_setup.js \
27
+ 'pixaton-tests/**/*.test.js'
28
+
29
+ outdated:
30
+ @npm outdated --parseable |\
31
+ awk -F: '{ printf "npm i %-30s ;# %s\n", $$4, $$2 }'
32
+
33
+ .PHONY: *
package/README.md CHANGED
@@ -10,33 +10,6 @@
10
10
  An HTTP mock server for simulating APIs with minimal setup
11
11
  — ideal for testing difficult to reproduce states.
12
12
 
13
- <br/>
14
-
15
- ## Motivation
16
-
17
- **No API state should be too hard to test.**
18
- With Mockaton, developers can achieve correctness and speed.
19
-
20
- ### Correctness
21
- - Enables testing of complex scenarios that would otherwise be skipped. e.g.,
22
- - Triggering errors on third-party APIs.
23
- - Triggering errors on your project’s backend (if you are a frontend developer).
24
- - Allows for deterministic, comprehensive, and consistent state.
25
- - Spot inadvertent regressions during development.
26
- - Use it to set up screenshot tests, e.g., with [pixaton](https://github.com/ericfortis/pixaton).
27
-
28
- ### Speed
29
- - Works around unstable dev backends while developing UIs.
30
- - Spinning up development infrastructure.
31
- - Syncing database states.
32
- - Prevents progress from being blocked by waiting for APIs.
33
- - Time travel. If you commit the mocks to your repo,
34
- you don’t have to downgrade backends for:
35
- - checking out long-lived branches
36
- - bisecting bugs
37
-
38
- <br/>
39
-
40
13
  ## Overview
41
14
  With Mockaton, you don’t need to write code for wiring up your
42
15
  mocks. Instead, a given directory is scanned for filenames
@@ -118,6 +91,31 @@ checking ✅ **Save Mocks**, you can collect the responses that hit your backend
118
91
  They will be saved in your `config.mocksDir` following the filename convention.
119
92
  </details>
120
93
 
94
+ <br/>
95
+
96
+ ## Motivation
97
+
98
+ **No API state should be too hard to test.**
99
+ With Mockaton, developers can achieve correctness and speed.
100
+
101
+ ### Correctness
102
+ - Enables testing of complex scenarios that would otherwise be skipped. e.g.,
103
+ - Triggering errors on third-party APIs.
104
+ - Triggering errors on your project’s backend (if you are a frontend developer).
105
+ - Allows for deterministic, comprehensive, and consistent state.
106
+ - Spot inadvertent regressions during development.
107
+ - Use it to set up screenshot tests, e.g., with [pixaton](https://github.com/ericfortis/pixaton).
108
+
109
+ ### Speed
110
+ - Works around unstable dev backends while developing UIs.
111
+ - Spinning up development infrastructure.
112
+ - Syncing database states.
113
+ - Prevents progress from being blocked by waiting for APIs.
114
+ - Time travel. If you commit the mocks to your repo,
115
+ you don’t have to downgrade backends for:
116
+ - checking out long-lived branches
117
+ - bisecting bugs
118
+
121
119
 
122
120
  <br/>
123
121
 
package/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Server, IncomingMessage, OutgoingMessage } from 'node:http';
1
+ import { Server, IncomingMessage, OutgoingMessage } from 'node:http'
2
2
 
3
3
  type Plugin = (
4
4
  filePath: string,
@@ -16,7 +16,7 @@ interface Config {
16
16
 
17
17
  host?: string,
18
18
  port?: number
19
-
19
+
20
20
  logLevel?: 'normal' | 'verbose' | 'quiet'
21
21
 
22
22
  delay?: number
@@ -37,8 +37,8 @@ interface Config {
37
37
  corsExposedHeaders?: string[]
38
38
  corsCredentials?: boolean
39
39
  corsMaxAge?: number
40
-
41
-
40
+
41
+
42
42
  plugins?: [filenameTester: RegExp, plugin: Plugin][]
43
43
 
44
44
  onReady?: (address: string) => void
@@ -64,11 +64,11 @@ export type JsonPromise<T> = Promise<Response & { json(): Promise<T> }>
64
64
 
65
65
  export type ClientMockBroker = {
66
66
  mocks: string[]
67
- currentMock: {
68
- file: string
69
- delayed: boolean
70
- proxied: boolean
71
- }
67
+ file: string
68
+ status: number
69
+ auto500: boolean
70
+ delayed: boolean
71
+ proxied: boolean
72
72
  }
73
73
  export type ClientBrokersByMethod = {
74
74
  [method: string]: {
@@ -89,14 +89,14 @@ export type ClientStaticBrokers = {
89
89
  export interface State {
90
90
  brokersByMethod: ClientBrokersByMethod
91
91
  staticBrokers: ClientStaticBrokers
92
-
93
- cookies: [label:string, selected:boolean][]
92
+
93
+ cookies: [label: string, selected: boolean][]
94
94
  comments: string[]
95
-
95
+
96
96
  delay: number
97
-
97
+
98
98
  collectProxied: boolean
99
99
  proxyFallback: string
100
-
100
+
101
101
  corsAllowed?: boolean
102
102
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "mockaton",
3
3
  "description": "HTTP Mock Server",
4
4
  "type": "module",
5
- "version": "10.6.8",
5
+ "version": "11.0.0",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
@@ -18,12 +18,7 @@
18
18
  "mockaton": "src/cli.js"
19
19
  },
20
20
  "scripts": {
21
- "test": "node --test 'src/**/*.test.js'",
22
- "coverage": "node --test --test-reporter=lcov --test-reporter-destination=lcov.info --experimental-test-coverage 'src/**/*.test.js'",
23
- "start": "node src/cli.js",
24
- "watch": "node --watch src/cli.js",
25
- "pixaton": "node --test --import=./pixaton-tests/_setup.js --experimental-test-isolation=none 'pixaton-tests/**/*.test.js'",
26
- "outdated": "npm outdated --parseable | awk -F: '{ printf \"npm i %s ;# %s\\n\", $4, $2 }'"
21
+ "start": "make start"
27
22
  },
28
23
  "devDependencies": {
29
24
  "pixaton": "1.1.3",
package/src/Api.js CHANGED
@@ -12,17 +12,21 @@ import * as staticCollection from './staticCollection.js'
12
12
  import * as mockBrokersCollection from './mockBrokersCollection.js'
13
13
  import { config, ConfigValidator } from './config.js'
14
14
  import { DashboardHtml, CSP } from './DashboardHtml.js'
15
- import { DF, API, LONG_POLL_SERVER_TIMEOUT } from './ApiConstants.js'
16
15
  import { sendOK, sendJSON, sendUnprocessableContent, sendFile, sendHTML } from './utils/http-response.js'
16
+ import { API, LONG_POLL_SERVER_TIMEOUT, HEADER_FOR_SYNC_VERSION } from './ApiConstants.js'
17
17
 
18
18
 
19
19
  export const apiGetRequests = new Map([
20
20
  [API.dashboard, serveDashboard],
21
21
  ...[
22
+ 'Logo.svg',
22
23
  'Dashboard.css',
24
+ 'ApiCommander.js',
25
+ 'ApiConstants.js',
23
26
  'Dashboard.js',
24
- 'ApiConstants.js', 'ApiCommander.js', 'Filename.js', 'DashboardStore.js', 'DashboardDom.js',
25
- 'Logo.svg'
27
+ 'DashboardDom.js',
28
+ 'DashboardStore.js',
29
+ 'Filename.js'
26
30
  ].map(f => [API.dashboard + '/' + f, serveStatic(f)]),
27
31
 
28
32
  [API.state, getState],
@@ -57,6 +61,7 @@ function serveStatic(f) {
57
61
  return (_, response) => sendFile(response, join(import.meta.dirname, f))
58
62
  }
59
63
 
64
+
60
65
  function getState(_, response) {
61
66
  sendJSON(response, {
62
67
  cookies: cookie.list(),
@@ -74,12 +79,14 @@ function getState(_, response) {
74
79
  })
75
80
  }
76
81
 
82
+
77
83
  function longPollClientSyncVersion(req, response) {
78
- if (uiSyncVersion.version !== Number(req.headers[DF.syncVersion])) {
84
+ if (uiSyncVersion.version !== Number(req.headers[HEADER_FOR_SYNC_VERSION])) {
79
85
  // e.g., tab was hidden while new mocks were added or removed
80
86
  sendJSON(response, uiSyncVersion.version)
81
87
  return
82
88
  }
89
+
83
90
  function onAddOrRemoveMock() {
84
91
  uiSyncVersion.unsubscribe(onAddOrRemoveMock)
85
92
  sendJSON(response, uiSyncVersion.version)
@@ -102,127 +109,139 @@ function reinitialize(_, response) {
102
109
  sendOK(response)
103
110
  }
104
111
 
112
+
105
113
  async function selectCookie(req, response) {
106
- const error = cookie.setCurrent(await parseJSON(req))
114
+ const label = await parseJSON(req)
115
+
116
+ const error = cookie.setCurrent(label)
107
117
  if (error)
108
118
  sendUnprocessableContent(response, error?.message || error)
109
119
  else
110
120
  sendJSON(response, cookie.list())
111
121
  }
112
122
 
123
+
113
124
  async function selectMock(req, response) {
114
125
  const file = await parseJSON(req)
126
+
115
127
  const broker = mockBrokersCollection.brokerByFilename(file)
116
128
  if (!broker || !broker.hasMock(file))
117
129
  sendUnprocessableContent(response, `Missing Mock: ${file}`)
118
130
  else {
119
131
  broker.selectFile(file)
120
- sendJSON(response, broker.currentMock)
132
+ sendJSON(response, broker)
121
133
  }
122
134
  }
123
135
 
136
+
124
137
  async function toggle500(req, response) {
125
- const body = await parseJSON(req)
126
- const broker = mockBrokersCollection.brokerByRoute(
127
- body[DF.routeMethod],
128
- body[DF.routeUrlMask])
138
+ const [method, urlMask] = await parseJSON(req)
139
+
140
+ const broker = mockBrokersCollection.brokerByRoute(method, urlMask)
129
141
  if (!broker)
130
- sendUnprocessableContent(response, `Route does not exist: ${body[DF.routeMethod]} ${body[DF.routeUrlMask]}`)
142
+ sendUnprocessableContent(response, `Route does not exist: ${method} ${urlMask}`)
131
143
  else {
132
144
  broker.toggle500()
133
- sendJSON(response, broker.currentMock)
145
+ sendJSON(response, broker)
134
146
  }
135
147
  }
136
148
 
149
+
137
150
  async function setRouteIsDelayed(req, response) {
138
- const body = await parseJSON(req)
139
- const delayed = body[DF.delayed]
140
- const broker = mockBrokersCollection.brokerByRoute(
141
- body[DF.routeMethod],
142
- body[DF.routeUrlMask])
151
+ const [method, urlMask, delayed] = await parseJSON(req)
143
152
 
153
+ const broker = mockBrokersCollection.brokerByRoute(method, urlMask)
144
154
  if (!broker)
145
- sendUnprocessableContent(response, `Route does not exist: ${body[DF.routeMethod]} ${body[DF.routeUrlMask]}`)
155
+ sendUnprocessableContent(response, `Route does not exist: ${method} ${urlMask}`)
146
156
  else if (typeof delayed !== 'boolean')
147
157
  sendUnprocessableContent(response, `Expected boolean for "delayed"`)
148
158
  else {
149
159
  broker.setDelayed(delayed)
150
- sendOK(response)
160
+ sendJSON(response, broker)
151
161
  }
152
162
  }
153
163
 
164
+
154
165
  async function setRouteIsProxied(req, response) {
155
- const body = await parseJSON(req)
156
- const proxied = body[DF.proxied]
157
- const broker = mockBrokersCollection.brokerByRoute(
158
- body[DF.routeMethod],
159
- body[DF.routeUrlMask])
166
+ const [method, urlMask, proxied] = await parseJSON(req)
160
167
 
168
+ const broker = mockBrokersCollection.brokerByRoute(method, urlMask)
161
169
  if (!broker)
162
- sendUnprocessableContent(response, `Route does not exist: ${body[DF.routeMethod]} ${body[DF.routeUrlMask]}`)
170
+ sendUnprocessableContent(response, `Route does not exist: ${method} ${urlMask}`)
163
171
  else if (typeof proxied !== 'boolean')
164
172
  sendUnprocessableContent(response, `Expected boolean for "proxied"`)
165
173
  else if (proxied && !config.proxyFallback)
166
174
  sendUnprocessableContent(response, `There’s no proxy fallback`)
167
175
  else {
168
176
  broker.setProxied(proxied)
169
- sendOK(response)
177
+ sendJSON(response, broker)
170
178
  }
171
179
  }
172
180
 
181
+
173
182
  async function updateProxyFallback(req, response) {
174
183
  const fallback = await parseJSON(req)
175
- if (!ConfigValidator.proxyFallback(fallback)) {
184
+
185
+ if (!ConfigValidator.proxyFallback(fallback))
176
186
  sendUnprocessableContent(response, `Invalid Proxy Fallback URL`)
177
- return
187
+ else {
188
+ config.proxyFallback = fallback
189
+ sendOK(response)
178
190
  }
179
- config.proxyFallback = fallback
180
- sendOK(response)
181
191
  }
182
192
 
193
+
183
194
  async function setCollectProxied(req, response) {
184
195
  const collectProxied = await parseJSON(req)
185
- if (!ConfigValidator.collectProxied(collectProxied)) {
196
+
197
+ if (!ConfigValidator.collectProxied(collectProxied))
186
198
  sendUnprocessableContent(response, `Expected a boolean for "collectProxied"`)
187
- return
199
+ else {
200
+ config.collectProxied = collectProxied
201
+ sendOK(response)
188
202
  }
189
- config.collectProxied = collectProxied
190
- sendOK(response)
191
203
  }
192
204
 
205
+
193
206
  async function bulkUpdateBrokersByCommentTag(req, response) {
194
- mockBrokersCollection.setMocksMatchingComment(await parseJSON(req))
207
+ const comment = await parseJSON(req)
208
+
209
+ mockBrokersCollection.setMocksMatchingComment(comment)
195
210
  sendOK(response)
196
211
  }
197
212
 
213
+
198
214
  async function setCorsAllowed(req, response) {
199
215
  const corsAllowed = await parseJSON(req)
200
- if (!ConfigValidator.corsAllowed(corsAllowed)) {
216
+
217
+ if (!ConfigValidator.corsAllowed(corsAllowed))
201
218
  sendUnprocessableContent(response, `Expected boolean for "corsAllowed"`)
202
- return
219
+ else {
220
+ config.corsAllowed = corsAllowed
221
+ sendOK(response)
203
222
  }
204
- config.corsAllowed = corsAllowed
205
- sendOK(response)
206
223
  }
207
224
 
225
+
208
226
  async function setGlobalDelay(req, response) {
209
227
  const delay = await parseJSON(req)
210
- if (!ConfigValidator.delay(delay)) {
228
+
229
+ if (!ConfigValidator.delay(delay))
211
230
  sendUnprocessableContent(response, `Expected non-negative integer for "delay"`)
212
- return
231
+ else {
232
+ config.delay = delay
233
+ sendOK(response)
213
234
  }
214
- config.delay = delay
215
- sendOK(response)
216
235
  }
217
236
 
218
237
 
238
+
219
239
  async function setStaticRouteStatusCode(req, response) {
220
- const body = await parseJSON(req)
221
- const status = Number(body[DF.statusCode])
222
- const broker = staticCollection.brokerByRoute(body[DF.routeUrlMask])
240
+ const [urlMask, status] = await parseJSON(req)
223
241
 
242
+ const broker = staticCollection.brokerByRoute(urlMask)
224
243
  if (!broker)
225
- sendUnprocessableContent(response, `Static route does not exist: ${body[DF.routeUrlMask]}`)
244
+ sendUnprocessableContent(response, `Static route does not exist: ${urlMask}`)
226
245
  else if (!(status === 200 || status === 404))
227
246
  sendUnprocessableContent(response, `Expected 200 or 404 status code`)
228
247
  else {
@@ -233,12 +252,11 @@ async function setStaticRouteStatusCode(req, response) {
233
252
 
234
253
 
235
254
  async function setStaticRouteIsDelayed(req, response) {
236
- const body = await parseJSON(req)
237
- const delayed = body[DF.delayed]
238
- const broker = staticCollection.brokerByRoute(body[DF.routeUrlMask])
255
+ const [urlMask, delayed] = await parseJSON(req)
239
256
 
257
+ const broker = staticCollection.brokerByRoute(urlMask)
240
258
  if (!broker)
241
- sendUnprocessableContent(response, `Static route does not exist: ${body[DF.routeUrlMask]}`)
259
+ sendUnprocessableContent(response, `Static route does not exist: ${urlMask}`)
242
260
  else if (typeof delayed !== 'boolean')
243
261
  sendUnprocessableContent(response, `Expected boolean for "delayed"`)
244
262
  else {
@@ -1,4 +1,4 @@
1
- import { API, DF, LONG_POLL_SERVER_TIMEOUT } from './ApiConstants.js'
1
+ import { API, LONG_POLL_SERVER_TIMEOUT, HEADER_FOR_SYNC_VERSION } from './ApiConstants.js'
2
2
 
3
3
 
4
4
  /** Client for controlling Mockaton via its HTTP API */
@@ -9,27 +9,27 @@ export class Commander {
9
9
  this.#addr = addr
10
10
  }
11
11
 
12
- #patch = (api, body) => {
13
- return fetch(this.#addr + api, {
12
+ #patch = (api, body) =>
13
+ fetch(this.#addr + api, {
14
14
  method: 'PATCH',
15
15
  body: JSON.stringify(body)
16
16
  })
17
- }
18
17
 
19
18
  /** @returns {JsonPromise<State>} */
20
- getState = () => {
21
- return fetch(this.#addr + API.state)
22
- }
19
+ getState = () =>
20
+ fetch(this.#addr + API.state)
23
21
 
24
22
  /** @returns {JsonPromise<number>} */
25
- getSyncVersion = (currentSyncVersion, abortSignal) => {
26
- return fetch(this.#addr + API.syncVersion, {
27
- signal: AbortSignal.any([abortSignal, AbortSignal.timeout(LONG_POLL_SERVER_TIMEOUT + 1000)]),
23
+ getSyncVersion = (currSyncVer, abortSignal) =>
24
+ fetch(this.#addr + API.syncVersion, {
25
+ signal: AbortSignal.any([
26
+ abortSignal,
27
+ AbortSignal.timeout(LONG_POLL_SERVER_TIMEOUT + 1000)
28
+ ]),
28
29
  headers: {
29
- [DF.syncVersion]: currentSyncVersion
30
+ [HEADER_FOR_SYNC_VERSION]: currSyncVer
30
31
  }
31
32
  })
32
- }
33
33
 
34
34
 
35
35
  reset() {
@@ -40,45 +40,28 @@ export class Commander {
40
40
  return this.#patch(API.select, file)
41
41
  }
42
42
 
43
- toggle500(routeMethod, routeUrlMask) {
44
- return this.#patch(API.toggle500, {
45
- [DF.routeMethod]: routeMethod,
46
- [DF.routeUrlMask]: routeUrlMask
47
- })
43
+ toggle500(method, urlMask) {
44
+ return this.#patch(API.toggle500, [method, urlMask])
48
45
  }
49
46
 
50
47
  bulkSelectByComment(comment) {
51
48
  return this.#patch(API.bulkSelect, comment)
52
49
  }
53
50
 
54
- setRouteIsDelayed(routeMethod, routeUrlMask, delayed) {
55
- return this.#patch(API.delay, {
56
- [DF.routeMethod]: routeMethod,
57
- [DF.routeUrlMask]: routeUrlMask,
58
- [DF.delayed]: delayed
59
- })
51
+ setRouteIsDelayed(method, urlMask, delayed) {
52
+ return this.#patch(API.delay, [method, urlMask, delayed])
60
53
  }
61
54
 
62
- setStaticRouteIsDelayed(routeUrlMask, delayed) {
63
- return this.#patch(API.delayStatic, {
64
- [DF.routeUrlMask]: routeUrlMask,
65
- [DF.delayed]: delayed
66
- })
55
+ setStaticRouteIsDelayed(urlMask, delayed) {
56
+ return this.#patch(API.delayStatic, [urlMask, delayed])
67
57
  }
68
58
 
69
- setStaticRouteStatus(routeUrlMask, status) {
70
- return this.#patch(API.staticStatus, {
71
- [DF.routeUrlMask]: routeUrlMask,
72
- [DF.statusCode]: status
73
- })
59
+ setStaticRouteStatus(urlMask, status) {
60
+ return this.#patch(API.staticStatus, [urlMask, status])
74
61
  }
75
62
 
76
- setRouteIsProxied(routeMethod, routeUrlMask, proxied) {
77
- return this.#patch(API.proxied, {
78
- [DF.routeMethod]: routeMethod,
79
- [DF.routeUrlMask]: routeUrlMask,
80
- [DF.proxied]: proxied
81
- })
63
+ setRouteIsProxied(method, urlMask, proxied) {
64
+ return this.#patch(API.proxied, [method, urlMask, proxied])
82
65
  }
83
66
 
84
67
  selectCookie(cookieKey) {
@@ -19,21 +19,9 @@ export const API = {
19
19
  toggle500: MOUNT + '/toggle500'
20
20
  }
21
21
 
22
- export const DF = { // Dashboard Fields (XHR)
23
- routeMethod: 'route_method',
24
- routeUrlMask: 'route_url_mask',
25
- delayed: 'delayed',
26
- proxied: 'proxied',
27
- statusCode: 'status_code',
28
- syncVersion: 'last_received_sync_version'
29
- }
22
+ export const HEADER_FOR_502 = 'Mockaton502'
23
+ export const HEADER_FOR_SYNC_VERSION = 'sync_version'
30
24
 
31
- // TODO @ThinkAbout these affecting partial matches when bulk-selecting
32
- // e.g. 'ton' would match
33
- export const AUTO_500_COMMENT = '(Mockaton 500)'
34
25
  export const DEFAULT_MOCK_COMMENT = '(default)'
35
-
36
26
  export const EXT_FOR_UNKNOWN_MIME = 'unknown'
37
27
  export const LONG_POLL_SERVER_TIMEOUT = 8_000
38
-
39
- export const HEADER_FOR_502 = 'Mockaton502'
package/src/Dashboard.css CHANGED
@@ -369,6 +369,19 @@ table {
369
369
  > tr:first-child > th {
370
370
  border-top: 0;
371
371
  }
372
+
373
+ tr.animIn {
374
+ opacity: 0;
375
+ transform: scaleY(0);
376
+ animation: _kfAnimIn 180ms ease-in-out forwards;
377
+ }
378
+ }
379
+
380
+ @keyframes _kfAnimIn {
381
+ to {
382
+ opacity: 1;
383
+ transform: scaleY(1);
384
+ }
372
385
  }
373
386
 
374
387
  .Method {
@@ -544,11 +557,22 @@ table {
544
557
  }
545
558
  }
546
559
 
560
+ &:disabled + span {
561
+ cursor: not-allowed;
562
+ opacity: 0.7;
563
+ }
547
564
  &:checked + span {
548
565
  border-color: var(--colorRed);
549
566
  color: white;
550
567
  background: var(--colorRed);
551
568
  }
569
+ &:not(:checked):enabled:hover + span {
570
+ border-color: var(--colorRed);
571
+ color: var(--colorRed);
572
+ }
573
+ &:enabled:active + span {
574
+ cursor: grabbing;
575
+ }
552
576
  }
553
577
 
554
578
  > span {
@@ -558,14 +582,6 @@ table {
558
582
  font-weight: bold;
559
583
  color: var(--colorSecondaryAction);
560
584
  border-radius: var(--radius);
561
-
562
- &:hover {
563
- border-color: var(--colorRed);
564
- color: var(--colorRed);
565
- }
566
- &:active {
567
- cursor: grabbing;
568
- }
569
585
  }
570
586
  }
571
587