ai-progress-controls 0.1.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/LICENSE +21 -0
- package/README.md +823 -0
- package/dist/ai-progress-controls.es.js +7191 -0
- package/dist/ai-progress-controls.es.js.map +1 -0
- package/dist/ai-progress-controls.umd.js +2 -0
- package/dist/ai-progress-controls.umd.js.map +1 -0
- package/dist/index.d.ts +2212 -0
- package/package.json +105 -0
- package/src/__tests__/setup.ts +93 -0
- package/src/core/base/AIControl.ts +230 -0
- package/src/core/base/index.ts +3 -0
- package/src/core/base/types.ts +77 -0
- package/src/core/base/utils.ts +168 -0
- package/src/core/batch-progress/BatchProgress.test.ts +458 -0
- package/src/core/batch-progress/BatchProgress.ts +760 -0
- package/src/core/batch-progress/index.ts +14 -0
- package/src/core/batch-progress/styles.ts +480 -0
- package/src/core/batch-progress/types.ts +169 -0
- package/src/core/model-loader/ModelLoader.test.ts +311 -0
- package/src/core/model-loader/ModelLoader.ts +673 -0
- package/src/core/model-loader/index.ts +2 -0
- package/src/core/model-loader/styles.ts +496 -0
- package/src/core/model-loader/types.ts +127 -0
- package/src/core/parameter-panel/ParameterPanel.test.ts +856 -0
- package/src/core/parameter-panel/ParameterPanel.ts +877 -0
- package/src/core/parameter-panel/index.ts +14 -0
- package/src/core/parameter-panel/styles.ts +323 -0
- package/src/core/parameter-panel/types.ts +278 -0
- package/src/core/parameter-slider/ParameterSlider.test.ts +299 -0
- package/src/core/parameter-slider/ParameterSlider.ts +653 -0
- package/src/core/parameter-slider/index.ts +8 -0
- package/src/core/parameter-slider/styles.ts +493 -0
- package/src/core/parameter-slider/types.ts +107 -0
- package/src/core/queue-progress/QueueProgress.test.ts +344 -0
- package/src/core/queue-progress/QueueProgress.ts +563 -0
- package/src/core/queue-progress/index.ts +5 -0
- package/src/core/queue-progress/styles.ts +469 -0
- package/src/core/queue-progress/types.ts +130 -0
- package/src/core/retry-progress/RetryProgress.test.ts +397 -0
- package/src/core/retry-progress/RetryProgress.ts +957 -0
- package/src/core/retry-progress/index.ts +6 -0
- package/src/core/retry-progress/styles.ts +530 -0
- package/src/core/retry-progress/types.ts +176 -0
- package/src/core/stream-progress/StreamProgress.test.ts +531 -0
- package/src/core/stream-progress/StreamProgress.ts +517 -0
- package/src/core/stream-progress/index.ts +2 -0
- package/src/core/stream-progress/styles.ts +349 -0
- package/src/core/stream-progress/types.ts +82 -0
- package/src/index.ts +19 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { RetryProgress } from './RetryProgress';
|
|
3
|
+
import { waitForElement, waitForNextTick } from '../../__tests__/setup';
|
|
4
|
+
|
|
5
|
+
describe('RetryProgress Component', () => {
|
|
6
|
+
let progress: RetryProgress;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
progress = new RetryProgress();
|
|
10
|
+
document.body.appendChild(progress);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
if (progress.parentNode) {
|
|
15
|
+
progress.remove();
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('Constructor & Configuration', () => {
|
|
20
|
+
it('should create instance with default config', () => {
|
|
21
|
+
expect(progress).toBeInstanceOf(RetryProgress);
|
|
22
|
+
expect(progress.tagName.toLowerCase()).toBe('retry-progress');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should accept custom configuration', () => {
|
|
26
|
+
const customProgress = new RetryProgress({
|
|
27
|
+
maxAttempts: 5,
|
|
28
|
+
strategy: 'exponential',
|
|
29
|
+
baseDelay: 1000,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(customProgress['config'].maxAttempts).toBe(5);
|
|
33
|
+
expect(customProgress['config'].strategy).toBe('exponential');
|
|
34
|
+
customProgress.remove();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should have shadow root', async () => {
|
|
38
|
+
await waitForElement(progress);
|
|
39
|
+
expect(progress.shadowRoot).toBeTruthy();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('State Management', () => {
|
|
44
|
+
it('should initialize with idle status', () => {
|
|
45
|
+
const status = progress.getStatus();
|
|
46
|
+
expect(status).toBe('idle');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should track attempt number', () => {
|
|
50
|
+
progress.attempt('Operation 1');
|
|
51
|
+
expect(progress.getAttempt()).toBe(1);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should track retry timing', () => {
|
|
55
|
+
progress.attempt('Operation 1');
|
|
56
|
+
progress.waitForRetry(1000);
|
|
57
|
+
|
|
58
|
+
const timeUntilRetry = progress.getTimeUntilRetry();
|
|
59
|
+
expect(timeUntilRetry).toBeGreaterThan(0);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('Methods', () => {
|
|
64
|
+
describe('attempt()', () => {
|
|
65
|
+
it('should start new attempt', () => {
|
|
66
|
+
progress.attempt('Test operation');
|
|
67
|
+
|
|
68
|
+
expect(progress.getStatus()).toBe('attempting');
|
|
69
|
+
expect(progress.getAttempt()).toBe(1);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should emit retryattempt event', async () => {
|
|
73
|
+
const spy = vi.fn();
|
|
74
|
+
progress.addEventListener('retryattempt', spy);
|
|
75
|
+
|
|
76
|
+
progress.attempt('Test');
|
|
77
|
+
await waitForNextTick();
|
|
78
|
+
|
|
79
|
+
expect(spy).toHaveBeenCalled();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should not auto-increment on each call', () => {
|
|
83
|
+
progress.attempt('Test 1');
|
|
84
|
+
expect(progress.getAttempt()).toBe(1);
|
|
85
|
+
|
|
86
|
+
// attempt() doesn't increment, waitForRetry() does
|
|
87
|
+
progress.attempt('Test 2');
|
|
88
|
+
expect(progress.getAttempt()).toBe(1);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('waitForRetry()', () => {
|
|
93
|
+
beforeEach(() => {
|
|
94
|
+
progress.attempt('Test operation');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should set retry wait state', () => {
|
|
98
|
+
progress.waitForRetry(1000);
|
|
99
|
+
|
|
100
|
+
expect(progress.getStatus()).toBe('waiting');
|
|
101
|
+
expect(progress.getTimeUntilRetry()).toBeGreaterThan(0);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should emit retrywaiting event', async () => {
|
|
105
|
+
const spy = vi.fn();
|
|
106
|
+
progress.addEventListener('retrywaiting', spy);
|
|
107
|
+
|
|
108
|
+
progress.waitForRetry({ delay: 500 });
|
|
109
|
+
await waitForNextTick();
|
|
110
|
+
|
|
111
|
+
expect(spy).toHaveBeenCalled();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should countdown retry timer', async () => {
|
|
115
|
+
progress.waitForRetry(100);
|
|
116
|
+
const initialTime = progress.getTimeUntilRetry();
|
|
117
|
+
|
|
118
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
119
|
+
|
|
120
|
+
const laterTime = progress.getTimeUntilRetry();
|
|
121
|
+
expect(laterTime).toBeLessThan(initialTime);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('success()', () => {
|
|
126
|
+
beforeEach(() => {
|
|
127
|
+
progress.attempt('Test operation');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should mark operation as successful', () => {
|
|
131
|
+
progress.success();
|
|
132
|
+
expect(progress.getStatus()).toBe('success');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should emit retrysuccess event', async () => {
|
|
136
|
+
const spy = vi.fn();
|
|
137
|
+
progress.addEventListener('retrysuccess', spy);
|
|
138
|
+
|
|
139
|
+
progress.success();
|
|
140
|
+
await waitForNextTick();
|
|
141
|
+
|
|
142
|
+
expect(spy).toHaveBeenCalled();
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('failure()', () => {
|
|
147
|
+
beforeEach(() => {
|
|
148
|
+
progress.attempt('Test operation');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should handle failure', () => {
|
|
152
|
+
progress.failure('Operation failed');
|
|
153
|
+
expect(progress.getStatus()).toBe('failed');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should emit retryfailure event', async () => {
|
|
157
|
+
const spy = vi.fn();
|
|
158
|
+
progress.addEventListener('retryfailure', spy);
|
|
159
|
+
|
|
160
|
+
progress.failure('Test error');
|
|
161
|
+
await waitForNextTick();
|
|
162
|
+
|
|
163
|
+
expect(spy).toHaveBeenCalled();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should track max attempts reached', () => {
|
|
167
|
+
const maxAttempts = progress['config'].maxAttempts || 3;
|
|
168
|
+
|
|
169
|
+
// First attempt
|
|
170
|
+
progress.attempt(`Attempt 1`);
|
|
171
|
+
expect(progress.getAttempt()).toBe(1);
|
|
172
|
+
|
|
173
|
+
// Subsequent attempts via waitForRetry which increments
|
|
174
|
+
for (let i = 2; i <= maxAttempts; i++) {
|
|
175
|
+
progress.waitForRetry({ attempt: i });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
expect(progress.getAttempt()).toBe(maxAttempts);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('cancel()', () => {
|
|
183
|
+
beforeEach(() => {
|
|
184
|
+
progress.attempt('Test operation');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should cancel retry process', () => {
|
|
188
|
+
progress.cancel();
|
|
189
|
+
expect(progress.getStatus()).toBe('cancelled');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should emit retrycancel event', async () => {
|
|
193
|
+
const spy = vi.fn();
|
|
194
|
+
progress.addEventListener('retrycancel', spy);
|
|
195
|
+
|
|
196
|
+
progress.cancel();
|
|
197
|
+
await waitForNextTick();
|
|
198
|
+
|
|
199
|
+
expect(spy).toHaveBeenCalled();
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe('reset()', () => {
|
|
204
|
+
it('should reset to initial state', () => {
|
|
205
|
+
progress.attempt('Test 1');
|
|
206
|
+
progress.waitForRetry({ attempt: 2, delay: 1000 });
|
|
207
|
+
progress.reset();
|
|
208
|
+
|
|
209
|
+
expect(progress.getStatus()).toBe('idle');
|
|
210
|
+
expect(progress.getAttempt()).toBe(1); // Reset to initial attempt
|
|
211
|
+
expect(progress.getTimeUntilRetry()).toBe(0);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe('Properties', () => {
|
|
217
|
+
it('should get/set disabled state', () => {
|
|
218
|
+
expect(progress.disabled).toBe(false);
|
|
219
|
+
|
|
220
|
+
progress.disabled = true;
|
|
221
|
+
expect(progress.disabled).toBe(true);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe('Rendering', () => {
|
|
226
|
+
it('should render in shadow DOM', async () => {
|
|
227
|
+
await waitForElement(progress);
|
|
228
|
+
expect(progress.shadowRoot?.querySelector('.retry-container')).toBeTruthy();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should update display on attempt', async () => {
|
|
232
|
+
progress.attempt('Test operation');
|
|
233
|
+
await waitForElement(progress);
|
|
234
|
+
|
|
235
|
+
const container = progress.shadowRoot?.querySelector('.retry-container');
|
|
236
|
+
expect(container).toBeTruthy();
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('Accessibility', () => {
|
|
241
|
+
it('should have proper ARIA role', async () => {
|
|
242
|
+
await waitForElement(progress);
|
|
243
|
+
const role = progress.getAttribute('role');
|
|
244
|
+
expect(role).toBe('region');
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
describe('Retry Strategies', () => {
|
|
249
|
+
it('should handle constant delay strategy', () => {
|
|
250
|
+
const constantProgress = new RetryProgress({
|
|
251
|
+
strategy: 'constant',
|
|
252
|
+
baseDelay: 1000,
|
|
253
|
+
});
|
|
254
|
+
document.body.appendChild(constantProgress);
|
|
255
|
+
|
|
256
|
+
constantProgress.attempt('Test');
|
|
257
|
+
constantProgress.waitForRetry();
|
|
258
|
+
|
|
259
|
+
expect(constantProgress.getTimeUntilRetry()).toBeCloseTo(1000, -2);
|
|
260
|
+
constantProgress.remove();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('should handle exponential backoff strategy', () => {
|
|
264
|
+
const expProgress = new RetryProgress({
|
|
265
|
+
strategy: 'exponential',
|
|
266
|
+
baseDelay: 100,
|
|
267
|
+
});
|
|
268
|
+
document.body.appendChild(expProgress);
|
|
269
|
+
|
|
270
|
+
expProgress.attempt('Test 1');
|
|
271
|
+
const delay1 = expProgress['calculateDelay'](1);
|
|
272
|
+
|
|
273
|
+
expProgress.attempt('Test 2');
|
|
274
|
+
const delay2 = expProgress['calculateDelay'](2);
|
|
275
|
+
|
|
276
|
+
expect(delay2).toBeGreaterThan(delay1);
|
|
277
|
+
expProgress.remove();
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe('Edge Cases', () => {
|
|
282
|
+
it('should handle zero max attempts', () => {
|
|
283
|
+
const zeroProgress = new RetryProgress({ maxAttempts: 0 });
|
|
284
|
+
document.body.appendChild(zeroProgress);
|
|
285
|
+
|
|
286
|
+
zeroProgress.attempt('Test');
|
|
287
|
+
expect(zeroProgress.getAttempt()).toBe(1);
|
|
288
|
+
|
|
289
|
+
zeroProgress.remove();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should handle rapid attempt cycles', async () => {
|
|
293
|
+
let currentAttempt = 1;
|
|
294
|
+
for (let i = 0; i < 5; i++) {
|
|
295
|
+
progress.attempt(`Attempt ${i}`);
|
|
296
|
+
await waitForNextTick();
|
|
297
|
+
progress.failure('Failed');
|
|
298
|
+
await waitForNextTick();
|
|
299
|
+
// Would need to call waitForRetry to actually increment for next cycle
|
|
300
|
+
if (i < 4) {
|
|
301
|
+
currentAttempt++;
|
|
302
|
+
progress.waitForRetry({ attempt: currentAttempt });
|
|
303
|
+
await waitForNextTick();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
expect(progress.getAttempt()).toBeGreaterThan(0);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
describe('Size Variants', () => {
|
|
312
|
+
it('should default to default size', () => {
|
|
313
|
+
expect(progress['config'].size).toBe('default');
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('should accept size in constructor', () => {
|
|
317
|
+
const compact = new RetryProgress({ size: 'compact' });
|
|
318
|
+
expect(compact['config'].size).toBe('compact');
|
|
319
|
+
compact.remove();
|
|
320
|
+
|
|
321
|
+
const large = new RetryProgress({ size: 'large' });
|
|
322
|
+
expect(large['config'].size).toBe('large');
|
|
323
|
+
large.remove();
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('should update size via attribute', async () => {
|
|
327
|
+
await waitForElement(progress);
|
|
328
|
+
|
|
329
|
+
progress.setAttribute('size', 'compact');
|
|
330
|
+
await waitForNextTick();
|
|
331
|
+
expect(progress['config'].size).toBe('compact');
|
|
332
|
+
expect(progress.getAttribute('size')).toBe('compact');
|
|
333
|
+
|
|
334
|
+
progress.setAttribute('size', 'large');
|
|
335
|
+
await waitForNextTick();
|
|
336
|
+
expect(progress['config'].size).toBe('large');
|
|
337
|
+
expect(progress.getAttribute('size')).toBe('large');
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('should apply size attribute to host element', async () => {
|
|
341
|
+
await waitForElement(progress);
|
|
342
|
+
|
|
343
|
+
progress.setAttribute('size', 'compact');
|
|
344
|
+
await waitForNextTick();
|
|
345
|
+
expect(progress.hasAttribute('size')).toBe(true);
|
|
346
|
+
expect(progress.getAttribute('size')).toBe('compact');
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
describe('Visual Variants', () => {
|
|
351
|
+
it('should apply default variant by default', () => {
|
|
352
|
+
expect(progress['config'].variant).toBe('default');
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('should apply minimal variant', () => {
|
|
356
|
+
const minimal = new RetryProgress({ variant: 'minimal' });
|
|
357
|
+
expect(minimal['config'].variant).toBe('minimal');
|
|
358
|
+
minimal.remove();
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('should apply gradient variant', () => {
|
|
362
|
+
const gradient = new RetryProgress({ variant: 'gradient' });
|
|
363
|
+
expect(gradient['config'].variant).toBe('gradient');
|
|
364
|
+
gradient.remove();
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('should apply glassmorphic variant', () => {
|
|
368
|
+
const glassmorphic = new RetryProgress({ variant: 'glassmorphic' });
|
|
369
|
+
expect(glassmorphic['config'].variant).toBe('glassmorphic');
|
|
370
|
+
glassmorphic.remove();
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
describe('Animation Effects', () => {
|
|
375
|
+
it('should apply none animation by default', () => {
|
|
376
|
+
expect(progress['config'].animation).toBe('none');
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('should apply striped animation', () => {
|
|
380
|
+
const striped = new RetryProgress({ animation: 'striped' });
|
|
381
|
+
expect(striped['config'].animation).toBe('striped');
|
|
382
|
+
striped.remove();
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it('should apply pulse animation', () => {
|
|
386
|
+
const pulse = new RetryProgress({ animation: 'pulse' });
|
|
387
|
+
expect(pulse['config'].animation).toBe('pulse');
|
|
388
|
+
pulse.remove();
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('should apply glow animation', () => {
|
|
392
|
+
const glow = new RetryProgress({ animation: 'glow' });
|
|
393
|
+
expect(glow['config'].animation).toBe('glow');
|
|
394
|
+
glow.remove();
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
});
|