mockaton 13.3.4 → 13.3.5

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": "13.3.4",
5
+ "version": "13.3.5",
6
6
  "exports": {
7
7
  ".": {
8
8
  "import": "./index.js",
@@ -1,11 +1,12 @@
1
- import { createElement as r, t, defineClassNames } from './dom-utils.js'
1
+ import { createElement as r, t, extractClassNames } from './dom-utils.js'
2
2
  import { Logo, HelpIcon } from './graphics.js'
3
3
  import { store } from './app-store.js'
4
4
 
5
5
  import CSS from './app.css' with { type: 'css' }
6
- defineClassNames(CSS)
7
6
 
8
7
 
8
+ Object.assign(CSS, extractClassNames(CSS))
9
+
9
10
  export function Header() {
10
11
  return (
11
12
  r('header', null,
@@ -1,12 +1,13 @@
1
- import { createElement as r, t, defineClassNames } from './dom-utils.js'
1
+ import { createElement as r, t, extractClassNames } from './dom-utils.js'
2
2
  import { HEADER_502 } from './ApiConstants.js'
3
3
  import { parseFilename } from './Filename.js'
4
4
  import { store } from './app-store.js'
5
5
 
6
6
  import CSS from './app.css' with { type: 'css' }
7
- defineClassNames(CSS)
8
7
 
9
8
 
9
+ Object.assign(CSS, extractClassNames(CSS))
10
+
10
11
  const titleRef = {}
11
12
  const codeRef = {}
12
13
 
@@ -1,5 +1,7 @@
1
1
  import { Commander } from './ApiCommander.js'
2
+ import { dittoSplitPaths, groupByFolder } from './dir-tree.js'
2
3
  import { parseFilename, extractComments } from './Filename.js'
4
+ import { QueryParamBool, LocalStorageSet } from './dom-utils.js'
3
5
  import { EXT_UNKNOWN_MIME, EXT_EMPTY } from './ApiConstants.js'
4
6
 
5
7
 
@@ -22,25 +24,21 @@ export const store = {
22
24
  collectProxied: false,
23
25
  proxyFallback: '',
24
26
  showProxyField: null,
25
- get canProxy() {
26
- return Boolean(store.proxyFallback)
27
- },
27
+ get canProxy() { return Boolean(store.proxyFallback) },
28
28
 
29
- groupByMethod: initPreference('groupByMethod'),
29
+ _groupByMethod: new QueryParamBool('groupByMethod'),
30
+ get groupByMethod() { return store._groupByMethod.value },
30
31
  toggleGroupByMethod() {
31
- store.groupByMethod = !store.groupByMethod
32
- togglePreference('groupByMethod', store.groupByMethod)
32
+ store._groupByMethod.toggle()
33
33
  store.render()
34
34
  },
35
35
 
36
- collapsedFolders: new Set(JSON.parse(globalThis.localStorage?.getItem('collapsedFolders') || '[]')),
36
+ collapsedFolders: new LocalStorageSet('collapsedFolders'),
37
37
  setFolderCollapsed(folder, collapsed) {
38
38
  if (collapsed)
39
39
  store.collapsedFolders.add(folder)
40
40
  else
41
41
  store.collapsedFolders.delete(folder)
42
-
43
- globalThis.localStorage?.setItem('collapsedFolders', JSON.stringify([...store.collapsedFolders]))
44
42
  },
45
43
 
46
44
  chosenLink: { method: '', urlMask: '' },
@@ -123,10 +121,6 @@ export const store = {
123
121
 
124
122
  _dittoCache: new Map(),
125
123
 
126
- brokerFor(method, urlMask) {
127
- return store.brokersByMethod[method]?.[urlMask]
128
- },
129
-
130
124
  brokerAsRow(method, urlMask) {
131
125
  const b = store.brokerFor(method, urlMask)
132
126
  const r = new BrokerRowModel(b, store.canProxy)
@@ -134,13 +128,15 @@ export const store = {
134
128
  return r
135
129
  },
136
130
 
137
- _setBroker(broker) {
138
- const { method, urlMask } = parseFilename(broker.file)
139
- store.brokersByMethod[method] ??= {}
140
- store.brokersByMethod[method][urlMask] = broker
131
+ brokerFor(method, urlMask) {
132
+ return store.brokersByMethod[method]?.[urlMask]
133
+ },
134
+
135
+ folderGroupsByMethod(method) {
136
+ return groupByFolder(store._brokersAsRowsByMethod(method))
141
137
  },
142
138
 
143
- brokersAsRowsByMethod(method) {
139
+ _brokersAsRowsByMethod(method) {
144
140
  const rows = store._brokersAsArray(method)
145
141
  .map(b => new BrokerRowModel(b, store.canProxy))
146
142
  .sort((a, b) => a.urlMask.localeCompare(b.urlMask))
@@ -200,11 +196,15 @@ export const store = {
200
196
  store._request(() => api.setRouteIsDelayed(method, urlMask, checked), async response => {
201
197
  store._setBroker(await response.json())
202
198
  })
199
+ },
200
+
201
+ _setBroker(broker) {
202
+ const { method, urlMask } = parseFilename(broker.file)
203
+ store.brokersByMethod[method] ??= {}
204
+ store.brokersByMethod[method][urlMask] = broker
203
205
  }
204
206
  }
205
207
 
206
-
207
-
208
208
  // When false, the URL will be updated with param=false
209
209
  function initPreference(param) {
210
210
  const qs = new URLSearchParams(globalThis.location?.search)
@@ -237,33 +237,6 @@ function togglePreference(param, nextVal) {
237
237
 
238
238
 
239
239
 
240
- /**
241
- * Think of this as a way of printing a directory tree in which
242
- * the repeated folder paths are kept but styled differently.
243
- * @param {string[]} paths - sorted
244
- */
245
- export function dittoSplitPaths(paths) {
246
- const pParts = paths.map(p => p.split('/').filter(Boolean))
247
- return paths.map((p, i) => {
248
- if (i === 0)
249
- return ['', p]
250
-
251
- const prev = pParts[i - 1]
252
- const curr = pParts[i]
253
- const min = Math.min(curr.length, prev.length)
254
- let j = 0
255
- while (j < min && curr[j] === prev[j])
256
- j++
257
-
258
- if (!j) // no common dirs
259
- return ['', p]
260
-
261
- const ditto = '/' + curr.slice(0, j).join('/') + '/'
262
- return [ditto, p.slice(ditto.length)]
263
- })
264
- }
265
-
266
-
267
240
  export class BrokerRowModel {
268
241
  opts = /** @type {[key:string, label:string, selected:boolean][]} */ []
269
242
  isNew = false
@@ -1,31 +1,7 @@
1
1
  import { test } from 'node:test'
2
2
  import { deepEqual } from 'node:assert/strict'
3
3
 
4
- import { dittoSplitPaths, BrokerRowModel, t } from './app-store.js'
5
-
6
-
7
- test('dittoSplitPaths', () => {
8
- const input = [
9
- '/api/user',
10
- '/api/user/avatar',
11
- '/api/user/friends',
12
- '/api/vid',
13
- '/api/video/id',
14
- '/api/video/stats',
15
- '/v2/foo',
16
- '/v2/foo/bar'
17
- ]
18
- deepEqual(dittoSplitPaths(input), [
19
- ['', '/api/user'],
20
- ['/api/user/', 'avatar'],
21
- ['/api/user/', 'friends'],
22
- ['/api/', 'vid'],
23
- ['/api/', 'video/id'],
24
- ['/api/video/', 'stats'],
25
- ['', '/v2/foo'],
26
- ['/v2/foo/', 'bar']
27
- ])
28
- })
4
+ import { BrokerRowModel, t } from './app-store.js'
29
5
 
30
6
 
31
7
  test('BrokerRowModel', () => {
@@ -185,15 +185,19 @@ header {
185
185
  }
186
186
 
187
187
  .HelpLink {
188
- width: 24px;
189
- height: 24px;
188
+ width: 22px;
189
+ height: 22px;
190
190
  flex-shrink: 0;
191
191
  align-self: end;
192
- margin-bottom: 2px;
192
+ margin-bottom: 3px;
193
193
  margin-left: auto;
194
194
  border-radius: 50%;
195
- fill: white;
196
- background: var(--colorAccent);
195
+ fill: var(--colorBgHeader);
196
+ background: var(--colorLabel);
197
+
198
+ &:hover {
199
+ background: var(--colorAccent);
200
+ }
197
201
 
198
202
  svg {
199
203
  transform: scale(.7);
package/src/client/app.js CHANGED
@@ -1,16 +1,15 @@
1
- import { createElement as r, t, classNames, restoreFocus, Fragment, defineClassNames } from './dom-utils.js'
1
+ import { createElement as r, t, restoreFocus, Fragment, classNames, extractClassNames } from './dom-utils.js'
2
2
 
3
3
  import { store } from './app-store.js'
4
4
  import { API } from './ApiConstants.js'
5
5
  import { Header } from './app-header.js'
6
- import { dirStructure } from './dirStructure.js'
7
6
  import { PayloadViewer, previewMock } from './app-payload-viewer.js'
8
7
  import { TimerIcon, CloudIcon, ChevronDownIcon } from './graphics.js'
9
8
 
10
9
  import CSS from './app.css' with { type: 'css' }
11
10
  document.adoptedStyleSheets.push(CSS)
12
- defineClassNames(CSS)
13
11
 
12
+ Object.assign(CSS, extractClassNames(CSS))
14
13
 
15
14
  store.onError = onError
16
15
  store.render = render
@@ -100,14 +99,14 @@ function MockList() {
100
99
  r('div', {
101
100
  className: classNames(CSS.TableHeading, store.canProxy && CSS.canProxy)
102
101
  }, method),
103
- FolderGroups(store.brokersAsRowsByMethod(method))))
102
+ FolderGroups(store.folderGroupsByMethod(method))))
104
103
 
105
- return FolderGroups(store.brokersAsRowsByMethod('*'))
104
+ return FolderGroups(store.folderGroupsByMethod('*'))
106
105
  }
107
106
 
108
- function FolderGroups(bRows) {
107
+ function FolderGroups(brokersTree) {
109
108
  const res = []
110
- for (const b of dirStructure(bRows)) {
109
+ for (const b of brokersTree) {
111
110
  if (!b.children.length)
112
111
  res.push(Row(b))
113
112
  else
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Think of this as a way of printing a directory tree in which
3
+ * the repeated folder paths are kept but styled differently.
4
+ * @param {string[]} paths - sorted
5
+ */
6
+ export function dittoSplitPaths(paths) {
7
+ const pParts = paths.map(p => p.split('/').filter(Boolean))
8
+ return paths.map((p, i) => {
9
+ if (i === 0)
10
+ return ['', p]
11
+
12
+ const prev = pParts[i - 1]
13
+ const curr = pParts[i]
14
+ const min = Math.min(curr.length, prev.length)
15
+ let j = 0
16
+ while (j < min && curr[j] === prev[j])
17
+ j++
18
+
19
+ if (!j) // no common dirs
20
+ return ['', p]
21
+
22
+ const ditto = '/' + curr.slice(0, j).join('/') + '/'
23
+ return [ditto, p.slice(ditto.length)]
24
+ })
25
+ }
26
+
27
+
28
+
29
+ /**
30
+ * @param {Partial<BrokerRowModel>[]} brokers
31
+ * @returns {Partial<BrokerRowModel>[]}
32
+ */
33
+ export function groupByFolder(brokers) {
34
+ return dfs(trie(brokers))
35
+ }
36
+
37
+ function trie(brokers) {
38
+ const root = new TrieNode()
39
+ for (const b of brokers) {
40
+ let node = root
41
+ for (const seg of b.urlMask.split('/')) { // TODO it should ignore query string
42
+ const segNode = node.getChild(seg) || new TrieNode()
43
+ node.addChild(seg, segNode)
44
+ node = segNode
45
+ }
46
+ node.brokers.push(b)
47
+ }
48
+ return root
49
+ }
50
+
51
+ class TrieNode {
52
+ #children
53
+ constructor() {
54
+ this.brokers = []
55
+ this.#children = new Map()
56
+ }
57
+ addChild(k, v) { this.#children.set(k, v) }
58
+ getChild(k) { return this.#children.get(k) }
59
+ getChildren() { return this.#children.values() }
60
+ }
61
+
62
+ /** @param {TrieNode} node */
63
+ function dfs(node) {
64
+ const childBrokers = []
65
+ for (const tnc of node.getChildren())
66
+ childBrokers.push(...dfs(tnc))
67
+
68
+ const brokers = node.brokers.length
69
+ ? [node.brokers[0], ...childBrokers, ...node.brokers.slice(1)]
70
+ : childBrokers
71
+
72
+ if (!brokers.length)
73
+ return []
74
+
75
+ const [b0, ...rest] = brokers
76
+ if (node.brokers.length || !b0.children.length) {
77
+ b0.children.push(...rest)
78
+ return [b0]
79
+ }
80
+ return brokers
81
+ }
82
+
@@ -0,0 +1,106 @@
1
+ import { test } from 'node:test'
2
+ import { deepEqual } from 'node:assert/strict'
3
+ import { groupByFolder, dittoSplitPaths } from './dir-tree.js'
4
+
5
+
6
+ test('dittoSplitPaths', () => {
7
+ const input = [
8
+ '/api/user',
9
+ '/api/user/avatar',
10
+ '/api/user/friends',
11
+ '/api/vid',
12
+ '/api/video/id',
13
+ '/api/video/stats',
14
+ '/v2/foo',
15
+ '/v2/foo/bar'
16
+ ]
17
+ deepEqual(dittoSplitPaths(input), [
18
+ ['', '/api/user'],
19
+ ['/api/user/', 'avatar'],
20
+ ['/api/user/', 'friends'],
21
+ ['/api/', 'vid'],
22
+ ['/api/', 'video/id'],
23
+ ['/api/video/', 'stats'],
24
+ ['', '/v2/foo'],
25
+ ['/v2/foo/', 'bar']
26
+ ])
27
+ })
28
+
29
+
30
+ test('dirStructure', () => {
31
+ const input = [
32
+ { children: [], method: 'GET', urlMask: '/api/user' },
33
+ { children: [], method: 'GET', urlMask: '/api/user/avatar' },
34
+ { children: [], method: 'GET', urlMask: '/api/video/[id]' },
35
+ { children: [], method: 'GET', urlMask: '/index.html' },
36
+ { children: [], method: 'GET', urlMask: '/media/file-a.txt' },
37
+ { children: [], method: 'GET', urlMask: '/media/file-b.txt' },
38
+ { children: [], method: 'GET', urlMask: '/media/sub/file-aa.txt' },
39
+ { children: [], method: 'GET', urlMask: '/media/sub/file-bb.txt' },
40
+ { children: [], method: 'POST', urlMask: '/api/user' },
41
+ { children: [], method: 'POST', urlMask: '/api/user/avatar/foo' },
42
+ { children: [], method: 'PATCH', urlMask: '/api/user' }
43
+ ]
44
+
45
+
46
+ const expected = [
47
+ {
48
+ urlMask: '/api/user',
49
+ method: 'GET',
50
+ children: [
51
+ {
52
+ urlMask: '/api/user/avatar',
53
+ method: 'GET',
54
+ children: [
55
+ {
56
+ urlMask: '/api/user/avatar/foo',
57
+ method: 'POST',
58
+ children: []
59
+ }
60
+ ]
61
+ }, {
62
+ urlMask: '/api/user',
63
+ method: 'POST',
64
+ children: []
65
+ }, {
66
+ urlMask: '/api/user',
67
+ method: 'PATCH',
68
+ children: []
69
+ }
70
+ ]
71
+ },
72
+ {
73
+ urlMask: '/api/video/[id]',
74
+ method: 'GET',
75
+ children: []
76
+ },
77
+ {
78
+ urlMask: '/index.html',
79
+ method: 'GET',
80
+ children: []
81
+ },
82
+ {
83
+ urlMask: '/media/file-a.txt',
84
+ method: 'GET',
85
+ children: [
86
+ {
87
+ urlMask: '/media/file-b.txt',
88
+ method: 'GET',
89
+ children: []
90
+ }, {
91
+ urlMask: '/media/sub/file-aa.txt',
92
+ method: 'GET',
93
+ children: [
94
+ {
95
+ urlMask: '/media/sub/file-bb.txt',
96
+ method: 'GET',
97
+ children: []
98
+ }
99
+ ]
100
+ }
101
+ ]
102
+ }
103
+ ]
104
+
105
+ deepEqual(groupByFolder(input), expected)
106
+ })
@@ -0,0 +1,74 @@
1
+ import { test } from 'node:test'
2
+ import { deepEqual, equal } from 'node:assert/strict'
3
+ import { classNames, extractClassNames } from './dom-utils.js'
4
+
5
+
6
+ test('classNames', () => equal(classNames('a', false && 'b'), 'a'))
7
+
8
+
9
+ test('extractClassNames', () => {
10
+ const cssRules = [
11
+ { cssText: '.TopLevelPascal { color: red; }' },
12
+ { cssText: '.topLevelCamel { color: blue; }' },
13
+ { cssText: '.top_level_snake { color: green; }' },
14
+ { cssText: '.top-level-kebab { color: yellow; }' },
15
+ { cssText: '.Level2Parent {\n & .level2ChildCamel { color: purple; }\n}' },
16
+ { cssText: '.level2Base {\n &.level2ModifierCamel { font-weight: bold; }\n}' },
17
+ { cssText: '.Level3Parent {\n & .level3ChildCamel {\n & .level3_grand_child_snake { color: orange; }\n}\n}' },
18
+ { cssText: '.pseudoParent {\n &:hover { background: red; }\n & .pseudoNestedChild { color: pink; }\n}' },
19
+ { cssText: '.multiClass1, .multi_class_2 { padding: 10px; }' },
20
+ { cssText: '.combParent {\n & > .combChildDirect { margin: 5px; }\n}' },
21
+ { cssText: '.siblingBase {\n & + .siblingAdjacent { border: 1px solid; }\n}' },
22
+ { cssText: '@media (max-width: 768px) {\n .mediaQueryClass {\n & .mqNestedChild { display: none; }\n}\n}' },
23
+ { cssText: '.class_with_123_numbers { color: cyan; }' },
24
+ { cssText: '._privateStyleClass { opacity: 0.5; }' },
25
+ { cssText: '.stringTest { content: ".shouldNotBeExtracted"; background: url(".alsoIgnored"); }' },
26
+ { cssText: '.ComplexRoot {\n & .level2-kebab {\n &.level2ModCamel { color: red; }\n & .level3_snake {\n & .level4PascalChild { color: blue; }\n}\n}\n}' }
27
+ ]
28
+
29
+ const expected = {
30
+ TopLevelPascal: null,
31
+ topLevelCamel: null,
32
+ top_level_snake: null,
33
+ 'top-level-kebab': null,
34
+
35
+ Level2Parent: null,
36
+ level2ChildCamel: null,
37
+ level2Base: null,
38
+ level2ModifierCamel: null,
39
+
40
+ Level3Parent: null,
41
+ level3ChildCamel: null,
42
+ level3_grand_child_snake: null,
43
+
44
+ pseudoParent: null,
45
+ pseudoNestedChild: null,
46
+
47
+ multiClass1: null,
48
+ multi_class_2: null,
49
+
50
+ combParent: null,
51
+ combChildDirect: null,
52
+
53
+ siblingBase: null,
54
+ siblingAdjacent: null,
55
+
56
+ mediaQueryClass: null,
57
+ mqNestedChild: null,
58
+
59
+ class_with_123_numbers: null,
60
+ _privateStyleClass: null,
61
+
62
+ stringTest: null,
63
+
64
+ ComplexRoot: null,
65
+ 'level2-kebab': null,
66
+ level2ModCamel: null,
67
+ level3_snake: null,
68
+ level4PascalChild: null
69
+ }
70
+ for (const k of Object.keys(expected))
71
+ expected[k] = k
72
+
73
+ deepEqual(extractClassNames({ cssRules }), expected)
74
+ })
@@ -70,12 +70,9 @@ function selectorFor(elem) {
70
70
  }
71
71
 
72
72
 
73
- export function defineClassNames(sheet) {
74
- Object.assign(sheet, extractClassNames(sheet))
75
- }
76
-
77
73
  export function extractClassNames({ cssRules }) {
78
74
  // Class names must begin with _ or a letter, then it can have numbers and hyphens
75
+ // TODO think about tag.className selectors
79
76
  const reClassName = /(?:^|[\s,{>])&?\s*\.([a-zA-Z_][\w-]*)/g
80
77
  const cNames = {}
81
78
  let match
@@ -85,3 +82,73 @@ export function extractClassNames({ cssRules }) {
85
82
  return cNames
86
83
  }
87
84
 
85
+
86
+ export class QueryParamBool {
87
+ constructor(param) {
88
+ this.param = param
89
+ this.value = this.#init()
90
+ }
91
+
92
+ #init() {
93
+ const qs = new URLSearchParams(globalThis.location?.search)
94
+ if (qs.has(this.param))
95
+ return qs.get(this.param) !== '0'
96
+ const stored = globalThis.localStorage?.getItem(this.param) !== '0'
97
+ if (!stored)
98
+ this.#applyToUrl(false)
99
+ return stored
100
+ }
101
+
102
+ toggle() {
103
+ this.value = !this.value
104
+ if (this.value)
105
+ globalThis.localStorage?.removeItem(this.param)
106
+ else
107
+ globalThis.localStorage?.setItem(this.param, '0')
108
+ this.#applyToUrl(this.value)
109
+ }
110
+
111
+ #applyToUrl(nextVal) {
112
+ const url = new URL(globalThis.location?.href)
113
+ if (nextVal)
114
+ url.searchParams.delete(this.param)
115
+ else
116
+ url.searchParams.set(this.param, '0')
117
+ history.replaceState(null, '', url)
118
+ }
119
+ }
120
+
121
+
122
+ export class LocalStorageSet {
123
+ constructor(key) {
124
+ this.key = key
125
+ this.value = this.#parse()
126
+ }
127
+
128
+ add(item) {
129
+ this.value.add(item)
130
+ this.#persist()
131
+ }
132
+
133
+ delete(item) {
134
+ this.value.delete(item)
135
+ this.#persist()
136
+ }
137
+
138
+ has(item) {
139
+ return this.value.has(item)
140
+ }
141
+
142
+ #parse() {
143
+ try {
144
+ return new Set(JSON.parse(globalThis.localStorage?.getItem(this.key) || '[]'))
145
+ }
146
+ catch {
147
+ return new Set()
148
+ }
149
+ }
150
+
151
+ #persist() {
152
+ globalThis.localStorage?.setItem(this.key, JSON.stringify([...this.value]))
153
+ }
154
+ }
@@ -4,7 +4,7 @@ import { createSvgElement as s } from './dom-utils.js'
4
4
  export const Logo = () =>
5
5
  s('svg', { viewBox: '0 0 556 100' },
6
6
  s('path', { d: 'm13.75 1.8789c-5.9487 0.19352-10.865 4.5652-11.082 11.686v81.445c-1e-7 2.216 1.784 4 4 4h4.793c2.216 0 4-1.784 4-4v-64.982c0.02794-3.4488 3.0988-3.5551 4.2031-1.1562l16.615 59.059c1.4393 5.3711 5.1083 7.9633 8.7656 7.9473 3.6573 0.01603 7.3263-2.5762 8.7656-7.9473l16.615-59.059c1.1043-2.3989 4.1752-2.2925 4.2031 1.1562v64.982c0 2.216 1.784 4 4 4h4.793c2.216 0 4-1.784 4-4v-81.445c-0.17732-7.0807-5.1334-11.492-11.082-11.686-5.9487-0.19352-12.652 3.8309-15.609 13.619l-15.686 57.334-15.686-57.334c-2.9569-9.7882-9.6607-13.813-15.609-13.619zm239.19 0.074219c-2.216 0-4 1.784-4 4v89.057c0 2.216 1.784 4 4 4h4.793c2.216 0 3.9868-1.784 4-4l0.10644-17.94c0.0734-0.07237 12.175-13.75 12.175-13.75 5.6772 11.091 11.404 22.158 17.113 33.232 1.0168 1.9689 3.4217 2.7356 5.3906 1.7188l4.2578-2.1992c1.9689-1.0168 2.7356-3.4217 1.7188-5.3906-6.4691-12.585-12.958-25.16-19.442-37.738l17.223-19.771c1.4555-1.671 1.2803-4.189-0.39062-5.6445l-3.6133-3.1465c-0.73105-0.63679-1.6224-0.96212-2.5176-0.98633-1.151-0.03113-2.3063 0.43508-3.125 1.375l-28.896 33.174v-51.99c0-2.216-1.784-4-4-4zm-58.255 23.316c-10.699 0-19.312 8.6137-19.312 19.312v34.535c0 10.699 8.6137 19.312 19.312 19.312h19.717c10.699 0 19.311-8.6137 19.311-19.312l-0.125-7.8457c0-2.216-1.784-4-4-4h-4.6524c-2.216 0-4 1.784-4 4l3e-3 6.7888c3e-3 3.8063-1.5601 9.3694-8.4716 9.3694h-15.846c-6.9115 0-8.4766-5.5631-8.4766-12.475v-26.209c0-6.9115 1.5651-12.477 8.4766-12.477h15.846c6.6937 0 8.3697 5.2207 8.4687 11.828v2.2207c0 2.216 1.784 4 4 4h4.6524c2.216 0 4-1.784 4-4l0.125-5.7363c0-10.699-8.6117-19.312-19.311-19.312zm-72.182 0c-10.699 0-19.312 8.6137-19.312 19.312v34.535c0 10.699 8.6137 19.312 19.312 19.312h19.717c10.699 0 19.311-8.6137 19.311-19.312v-34.535c0-10.699-8.6117-19.312-19.311-19.312zm1.9356 11h15.846c6.9115 0 8.4746 5.5651 8.4746 12.477v26.209c0 6.9115-1.5631 12.475-8.4746 12.475h-15.846c-6.9115 0-8.4766-5.5631-8.4766-12.475v-26.209c0-6.9115 1.5651-12.477 8.4766-12.477z' }),
7
- s('path', { opacity: 1, fill: 'currentColor', d: 'm331.9 25.27c-10.699 0-19.312 8.6137-19.312 19.312v4.3682c0 2.216 1.784 4 4 4h4.7715c2.216 0 4-1.784 4-4v-0.20414c0-6.9115 1.5651-12.477 8.4766-12.477h15.846c6.9115 0 8.4746 5.5651 8.4746 12.477v7.0148h-28.059c-10.699 0-19.312 8.6117-19.312 19.311v4.0477c0 10.699 8.6137 19.313 19.312 19.312h17.812c2.216-1e-6 4-1.784 4-4v-4.7715c0-2.216-1.784-4-4-4h-13.648c-6.9115-2e-5 -12.477-1.5651-12.477-8.5649 0-6.9998 5.5651-8.5629 12.477-8.5629h23.895v25.897c0 2.216 1.784 4 4 4h4.7715c2.216-1e-6 4-1.784 4-4v-49.848c0-10.699-8.6117-19.312-19.311-19.312z' }),
7
+ s('path', { opacity: 0.85, fill: 'currentColor', d: 'm331.9 25.27c-10.699 0-19.312 8.6137-19.312 19.312v4.3682c0 2.216 1.784 4 4 4h4.7715c2.216 0 4-1.784 4-4v-0.20414c0-6.9115 1.5651-12.477 8.4766-12.477h15.846c6.9115 0 8.4746 5.5651 8.4746 12.477v7.0148h-28.059c-10.699 0-19.312 8.6117-19.312 19.311v4.0477c0 10.699 8.6137 19.313 19.312 19.312h17.812c2.216-1e-6 4-1.784 4-4v-4.7715c0-2.216-1.784-4-4-4h-13.648c-6.9115-2e-5 -12.477-1.5651-12.477-8.5649 0-6.9998 5.5651-8.5629 12.477-8.5629h23.895v25.897c0 2.216 1.784 4 4 4h4.7715c2.216-1e-6 4-1.784 4-4v-49.848c0-10.699-8.6117-19.312-19.311-19.312z' }),
8
8
  s('path', { d: 'm392.75 1.373c-2.216 0-4 1.784-4 4v18.043h-5.3086c-2.216 0-4 1.784-4 4v4.793c0 2.216 1.784 4 4 4h5.3086v51.398c0 6.1465 3.7064 10.823 9.232 10.823h16.531c2.216 0 4-1.784 4-4v-4.793c0-2.216-1.784-4-4-4h-12.97v-49.428h9.8711c2.216 0 4-1.784 4-4v-4.793c0-2.216-1.784-4-4-4h-9.8711v-18.043c0-2.216-1.784-4-4-4zm122.96 23.896c-10.699 0-19.312 8.6137-19.312 19.312v49.812c0 2.216 1.784 4 4 4h4.7715c2.216 0 4-1.784 4-4v-45.648c0-6.9115 1.5651-12.477 8.4766-12.477h15.846c6.9115 0 8.4746 5.5651 8.4746 12.477v45.684c0 2.216 1.784 4 4 4h4.7715c2.216-1e-6 4-1.784 4-4v-49.848c0-10.699-8.6117-19.312-19.311-19.312zm-69.999 0c-10.699 0-19.312 8.6137-19.312 19.312v34.535c0 10.699 8.6137 19.312 19.312 19.312h19.717c10.699 0 19.311-8.6137 19.311-19.312v-34.535c0-10.699-8.6117-19.312-19.311-19.312zm1.9356 11h15.846c6.9115 0 8.4746 5.5651 8.4746 12.477v26.209c0 6.9115-1.5631 12.475-8.4746 12.475h-15.846c-6.9115 0-8.4766-5.5631-8.4766-12.475v-26.209c0-6.9115 1.5651-12.477 8.4766-12.477z' }))
9
9
 
10
10
  export const TimerIcon = () =>
@@ -10,6 +10,7 @@ import { IncomingMessage, BodyReaderError, hasControlChars } from './utils/HttpI
10
10
 
11
11
  import { API } from '../client/ApiConstants.js'
12
12
 
13
+ import { cookie } from './cookie.js'
13
14
  import { config, setup } from './config.js'
14
15
  import { apiPatchReqs, apiGetReqs } from './Api.js'
15
16
 
@@ -24,6 +25,7 @@ import { watchMocksDir } from './Watcher.js'
24
25
  export function Mockaton(options) {
25
26
  return new Promise((resolve, reject) => {
26
27
  setup(options)
28
+ cookie.init(config.cookies)
27
29
  mockBrokerCollection.init()
28
30
 
29
31
  if (config.watcherEnabled) {
@@ -1,47 +0,0 @@
1
- function TrieNode() {
2
- this.brokers = []
3
- this.tnChildren = new Map()
4
- }
5
-
6
- /**
7
- * @param {Partial<BrokerRowModel>[]} brokers
8
- * @returns {Partial<BrokerRowModel>[]}
9
- */
10
- export function dirStructure(brokers) {
11
- return dfs(trie(brokers))
12
- }
13
-
14
- function trie(brokers) {
15
- const root = new TrieNode()
16
- for (const b of brokers) {
17
- let node = root
18
- for (const seg of b.urlMask.split('/')) { // TODO it should ignore query string
19
- const segNode = node.tnChildren.get(seg) || new TrieNode()
20
- node.tnChildren.set(seg, segNode)
21
- node = segNode
22
- }
23
- node.brokers.push(b)
24
- }
25
- return root
26
- }
27
-
28
- /** @param {TrieNode} node */
29
- function dfs(node) {
30
- const childBrokers = []
31
- for (const tnc of node.tnChildren.values())
32
- childBrokers.push(...dfs(tnc))
33
-
34
- const brokers = node.brokers.length
35
- ? [node.brokers[0], ...childBrokers, ...node.brokers.slice(1)]
36
- : childBrokers
37
-
38
- if (!brokers.length)
39
- return []
40
-
41
- const [head, ...rest] = brokers
42
- if (node.brokers.length || !head.children.length) {
43
- head.children.push(...rest)
44
- return [head]
45
- }
46
- return brokers
47
- }
@@ -1,81 +0,0 @@
1
- import { test } from 'node:test'
2
- import { deepEqual } from 'node:assert/strict'
3
- import { dirStructure } from './dirStructure.js'
4
-
5
-
6
- const input = [
7
- { children: [], method: 'GET', urlMask: '/api/user' },
8
- { children: [], method: 'GET', urlMask: '/api/user/avatar' },
9
- { children: [], method: 'GET', urlMask: '/api/video/[id]' },
10
- { children: [], method: 'GET', urlMask: '/index.html' },
11
- { children: [], method: 'GET', urlMask: '/media/file-a.txt' },
12
- { children: [], method: 'GET', urlMask: '/media/file-b.txt' },
13
- { children: [], method: 'GET', urlMask: '/media/sub/file-aa.txt' },
14
- { children: [], method: 'GET', urlMask: '/media/sub/file-bb.txt' },
15
- { children: [], method: 'POST', urlMask: '/api/user' },
16
- { children: [], method: 'POST', urlMask: '/api/user/avatar/foo' },
17
- { children: [], method: 'PATCH', urlMask: '/api/user' }
18
- ]
19
-
20
-
21
- const expected = [
22
- {
23
- urlMask: '/api/user',
24
- method: 'GET',
25
- children: [
26
- {
27
- urlMask: '/api/user/avatar',
28
- method: 'GET',
29
- children: [
30
- {
31
- urlMask: '/api/user/avatar/foo',
32
- method: 'POST',
33
- children: []
34
- }
35
- ]
36
- }, {
37
- urlMask: '/api/user',
38
- method: 'POST',
39
- children: []
40
- }, {
41
- urlMask: '/api/user',
42
- method: 'PATCH',
43
- children: []
44
- }
45
- ]
46
- },
47
- {
48
- urlMask: '/api/video/[id]',
49
- method: 'GET',
50
- children: []
51
- },
52
- {
53
- urlMask: '/index.html',
54
- method: 'GET',
55
- children: []
56
- },
57
- {
58
- urlMask: '/media/file-a.txt',
59
- method: 'GET',
60
- children: [
61
- {
62
- urlMask: '/media/file-b.txt',
63
- method: 'GET',
64
- children: []
65
- }, {
66
- urlMask: '/media/sub/file-aa.txt',
67
- method: 'GET',
68
- children: [
69
- {
70
- urlMask: '/media/sub/file-bb.txt',
71
- method: 'GET',
72
- children: []
73
- }
74
- ]
75
- }
76
- ]
77
- }
78
- ]
79
-
80
- test('acceptance', () => deepEqual(dirStructure(input), expected))
81
-
@@ -1,76 +0,0 @@
1
- import { test } from 'node:test'
2
- import { deepEqual, equal } from 'node:assert/strict'
3
- import { extractClassNames, classNames } from './dom-utils.js'
4
-
5
-
6
- test('classNames', () => {
7
- equal(classNames('a', false && 'b'), 'a')
8
- })
9
-
10
-
11
- const cssRules = [
12
- { cssText: '.TopLevelPascal { color: red; }' },
13
- { cssText: '.topLevelCamel { color: blue; }' },
14
- { cssText: '.top_level_snake { color: green; }' },
15
- { cssText: '.top-level-kebab { color: yellow; }' },
16
- { cssText: '.Level2Parent {\n & .level2ChildCamel { color: purple; }\n}' },
17
- { cssText: '.level2Base {\n &.level2ModifierCamel { font-weight: bold; }\n}' },
18
- { cssText: '.Level3Parent {\n & .level3ChildCamel {\n & .level3_grand_child_snake { color: orange; }\n}\n}' },
19
- { cssText: '.pseudoParent {\n &:hover { background: red; }\n & .pseudoNestedChild { color: pink; }\n}' },
20
- { cssText: '.multiClass1, .multi_class_2 { padding: 10px; }' },
21
- { cssText: '.combParent {\n & > .combChildDirect { margin: 5px; }\n}' },
22
- { cssText: '.siblingBase {\n & + .siblingAdjacent { border: 1px solid; }\n}' },
23
- { cssText: '@media (max-width: 768px) {\n .mediaQueryClass {\n & .mqNestedChild { display: none; }\n}\n}' },
24
- { cssText: '.class_with_123_numbers { color: cyan; }' },
25
- { cssText: '._privateStyleClass { opacity: 0.5; }' },
26
- { cssText: '.stringTest { content: ".shouldNotBeExtracted"; background: url(".alsoIgnored"); }' },
27
- { cssText: '.ComplexRoot {\n & .level2-kebab {\n &.level2ModCamel { color: red; }\n & .level3_snake {\n & .level4PascalChild { color: blue; }\n}\n}\n}' }
28
- ]
29
-
30
- const expected = {
31
- TopLevelPascal: null,
32
- topLevelCamel: null,
33
- top_level_snake: null,
34
- 'top-level-kebab': null,
35
-
36
- Level2Parent: null,
37
- level2ChildCamel: null,
38
- level2Base: null,
39
- level2ModifierCamel: null,
40
-
41
- Level3Parent: null,
42
- level3ChildCamel: null,
43
- level3_grand_child_snake: null,
44
-
45
- pseudoParent: null,
46
- pseudoNestedChild: null,
47
-
48
- multiClass1: null,
49
- multi_class_2: null,
50
-
51
- combParent: null,
52
- combChildDirect: null,
53
-
54
- siblingBase: null,
55
- siblingAdjacent: null,
56
-
57
- mediaQueryClass: null,
58
- mqNestedChild: null,
59
-
60
- class_with_123_numbers: null,
61
- _privateStyleClass: null,
62
-
63
- stringTest: null,
64
-
65
- ComplexRoot: null,
66
- 'level2-kebab': null,
67
- level2ModCamel: null,
68
- level3_snake: null,
69
- level4PascalChild: null
70
- }
71
- for (const k of Object.keys(expected))
72
- expected[k] = k
73
-
74
-
75
- test('extracts', () => deepEqual(extractClassNames({ cssRules }), expected))
76
-