@harborclient/sdk 1.0.0 → 1.0.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/dist/build/index.d.ts.map +1 -1
- package/dist/build/index.js +10 -1
- package/dist/client.d.ts +2 -2
- package/dist/components/portalToBody.d.ts +2 -1
- package/dist/components/portalToBody.d.ts.map +1 -1
- package/dist/components/portalToBody.js +5 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/main.d.ts +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/jsx-dev-runtime.d.ts +1 -1
- package/dist/runtime/store.d.ts +6 -1
- package/dist/runtime/store.d.ts.map +1 -1
- package/dist/runtime/store.js +8 -2
- package/dist/runtime/store.ts +11 -3
- package/package.json +3 -2
- package/dist/components/enhanceControl.test.d.ts +0 -2
- package/dist/components/enhanceControl.test.d.ts.map +0 -1
- package/dist/components/enhanceControl.test.js +0 -83
- package/dist/components/utils.test.d.ts +0 -2
- package/dist/components/utils.test.d.ts.map +0 -1
- package/dist/components/utils.test.js +0 -44
- package/dist/http/resolveRequest.test.d.ts +0 -2
- package/dist/http/resolveRequest.test.d.ts.map +0 -1
- package/dist/http/resolveRequest.test.js +0 -41
- package/dist/http/substitute.test.d.ts +0 -2
- package/dist/http/substitute.test.d.ts.map +0 -1
- package/dist/http/substitute.test.js +0 -93
- package/dist/pluginDatabaseApi.test.d.ts +0 -2
- package/dist/pluginDatabaseApi.test.d.ts.map +0 -1
- package/dist/pluginDatabaseApi.test.js +0 -65
- package/dist/runtime/index.test.d.ts +0 -2
- package/dist/runtime/index.test.d.ts.map +0 -1
- package/dist/runtime/index.test.js +0 -41
- package/dist/runtime/index.test.ts +0 -53
- package/dist/runtime-utils.test.d.ts +0 -2
- package/dist/runtime-utils.test.d.ts.map +0 -1
- package/dist/runtime-utils.test.js +0 -177
- package/dist/signing/signing.test.d.ts +0 -2
- package/dist/signing/signing.test.d.ts.map +0 -1
- package/dist/signing/signing.test.js +0 -100
- package/dist/storage/cappedList.test.d.ts +0 -2
- package/dist/storage/cappedList.test.d.ts.map +0 -1
- package/dist/storage/cappedList.test.js +0 -11
- package/dist/storage/validate.test.d.ts +0 -2
- package/dist/storage/validate.test.d.ts.map +0 -1
- package/dist/storage/validate.test.js +0 -78
- package/dist/store.test.d.ts +0 -2
- package/dist/store.test.d.ts.map +0 -1
- package/dist/store.test.js +0 -248
- package/dist/utilities.test.d.ts +0 -2
- package/dist/utilities.test.d.ts.map +0 -1
- package/dist/utilities.test.js +0 -16
- package/dist/variables/dynamic.test.d.ts +0 -2
- package/dist/variables/dynamic.test.d.ts.map +0 -1
- package/dist/variables/dynamic.test.js +0 -60
- package/dist/variables/tokens.test.d.ts +0 -2
- package/dist/variables/tokens.test.d.ts.map +0 -1
- package/dist/variables/tokens.test.js +0 -160
package/dist/store.test.js
DELETED
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it, jest } from '@jest/globals';
|
|
2
|
-
import { createExternalStore, createStorageStore, setIntervalDisposable, syncOnWindowFocus } from './runtime/store.js';
|
|
3
|
-
/**
|
|
4
|
-
* Builds an in-memory storage mock for store tests.
|
|
5
|
-
*
|
|
6
|
-
* @param initial - Seed values keyed by storage key.
|
|
7
|
-
*/
|
|
8
|
-
function createMockStorage(initial = {}) {
|
|
9
|
-
const data = { ...initial };
|
|
10
|
-
const getMock = jest.fn(async (key) => data[key]);
|
|
11
|
-
const setMock = jest.fn(async (key, value) => {
|
|
12
|
-
data[key] = value;
|
|
13
|
-
});
|
|
14
|
-
return {
|
|
15
|
-
data,
|
|
16
|
-
get: getMock,
|
|
17
|
-
set: setMock,
|
|
18
|
-
getMock,
|
|
19
|
-
setMock
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
describe('createExternalStore', () => {
|
|
23
|
-
it('notifies subscribers when setState is called', () => {
|
|
24
|
-
const store = createExternalStore(0);
|
|
25
|
-
const listener = jest.fn();
|
|
26
|
-
store.subscribe(listener);
|
|
27
|
-
store.setState(1);
|
|
28
|
-
expect(listener).toHaveBeenCalledTimes(1);
|
|
29
|
-
expect(store.getSnapshot()).toBe(1);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
describe('createStorageStore', () => {
|
|
33
|
-
it('starts from parse(undefined)', () => {
|
|
34
|
-
const storage = createMockStorage();
|
|
35
|
-
const store = createStorageStore({
|
|
36
|
-
storage,
|
|
37
|
-
key: 'items',
|
|
38
|
-
parse: (raw) => (Array.isArray(raw) ? raw : [])
|
|
39
|
-
});
|
|
40
|
-
expect(store.getSnapshot()).toEqual([]);
|
|
41
|
-
});
|
|
42
|
-
it('persists and updates the snapshot via set', async () => {
|
|
43
|
-
const storage = createMockStorage();
|
|
44
|
-
const store = createStorageStore({
|
|
45
|
-
storage,
|
|
46
|
-
key: 'items',
|
|
47
|
-
parse: (raw) => (Array.isArray(raw) ? raw : [])
|
|
48
|
-
});
|
|
49
|
-
const listener = jest.fn();
|
|
50
|
-
store.subscribe(listener);
|
|
51
|
-
await store.set(['a']);
|
|
52
|
-
expect(store.getSnapshot()).toEqual(['a']);
|
|
53
|
-
expect(storage.setMock).toHaveBeenCalledWith('items', ['a']);
|
|
54
|
-
expect(listener).toHaveBeenCalledTimes(1);
|
|
55
|
-
});
|
|
56
|
-
it('skips set when equals reports no change', async () => {
|
|
57
|
-
const storage = createMockStorage({ items: ['a'] });
|
|
58
|
-
const store = createStorageStore({
|
|
59
|
-
storage,
|
|
60
|
-
key: 'items',
|
|
61
|
-
parse: (raw) => (Array.isArray(raw) ? raw : [])
|
|
62
|
-
});
|
|
63
|
-
await store.reloadFromStorage();
|
|
64
|
-
const listener = jest.fn();
|
|
65
|
-
store.subscribe(listener);
|
|
66
|
-
await store.set(['a']);
|
|
67
|
-
expect(listener).not.toHaveBeenCalled();
|
|
68
|
-
expect(storage.setMock).not.toHaveBeenCalled();
|
|
69
|
-
});
|
|
70
|
-
it('reloadFromStorage applies parsed storage values', async () => {
|
|
71
|
-
const storage = createMockStorage({ count: 3 });
|
|
72
|
-
const store = createStorageStore({
|
|
73
|
-
storage,
|
|
74
|
-
key: 'count',
|
|
75
|
-
parse: (raw) => (typeof raw === 'number' ? raw : 0)
|
|
76
|
-
});
|
|
77
|
-
const listener = jest.fn();
|
|
78
|
-
store.subscribe(listener);
|
|
79
|
-
await store.reloadFromStorage();
|
|
80
|
-
expect(store.getSnapshot()).toBe(3);
|
|
81
|
-
expect(listener).toHaveBeenCalledTimes(1);
|
|
82
|
-
});
|
|
83
|
-
it('reloadFromStorage skips notify when parsed value is unchanged', async () => {
|
|
84
|
-
const storage = createMockStorage({ count: 2 });
|
|
85
|
-
const store = createStorageStore({
|
|
86
|
-
storage,
|
|
87
|
-
key: 'count',
|
|
88
|
-
parse: (raw) => (typeof raw === 'number' ? raw : 0)
|
|
89
|
-
});
|
|
90
|
-
await store.reloadFromStorage();
|
|
91
|
-
const listener = jest.fn();
|
|
92
|
-
store.subscribe(listener);
|
|
93
|
-
await store.reloadFromStorage();
|
|
94
|
-
expect(listener).not.toHaveBeenCalled();
|
|
95
|
-
});
|
|
96
|
-
it('uses a custom equals function', async () => {
|
|
97
|
-
const storage = createMockStorage();
|
|
98
|
-
const store = createStorageStore({
|
|
99
|
-
storage,
|
|
100
|
-
key: 'status',
|
|
101
|
-
parse: (raw) => raw && typeof raw === 'object' && 'running' in raw
|
|
102
|
-
? raw
|
|
103
|
-
: { running: false },
|
|
104
|
-
equals: (a, b) => a.running === b.running
|
|
105
|
-
});
|
|
106
|
-
await store.set({ running: true });
|
|
107
|
-
const listener = jest.fn();
|
|
108
|
-
store.subscribe(listener);
|
|
109
|
-
storage.data.status = { running: true, port: 8080 };
|
|
110
|
-
await store.reloadFromStorage();
|
|
111
|
-
expect(listener).not.toHaveBeenCalled();
|
|
112
|
-
expect(store.getSnapshot()).toEqual({ running: true });
|
|
113
|
-
});
|
|
114
|
-
it('keeps the current snapshot when storage is missing and keepCurrentWhenMissing is true', async () => {
|
|
115
|
-
const storage = createMockStorage();
|
|
116
|
-
const store = createStorageStore({
|
|
117
|
-
storage,
|
|
118
|
-
key: 'status',
|
|
119
|
-
parse: () => ({ running: false }),
|
|
120
|
-
keepCurrentWhenMissing: true
|
|
121
|
-
});
|
|
122
|
-
await store.set({ running: true });
|
|
123
|
-
delete storage.data.status;
|
|
124
|
-
const listener = jest.fn();
|
|
125
|
-
store.subscribe(listener);
|
|
126
|
-
await store.reloadFromStorage();
|
|
127
|
-
expect(listener).not.toHaveBeenCalled();
|
|
128
|
-
expect(store.getSnapshot()).toEqual({ running: true });
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
describe('setIntervalDisposable', () => {
|
|
132
|
-
beforeEach(() => {
|
|
133
|
-
jest.useFakeTimers();
|
|
134
|
-
});
|
|
135
|
-
afterEach(() => {
|
|
136
|
-
jest.useRealTimers();
|
|
137
|
-
});
|
|
138
|
-
it('invokes the callback on an interval until disposed', () => {
|
|
139
|
-
const callback = jest.fn();
|
|
140
|
-
const disposable = setIntervalDisposable(callback, 100);
|
|
141
|
-
jest.advanceTimersByTime(250);
|
|
142
|
-
expect(callback).toHaveBeenCalledTimes(2);
|
|
143
|
-
disposable.dispose();
|
|
144
|
-
jest.advanceTimersByTime(200);
|
|
145
|
-
expect(callback).toHaveBeenCalledTimes(2);
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
describe('syncOnWindowFocus', () => {
|
|
149
|
-
const focusListeners = new Set();
|
|
150
|
-
const visibilityListeners = new Set();
|
|
151
|
-
const originalWindow = globalThis.window;
|
|
152
|
-
const originalDocument = globalThis.document;
|
|
153
|
-
beforeEach(() => {
|
|
154
|
-
jest.useFakeTimers();
|
|
155
|
-
focusListeners.clear();
|
|
156
|
-
visibilityListeners.clear();
|
|
157
|
-
globalThis.window = {
|
|
158
|
-
addEventListener: (type, listener) => {
|
|
159
|
-
if (type === 'focus' && typeof listener === 'function') {
|
|
160
|
-
focusListeners.add(listener);
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
removeEventListener: (type, listener) => {
|
|
164
|
-
if (type === 'focus' && typeof listener === 'function') {
|
|
165
|
-
focusListeners.delete(listener);
|
|
166
|
-
}
|
|
167
|
-
},
|
|
168
|
-
dispatchEvent: (event) => {
|
|
169
|
-
if (event.type === 'focus') {
|
|
170
|
-
for (const listener of focusListeners) {
|
|
171
|
-
listener(event);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
return true;
|
|
175
|
-
}
|
|
176
|
-
};
|
|
177
|
-
globalThis.document = {
|
|
178
|
-
addEventListener: (type, listener) => {
|
|
179
|
-
if (type === 'visibilitychange' && typeof listener === 'function') {
|
|
180
|
-
visibilityListeners.add(listener);
|
|
181
|
-
}
|
|
182
|
-
},
|
|
183
|
-
removeEventListener: (type, listener) => {
|
|
184
|
-
if (type === 'visibilitychange' && typeof listener === 'function') {
|
|
185
|
-
visibilityListeners.delete(listener);
|
|
186
|
-
}
|
|
187
|
-
},
|
|
188
|
-
dispatchEvent: (event) => {
|
|
189
|
-
if (event.type === 'visibilitychange') {
|
|
190
|
-
for (const listener of visibilityListeners) {
|
|
191
|
-
listener(event);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return true;
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
});
|
|
198
|
-
afterEach(() => {
|
|
199
|
-
jest.useRealTimers();
|
|
200
|
-
globalThis.window = originalWindow;
|
|
201
|
-
globalThis.document = originalDocument;
|
|
202
|
-
});
|
|
203
|
-
it('reloads stores on focus, visibility change, mount, and interval', async () => {
|
|
204
|
-
const storage = createMockStorage({ count: 1 });
|
|
205
|
-
const store = createStorageStore({
|
|
206
|
-
storage,
|
|
207
|
-
key: 'count',
|
|
208
|
-
parse: (raw) => (typeof raw === 'number' ? raw : 0)
|
|
209
|
-
});
|
|
210
|
-
const reloadSpy = jest.spyOn(store, 'reloadFromStorage');
|
|
211
|
-
const disposable = syncOnWindowFocus(store, { intervalMs: 500 });
|
|
212
|
-
expect(reloadSpy).toHaveBeenCalledTimes(1);
|
|
213
|
-
storage.data.count = 2;
|
|
214
|
-
window.dispatchEvent(new Event('focus'));
|
|
215
|
-
expect(reloadSpy).toHaveBeenCalledTimes(2);
|
|
216
|
-
storage.data.count = 3;
|
|
217
|
-
document.dispatchEvent(new Event('visibilitychange'));
|
|
218
|
-
expect(reloadSpy).toHaveBeenCalledTimes(3);
|
|
219
|
-
storage.data.count = 4;
|
|
220
|
-
jest.advanceTimersByTime(500);
|
|
221
|
-
expect(reloadSpy).toHaveBeenCalledTimes(4);
|
|
222
|
-
disposable.dispose();
|
|
223
|
-
storage.data.count = 5;
|
|
224
|
-
window.dispatchEvent(new Event('focus'));
|
|
225
|
-
jest.advanceTimersByTime(500);
|
|
226
|
-
expect(reloadSpy).toHaveBeenCalledTimes(4);
|
|
227
|
-
await store.reloadFromStorage();
|
|
228
|
-
expect(store.getSnapshot()).toBe(5);
|
|
229
|
-
});
|
|
230
|
-
it('reloads multiple stores together', async () => {
|
|
231
|
-
const storage = createMockStorage();
|
|
232
|
-
const first = createStorageStore({
|
|
233
|
-
storage,
|
|
234
|
-
key: 'first',
|
|
235
|
-
parse: (raw) => (typeof raw === 'number' ? raw : 0)
|
|
236
|
-
});
|
|
237
|
-
const second = createStorageStore({
|
|
238
|
-
storage,
|
|
239
|
-
key: 'second',
|
|
240
|
-
parse: (raw) => (typeof raw === 'number' ? raw : 0)
|
|
241
|
-
});
|
|
242
|
-
const reloadFirst = jest.spyOn(first, 'reloadFromStorage');
|
|
243
|
-
const reloadSecond = jest.spyOn(second, 'reloadFromStorage');
|
|
244
|
-
syncOnWindowFocus([first, second]).dispose();
|
|
245
|
-
expect(reloadFirst).toHaveBeenCalledTimes(1);
|
|
246
|
-
expect(reloadSecond).toHaveBeenCalledTimes(1);
|
|
247
|
-
});
|
|
248
|
-
});
|
package/dist/utilities.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utilities.test.d.ts","sourceRoot":"","sources":["../src/utilities.test.ts"],"names":[],"mappings":""}
|
package/dist/utilities.test.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from '@jest/globals';
|
|
2
|
-
import { methodColorClass, statusColorClass } from './ui/tokens.js';
|
|
3
|
-
import { formatRelativeTime, formatHeaders } from './ui/format.js';
|
|
4
|
-
describe('method and status color helpers', () => {
|
|
5
|
-
it('return host classes', () => {
|
|
6
|
-
expect(methodColorClass('GET')).toBe('text-method-get');
|
|
7
|
-
expect(statusColorClass(200)).toBe('bg-success');
|
|
8
|
-
expect(statusColorClass(404)).toBe('bg-danger');
|
|
9
|
-
});
|
|
10
|
-
});
|
|
11
|
-
describe('format helpers', () => {
|
|
12
|
-
it('produce readable output', () => {
|
|
13
|
-
expect(formatHeaders({ Accept: 'application/json' })).toBe('Accept: application/json');
|
|
14
|
-
expect(formatRelativeTime(Date.now() - 10_000)).toMatch(/ago|just now/);
|
|
15
|
-
});
|
|
16
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic.test.d.ts","sourceRoot":"","sources":["../../src/variables/dynamic.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from '@jest/globals';
|
|
2
|
-
import { DYNAMIC_VARIABLE_CATEGORIES, DYNAMIC_VARIABLE_NAMES, DYNAMIC_VARIABLES, getDynamicVariableDescription, isDynamicVariable, resolveDynamicVariable } from './dynamic.js';
|
|
3
|
-
describe('dynamicVariables', () => {
|
|
4
|
-
it('registers all Postman-style dynamic variable names with $ prefix', () => {
|
|
5
|
-
expect(DYNAMIC_VARIABLE_NAMES.length).toBeGreaterThan(100);
|
|
6
|
-
for (const name of DYNAMIC_VARIABLE_NAMES) {
|
|
7
|
-
expect(name.startsWith('$')).toBe(true);
|
|
8
|
-
}
|
|
9
|
-
});
|
|
10
|
-
it('generates a non-empty value for every registered dynamic variable', () => {
|
|
11
|
-
for (const name of DYNAMIC_VARIABLE_NAMES) {
|
|
12
|
-
const value = resolveDynamicVariable(name);
|
|
13
|
-
expect(value).toBeDefined();
|
|
14
|
-
expect(value.length).toBeGreaterThan(0);
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
it('isDynamicVariable and getDynamicVariableDescription reflect the registry', () => {
|
|
18
|
-
expect(isDynamicVariable('$guid')).toBe(true);
|
|
19
|
-
expect(isDynamicVariable('$randomFirstName')).toBe(true);
|
|
20
|
-
expect(isDynamicVariable('host')).toBe(false);
|
|
21
|
-
expect(isDynamicVariable('$unknownDynamic')).toBe(false);
|
|
22
|
-
expect(getDynamicVariableDescription('$guid')).toBe(DYNAMIC_VARIABLES.$guid.description);
|
|
23
|
-
expect(getDynamicVariableDescription('host')).toBeUndefined();
|
|
24
|
-
});
|
|
25
|
-
it('resolveDynamicVariable returns undefined for unknown keys', () => {
|
|
26
|
-
expect(resolveDynamicVariable('$notARealVariable')).toBeUndefined();
|
|
27
|
-
});
|
|
28
|
-
it('$guid and $randomUUID produce uuid-v4 shaped values', () => {
|
|
29
|
-
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
30
|
-
expect(resolveDynamicVariable('$guid')).toMatch(uuidPattern);
|
|
31
|
-
expect(resolveDynamicVariable('$randomUUID')).toMatch(uuidPattern);
|
|
32
|
-
});
|
|
33
|
-
it('$timestamp returns a numeric unix seconds string', () => {
|
|
34
|
-
const value = resolveDynamicVariable('$timestamp');
|
|
35
|
-
expect(value).toMatch(/^\d+$/);
|
|
36
|
-
const seconds = Number(value);
|
|
37
|
-
expect(seconds).toBeGreaterThan(1_500_000_000);
|
|
38
|
-
expect(seconds).toBeLessThanOrEqual(Math.floor(Date.now() / 1000));
|
|
39
|
-
});
|
|
40
|
-
it('$isoTimestamp returns an ISO-8601 UTC string', () => {
|
|
41
|
-
const value = resolveDynamicVariable('$isoTimestamp');
|
|
42
|
-
expect(value).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
|
|
43
|
-
expect(() => new Date(value).toISOString()).not.toThrow();
|
|
44
|
-
});
|
|
45
|
-
it('generates fresh values on each resolve call for random variables', () => {
|
|
46
|
-
const first = resolveDynamicVariable('$randomInt');
|
|
47
|
-
const second = resolveDynamicVariable('$randomInt');
|
|
48
|
-
expect(first).toMatch(/^\d+$/);
|
|
49
|
-
expect(second).toMatch(/^\d+$/);
|
|
50
|
-
});
|
|
51
|
-
it('lists every dynamic variable exactly once across documentation categories', () => {
|
|
52
|
-
const categorized = DYNAMIC_VARIABLE_CATEGORIES.flatMap((category) => category.keys);
|
|
53
|
-
const unique = new Set(categorized);
|
|
54
|
-
expect(categorized.length).toBe(DYNAMIC_VARIABLE_NAMES.length);
|
|
55
|
-
expect(unique.size).toBe(DYNAMIC_VARIABLE_NAMES.length);
|
|
56
|
-
for (const name of DYNAMIC_VARIABLE_NAMES) {
|
|
57
|
-
expect(unique.has(name)).toBe(true);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.test.d.ts","sourceRoot":"","sources":["../../src/variables/tokens.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from '@jest/globals';
|
|
2
|
-
import { getVariableTokenAtOffset, getVariableTooltipContent, resolveVariable, substituteVariables, tokenizeVariables } from './tokens.js';
|
|
3
|
-
/**
|
|
4
|
-
* Builds a test variable row.
|
|
5
|
-
*
|
|
6
|
-
* @param key - Variable name.
|
|
7
|
-
* @param value - Current value.
|
|
8
|
-
* @param defaultValue - Fallback when value is empty.
|
|
9
|
-
* @param share - Whether the variable is shared on export.
|
|
10
|
-
*/
|
|
11
|
-
const variable = (key, value, defaultValue = '', share = false) => ({
|
|
12
|
-
key,
|
|
13
|
-
value,
|
|
14
|
-
defaultValue,
|
|
15
|
-
share
|
|
16
|
-
});
|
|
17
|
-
describe('substituteVariables', () => {
|
|
18
|
-
it('replaces known variable tokens', () => {
|
|
19
|
-
const result = substituteVariables('https://{{host}}/api/{{version}}', [
|
|
20
|
-
variable('host', 'api.example.com'),
|
|
21
|
-
variable('version', 'v1')
|
|
22
|
-
]);
|
|
23
|
-
expect(result).toBe('https://api.example.com/api/v1');
|
|
24
|
-
});
|
|
25
|
-
it('leaves unknown tokens unchanged', () => {
|
|
26
|
-
const result = substituteVariables('https://{{host}}/{{missing}}', [
|
|
27
|
-
variable('host', 'api.example.com')
|
|
28
|
-
]);
|
|
29
|
-
expect(result).toBe('https://api.example.com/{{missing}}');
|
|
30
|
-
});
|
|
31
|
-
it('substitutes {{ host }} when token key has surrounding whitespace', () => {
|
|
32
|
-
const result = substituteVariables('https://{{ host }}/users', [
|
|
33
|
-
variable('host', 'api.example.com')
|
|
34
|
-
]);
|
|
35
|
-
expect(result).toBe('https://api.example.com/users');
|
|
36
|
-
});
|
|
37
|
-
it('trims variable keys when building the lookup', () => {
|
|
38
|
-
const result = substituteVariables('https://{{host}}', [
|
|
39
|
-
variable(' host ', 'api.example.com')
|
|
40
|
-
]);
|
|
41
|
-
expect(result).toBe('https://api.example.com');
|
|
42
|
-
});
|
|
43
|
-
it('ignores blank-key variables', () => {
|
|
44
|
-
const result = substituteVariables('https://{{host}}', [
|
|
45
|
-
variable(' ', 'ignored.example.com'),
|
|
46
|
-
variable('host', 'api.example.com')
|
|
47
|
-
]);
|
|
48
|
-
expect(result).toBe('https://api.example.com');
|
|
49
|
-
});
|
|
50
|
-
it('falls back to defaultValue when value is empty', () => {
|
|
51
|
-
const result = substituteVariables('https://{{host}}/api', [variable('host', '', 'localhost')]);
|
|
52
|
-
expect(result).toBe('https://localhost/api');
|
|
53
|
-
});
|
|
54
|
-
it('prefers value over defaultValue when both are set', () => {
|
|
55
|
-
const result = substituteVariables('https://{{host}}/api', [
|
|
56
|
-
variable('host', 'api.example.com', 'localhost')
|
|
57
|
-
]);
|
|
58
|
-
expect(result).toBe('https://api.example.com/api');
|
|
59
|
-
});
|
|
60
|
-
it('substitutes variables in collection header values', () => {
|
|
61
|
-
const result = substituteVariables('Bearer {{token}}', [variable('token', 'abc123')]);
|
|
62
|
-
expect(result).toBe('Bearer abc123');
|
|
63
|
-
});
|
|
64
|
-
it('resolves dynamic variables when no static variable is defined', () => {
|
|
65
|
-
const result = substituteVariables('id={{ $guid }}', []);
|
|
66
|
-
expect(result).not.toContain('{{');
|
|
67
|
-
expect(result).toMatch(/^id=[0-9a-f-]{36}$/i);
|
|
68
|
-
});
|
|
69
|
-
it('prefers static variables over dynamic variables with the same key', () => {
|
|
70
|
-
const result = substituteVariables('{{$randomInt}}', [variable('$randomInt', '42')]);
|
|
71
|
-
expect(result).toBe('42');
|
|
72
|
-
});
|
|
73
|
-
it('leaves unknown dynamic-style tokens unchanged', () => {
|
|
74
|
-
const result = substituteVariables('{{$notRegistered}}', []);
|
|
75
|
-
expect(result).toBe('{{$notRegistered}}');
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
describe('tokenizeVariables', () => {
|
|
79
|
-
it('returns a single plain-text token when no variables are present', () => {
|
|
80
|
-
expect(tokenizeVariables('https://api.example.com')).toEqual([
|
|
81
|
-
{ text: 'https://api.example.com' }
|
|
82
|
-
]);
|
|
83
|
-
});
|
|
84
|
-
it('splits text around variable tokens', () => {
|
|
85
|
-
expect(tokenizeVariables('https://{{host}}/api/{{version}}')).toEqual([
|
|
86
|
-
{ text: 'https://' },
|
|
87
|
-
{ text: '{{host}}', key: 'host' },
|
|
88
|
-
{ text: '/api/' },
|
|
89
|
-
{ text: '{{version}}', key: 'version' }
|
|
90
|
-
]);
|
|
91
|
-
});
|
|
92
|
-
it('tokenizeVariables extracts host key from {{ host }} token', () => {
|
|
93
|
-
expect(tokenizeVariables('https://{{ host }}/users')).toEqual([
|
|
94
|
-
{ text: 'https://' },
|
|
95
|
-
{ text: '{{ host }}', key: 'host' },
|
|
96
|
-
{ text: '/users' }
|
|
97
|
-
]);
|
|
98
|
-
});
|
|
99
|
-
it('tokenizeVariables recognizes dynamic variable keys with $ prefix', () => {
|
|
100
|
-
expect(tokenizeVariables('{{$randomUUID}}')).toEqual([
|
|
101
|
-
{ text: '{{$randomUUID}}', key: '$randomUUID' }
|
|
102
|
-
]);
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
describe('resolveVariable', () => {
|
|
106
|
-
it('returns the resolved value for a known key', () => {
|
|
107
|
-
expect(resolveVariable('host', [variable('host', 'api.example.com')])).toBe('api.example.com');
|
|
108
|
-
});
|
|
109
|
-
it('falls back to defaultValue when value is empty', () => {
|
|
110
|
-
expect(resolveVariable('host', [variable('host', '', 'localhost')])).toBe('localhost');
|
|
111
|
-
});
|
|
112
|
-
it('returns undefined for unknown keys', () => {
|
|
113
|
-
expect(resolveVariable('missing', [variable('host', 'api.example.com')])).toBeUndefined();
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
describe('getVariableTokenAtOffset', () => {
|
|
117
|
-
const text = 'https://{{host}}/api/{{version}}';
|
|
118
|
-
it('returns the token when offset is inside a variable placeholder', () => {
|
|
119
|
-
expect(getVariableTokenAtOffset(text, 10)).toEqual({
|
|
120
|
-
key: 'host',
|
|
121
|
-
start: 8,
|
|
122
|
-
end: 16
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
it('returns null when offset is outside any token', () => {
|
|
126
|
-
expect(getVariableTokenAtOffset(text, 0)).toBeNull();
|
|
127
|
-
expect(getVariableTokenAtOffset(text, 7)).toBeNull();
|
|
128
|
-
});
|
|
129
|
-
it('matches at token start and end boundaries', () => {
|
|
130
|
-
expect(getVariableTokenAtOffset('{{host}}', 0)?.key).toBe('host');
|
|
131
|
-
expect(getVariableTokenAtOffset('{{host}}', 8)?.key).toBe('host');
|
|
132
|
-
});
|
|
133
|
-
it('matches tokens with whitespace around the key', () => {
|
|
134
|
-
expect(getVariableTokenAtOffset('https://{{ host }}/users', 10)).toEqual({
|
|
135
|
-
key: 'host',
|
|
136
|
-
start: 8,
|
|
137
|
-
end: 18
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
describe('getVariableTooltipContent', () => {
|
|
142
|
-
it('returns resolved static value without muted styling', () => {
|
|
143
|
-
expect(getVariableTooltipContent('host', [variable('host', 'api.example.com')])).toEqual({
|
|
144
|
-
text: 'api.example.com',
|
|
145
|
-
muted: false
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
it('returns dynamic description for registered dynamic variables', () => {
|
|
149
|
-
expect(getVariableTooltipContent('$guid', [])).toEqual({
|
|
150
|
-
text: 'Dynamic: A uuid-v4 style guid',
|
|
151
|
-
muted: true
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
it('returns Not defined for unknown keys', () => {
|
|
155
|
-
expect(getVariableTooltipContent('missing', [variable('host', 'api.example.com')])).toEqual({
|
|
156
|
-
text: 'Not defined',
|
|
157
|
-
muted: true
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
});
|