mockaton 10.6.0 → 10.6.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": "10.6.0",
5
+ "version": "10.6.2",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
@@ -23,10 +23,10 @@
23
23
  "start": "node src/cli.js",
24
24
  "watch": "node --watch src/cli.js",
25
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 %-30s ;# %s\\n\", $4, $2 }'"
26
+ "outdated": "npm outdated --parseable | awk -F: '{ printf \"npm i %s ;# %s\\n\", $4, $2 }'"
27
27
  },
28
28
  "devDependencies": {
29
29
  "pixaton": "1.1.3",
30
- "puppeteer": "24.23.0"
30
+ "puppeteer": "24.24.1"
31
31
  }
32
32
  }
package/src/Dashboard.css CHANGED
@@ -343,7 +343,7 @@ main {
343
343
  }
344
344
 
345
345
  .leftSide {
346
- width: 50%;
346
+ /* the width is set in js (it’s resizable) */
347
347
  padding: 16px;
348
348
  border-right: 1px solid var(--colorSecondaryActionBorder);
349
349
  user-select: none;
package/src/Dashboard.js CHANGED
@@ -69,6 +69,8 @@ const state = /** @type {State} */ {
69
69
  return Boolean(state.proxyFallback)
70
70
  },
71
71
 
72
+ leftSideWidth: window.innerWidth / 2,
73
+
72
74
  groupByMethod: initPreference('groupByMethod'),
73
75
  toggleGroupByMethod() {
74
76
  state.groupByMethod = !state.groupByMethod
@@ -76,12 +78,7 @@ const state = /** @type {State} */ {
76
78
  updateState()
77
79
  },
78
80
 
79
- leftSideWidth: undefined,
80
-
81
81
  chosenLink: { method: '', urlMask: '' },
82
- clearChosenLink() {
83
- state.chosenLink = { method: '', urlMask: '' }
84
- },
85
82
  setChosenLink(method, urlMask) {
86
83
  state.chosenLink = { method, urlMask }
87
84
  }
@@ -90,7 +87,7 @@ const state = /** @type {State} */ {
90
87
 
91
88
  const mockaton = new Commander(location.origin)
92
89
  updateState()
93
- initLongPoll()
90
+ initRealTimeUpdates()
94
91
  initKeyboardNavigation()
95
92
 
96
93
  async function updateState() {
@@ -98,12 +95,18 @@ async function updateState() {
98
95
  const response = await mockaton.getState()
99
96
  if (!response.ok)
100
97
  throw response.status
98
+
101
99
  Object.assign(state, await response.json())
100
+
101
+ const focusedElem = selectorFor(document.activeElement)
102
102
  document.body.replaceChildren(...App())
103
- findChosenLink()?.focus()
103
+ if (focusedElem)
104
+ document.querySelector(focusedElem)?.focus()
105
+
104
106
  const { method, urlMask } = state.chosenLink
105
107
  if (method && urlMask)
106
108
  await previewMock(method, urlMask)
109
+
107
110
  }
108
111
  catch (error) {
109
112
  onError(error)
@@ -168,18 +171,25 @@ function SettingsMenuTrigger() {
168
171
 
169
172
  function SettingsMenu(id) {
170
173
  const { groupByMethod, toggleGroupByMethod } = state
174
+
175
+ const firstInputRef = useRef()
176
+ function onToggle(event) {
177
+ if (event.newState === 'open')
178
+ firstInputRef.current.focus()
179
+ }
171
180
  return (
172
181
  r('menu', {
173
182
  id,
174
183
  popover: '',
175
- className: CSS.SettingsMenu
184
+ className: CSS.SettingsMenu,
185
+ onToggle
176
186
  },
177
187
 
178
188
  r('label', className(CSS.GroupByMethod),
179
189
  r('input', {
190
+ ref: firstInputRef,
180
191
  type: 'checkbox',
181
192
  checked: groupByMethod,
182
- // autofocus: true, // TODO
183
193
  onChange: toggleGroupByMethod
184
194
  }),
185
195
  r('span', null, t`Group by Method`)),
@@ -219,13 +229,11 @@ function BulkSelector() {
219
229
  // But this way is easier to implement, with a few hacks.
220
230
  const firstOption = t`Pick Comment…`
221
231
  function onChange() {
222
- state.clearChosenLink()
223
232
  const value = this.value
224
233
  this.value = firstOption // Hack
225
234
  mockaton.bulkSelectByComment(value)
226
235
  .then(parseError)
227
236
  .then(updateState)
228
- .then(() => focus(`.${CSS.BulkSelector}`))
229
237
  .catch(onError)
230
238
  }
231
239
  const disabled = !comments.length
@@ -290,7 +298,6 @@ function ProxyFallbackField() {
290
298
  mockaton.setProxyFallback(this.value.trim())
291
299
  .then(parseError)
292
300
  .then(updateState)
293
- .then(() => focus(`.${CSS.FallbackBackend} input`))
294
301
  .catch(onError)
295
302
  }
296
303
  return (
@@ -328,11 +335,9 @@ function SaveProxiedCheckbox(ref) {
328
335
 
329
336
  function ResetButton() {
330
337
  function onClick() {
331
- state.clearChosenLink()
332
338
  mockaton.reset()
333
339
  .then(parseError)
334
340
  .then(updateState)
335
- .then(() => focus(`.${CSS.ResetButton}`))
336
341
  .catch(onError)
337
342
  }
338
343
  return (
@@ -368,7 +373,7 @@ function MockList() {
368
373
  function Row({ method, urlMask, urlMaskDittoed, broker }, i) {
369
374
  const { canProxy, groupByMethod } = state
370
375
  return (
371
- r('tr', { 'data-method': method, 'data-urlMask': urlMask },
376
+ r('tr', { key: method + '::' + urlMask },
372
377
  canProxy && r('td', null, ProxyToggler(broker)),
373
378
  r('td', null, DelayRouteToggler(broker)),
374
379
  r('td', null, InternalServerErrorToggler(broker)),
@@ -621,6 +626,7 @@ function ClickDragToggler({ checked, commit, focusGroup }) {
621
626
  }
622
627
  function onPointerDown() {
623
628
  this.checked = !this.checked
629
+ this.focus()
624
630
  commit(this.checked)
625
631
  }
626
632
  function onClick(event) {
@@ -649,38 +655,48 @@ function ClickDragToggler({ checked, commit, focusGroup }) {
649
655
 
650
656
 
651
657
  function Resizer() {
658
+ let raf = 0
659
+ let initialX = 0
660
+ let panelWidth = 0
661
+
662
+ function onPointerDown(event) {
663
+ initialX = event.clientX
664
+ panelWidth = leftSideRef.current.clientWidth
665
+ addEventListener('pointerup', onUp, { once: true })
666
+ addEventListener('pointermove', onMove)
667
+ Object.assign(document.body.style, {
668
+ cursor: 'col-resize',
669
+ userSelect: 'none',
670
+ pointerEvents: 'none'
671
+ })
672
+ }
673
+
674
+ function onMove(event) {
675
+ const MIN_LEFT_WIDTH = 380
676
+ raf = raf || requestAnimationFrame(() => {
677
+ state.leftSideWidth = Math.max(panelWidth - (initialX - event.clientX), MIN_LEFT_WIDTH)
678
+ leftSideRef.current.style.width = state.leftSideWidth + 'px'
679
+ raf = 0
680
+ })
681
+ }
682
+
683
+ function onUp() {
684
+ removeEventListener('pointermove', onMove)
685
+ cancelAnimationFrame(raf)
686
+ raf = 0
687
+ Object.assign(document.body.style, {
688
+ cursor: 'auto',
689
+ userSelect: 'auto',
690
+ pointerEvents: 'auto'
691
+ })
692
+ }
693
+
652
694
  return (
653
695
  r('div', {
654
696
  className: CSS.Resizer,
655
- onPointerDown: Resizer.onPointerDown
697
+ onPointerDown
656
698
  }))
657
699
  }
658
- Resizer.raf = 0
659
- Resizer.initialX = 0
660
- Resizer.panelWidth = 0
661
- Resizer.onPointerDown = function (event) {
662
- Resizer.initialX = event.clientX
663
- Resizer.panelWidth = leftSideRef.current.clientWidth
664
- addEventListener('pointerup', Resizer.onUp, { once: true })
665
- addEventListener('pointermove', Resizer.onMove)
666
- document.body.style.userSelect = 'none'
667
- document.body.style.cursor = 'col-resize'
668
- }
669
- Resizer.onMove = function (event) {
670
- const MIN_LEFT_WIDTH = 380
671
- Resizer.raf = Resizer.raf || requestAnimationFrame(() => {
672
- state.leftSideWidth = Math.max(Resizer.panelWidth - (Resizer.initialX - event.clientX), MIN_LEFT_WIDTH)
673
- leftSideRef.current.style.width = state.leftSideWidth + 'px'
674
- Resizer.raf = 0
675
- })
676
- }
677
- Resizer.onUp = function () {
678
- removeEventListener('pointermove', Resizer.onMove)
679
- cancelAnimationFrame(Resizer.raf)
680
- Resizer.raf = 0
681
- document.body.style.userSelect = 'auto'
682
- document.body.style.cursor = 'auto'
683
- }
684
700
 
685
701
 
686
702
  /** # Payload Preview */
@@ -752,7 +768,9 @@ async function previewMock(method, urlMask) {
752
768
  async function updatePayloadViewer(method, urlMask, response) {
753
769
  const mime = response.headers.get('content-type') || ''
754
770
 
755
- const file = mockSelectorFor(method, urlMask).value
771
+ const file = mockSelectorFor(method, urlMask)?.value
772
+ if (!file)
773
+ return // e.g. selected was deleted
756
774
  if (file === STR_PROXIED)
757
775
  payloadViewerTitleRef.current.replaceChildren(PayloadViewerTitleWhenProxied({
758
776
  status: response.status,
@@ -789,19 +807,11 @@ function isXML(mime) {
789
807
  }
790
808
 
791
809
 
792
- function trFor(method, urlMask) {
793
- return document.querySelector(`tr[data-method="${method}"][data-urlMask="${urlMask}"]`)
794
- }
795
- function linkFor(method, urlMask) {
796
- return trFor(method, urlMask)?.querySelector(`a.${CSS.PreviewLink}`)
797
- }
798
810
  function mockSelectorFor(method, urlMask) {
799
- return trFor(method, urlMask)?.querySelector(`select.${CSS.MockSelector}`)
811
+ const tr = document.querySelector(`tr[key="${method}::${urlMask}"]`)
812
+ return tr?.querySelector(`.${CSS.MockSelector}`)
800
813
  }
801
814
 
802
- function focus(selector) {
803
- document.querySelector(selector)?.focus()
804
- }
805
815
 
806
816
  function initKeyboardNavigation() {
807
817
  addEventListener('keydown', onKeyDown)
@@ -822,7 +832,7 @@ function initKeyboardNavigation() {
822
832
  case 'ArrowLeft': {
823
833
  if (pivot.hasAttribute('data-focus-group') || pivot.classList.contains(CSS.MockSelector)) {
824
834
  const offset = event.key === 'ArrowRight' ? +1 : -1
825
- rowFocusable(pivot, offset)?.focus()
835
+ rowFocusable(pivot, offset).focus()
826
836
  }
827
837
  break
828
838
  }
@@ -847,7 +857,7 @@ function initKeyboardNavigation() {
847
857
  }
848
858
  }
849
859
 
850
- /** # Misc */
860
+ /** # Error */
851
861
 
852
862
  async function parseError(response) {
853
863
  if (response.ok)
@@ -879,6 +889,9 @@ function showErrorToast(msg) {
879
889
  }, msg))
880
890
  }
881
891
 
892
+
893
+ /** # Icons */
894
+
882
895
  function TimerIcon() {
883
896
  return (
884
897
  s('svg', { viewBox: '0 0 24 24' },
@@ -899,43 +912,43 @@ function SettingsIcon() {
899
912
  }
900
913
 
901
914
  /**
902
- * # Poll UI Sync Version
903
- * The version increments when a mock file is added or removed
915
+ * # Long polls UI sync version
916
+ * The version increments when a mock file is added, removed, or renamed.
904
917
  */
918
+ function initRealTimeUpdates() {
919
+ let oldSyncVersion = -1
920
+ let controller = new AbortController()
905
921
 
906
- function initLongPoll() {
907
- poll.oldSyncVersion = -1
908
- poll.controller = new AbortController()
909
922
  poll()
910
923
  document.addEventListener('visibilitychange', () => {
911
924
  if (document.hidden) {
912
- poll.controller.abort('_hidden_tab_')
913
- poll.controller = new AbortController()
925
+ controller.abort('_hidden_tab_')
926
+ controller = new AbortController()
914
927
  }
915
928
  else
916
929
  poll()
917
930
  })
918
- }
919
931
 
920
- async function poll() {
921
- try {
922
- const response = await mockaton.getSyncVersion(poll.oldSyncVersion, poll.controller.signal)
923
- if (response.ok) {
924
- const syncVersion = await response.json()
925
- const skipUpdate = poll.oldSyncVersion === -1
926
- if (poll.oldSyncVersion !== syncVersion) { // because it could be < or >
927
- poll.oldSyncVersion = syncVersion
928
- if (!skipUpdate)
929
- await updateState()
932
+ async function poll() {
933
+ try {
934
+ const response = await mockaton.getSyncVersion(oldSyncVersion, controller.signal)
935
+ if (response.ok) {
936
+ const syncVersion = await response.json()
937
+ const skipUpdate = oldSyncVersion === -1
938
+ if (oldSyncVersion !== syncVersion) { // because it could be < or >
939
+ oldSyncVersion = syncVersion
940
+ if (!skipUpdate)
941
+ await updateState()
942
+ }
943
+ poll()
930
944
  }
931
- poll()
945
+ else
946
+ throw response.status
947
+ }
948
+ catch (error) {
949
+ if (error !== '_hidden_tab_')
950
+ setTimeout(poll, 3000)
932
951
  }
933
- else
934
- throw response.status
935
- }
936
- catch (error) {
937
- if (error !== '_hidden_tab_')
938
- setTimeout(poll, 3000)
939
952
  }
940
953
  }
941
954
 
@@ -948,7 +961,6 @@ function className(...args) {
948
961
  }
949
962
  }
950
963
 
951
-
952
964
  function createElement(tag, props, ...children) {
953
965
  const node = document.createElement(tag)
954
966
  for (const [k, v] of Object.entries(props || {}))
@@ -995,6 +1007,30 @@ function deferred(cb) {
995
1007
  : setTimeout(cb, 100) // Safari
996
1008
  }
997
1009
 
1010
+ function selectorFor(elem) {
1011
+ if (!(elem instanceof Element))
1012
+ return
1013
+
1014
+ const path = []
1015
+ while (elem) {
1016
+ let mod = ''
1017
+ if (elem.hasAttribute('key'))
1018
+ mod = `[key="${elem.getAttribute('key')}"]`
1019
+ else {
1020
+ let i = 0
1021
+ let sib = elem
1022
+ while ((sib = sib.previousElementSibling))
1023
+ if (sib.nodeName === elem.nodeName)
1024
+ i++
1025
+ if (i)
1026
+ mod = `:nth-of-type(${i + 1})`
1027
+ }
1028
+ path.push(elem.nodeName + mod)
1029
+ elem = elem.parentElement
1030
+ }
1031
+ return path.reverse().join('>')
1032
+ }
1033
+
998
1034
 
999
1035
  // When false, the URL will be updated with param=false
1000
1036
  function initPreference(param) {
@@ -9,23 +9,23 @@ export const CSP = [
9
9
  export const DashboardHtml = `<!DOCTYPE html>
10
10
  <html lang="en-US">
11
11
  <head>
12
- <meta charset="UTF-8">
13
- <base href="${API.dashboard}/">
14
-
15
- <link rel="stylesheet" href="Dashboard.css">
16
- <script type="module" src="Dashboard.js"></script>
17
-
12
+ <meta charset="UTF-8">
13
+ <base href="${API.dashboard}/">
14
+
15
+ <link rel="stylesheet" href="Dashboard.css">
16
+ <script type="module" src="Dashboard.js"></script>
17
+
18
18
  <link rel="preload" href="${API.state}" as="fetch" crossorigin>
19
19
 
20
20
  <link rel="modulepreload" href="ApiConstants.js">
21
21
  <link rel="modulepreload" href="ApiCommander.js">
22
22
  <link rel="modulepreload" href="Filename.js">
23
23
  <link rel="preload" href="Logo.svg" as="image">
24
-
25
- <link rel="icon" href="data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m235 33.7v202c0 9.19-5.81 14-17.4 14-11.6 0-17.4-4.83-17.4-14v-151c-0.115-4.49-6.72-5.88-8.46-0.87l-48.3 155c-2.22 7.01-7.72 10.1-16 9.9-3.63-0.191-7.01-1.14-9.66-2.89-2.89-1.72-4.83-4.34-5.57-7.72-11.1-37-22.6-74.3-34.1-111-4.34-14-8.95-31.4-14-48.3-1.82-4.83-8.16-5.32-8.46 1.16v156c0 9.19-5.81 14-17.4 14-11.6 0-17.4-4.83-17.4-14v-207c0-5.74 2.62-13.2 9.39-16.3 7.5-3.14 15-4.05 21.8-3.8 3.14 0 6.03 0.686 8.95 1.46 3.14 0.797 6.03 1.98 8.7 3.63 2.65 1.38 5.32 3.14 7.5 5.57 2.22 2.22 3.87 4.83 5.07 7.72l45.8 157c4.63-15.9 32.4-117 33.3-121 4.12-13.8 7.72-26.5 10.9-38.7 1.16-2.65 2.89-5.32 5.07-7.5 2.15-2.15 4.58-4.12 7.5-5.32 2.65-1.57 5.57-2.89 8.46-3.63 3.14-0.797 9.44-0.988 12.1-0.988 11.6 1.07 29.4 9.14 29.4 27z' fill='%23808080'/%3E%3C/svg%3E">
26
- <meta name="viewport" content="width=device-width, initial-scale=1">
27
- <meta name="description" content="HTTP Mock Server">
28
- <title>Mockaton</title>
24
+
25
+ <link rel="icon" href="data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m235 33.7v202c0 9.19-5.81 14-17.4 14-11.6 0-17.4-4.83-17.4-14v-151c-0.115-4.49-6.72-5.88-8.46-0.87l-48.3 155c-2.22 7.01-7.72 10.1-16 9.9-3.63-0.191-7.01-1.14-9.66-2.89-2.89-1.72-4.83-4.34-5.57-7.72-11.1-37-22.6-74.3-34.1-111-4.34-14-8.95-31.4-14-48.3-1.82-4.83-8.16-5.32-8.46 1.16v156c0 9.19-5.81 14-17.4 14-11.6 0-17.4-4.83-17.4-14v-207c0-5.74 2.62-13.2 9.39-16.3 7.5-3.14 15-4.05 21.8-3.8 3.14 0 6.03 0.686 8.95 1.46 3.14 0.797 6.03 1.98 8.7 3.63 2.65 1.38 5.32 3.14 7.5 5.57 2.22 2.22 3.87 4.83 5.07 7.72l45.8 157c4.63-15.9 32.4-117 33.3-121 4.12-13.8 7.72-26.5 10.9-38.7 1.16-2.65 2.89-5.32 5.07-7.5 2.15-2.15 4.58-4.12 7.5-5.32 2.65-1.57 5.57-2.89 8.46-3.63 3.14-0.797 9.44-0.988 12.1-0.988 11.6 1.07 29.4 9.14 29.4 27z' fill='%23808080'/%3E%3C/svg%3E">
26
+ <meta name="viewport" content="width=device-width, initial-scale=1">
27
+ <meta name="description" content="HTTP Mock Server">
28
+ <title>Mockaton</title>
29
29
  </head>
30
30
  <body>
31
31
  </body>