@rogieking/figui3 2.10.0 → 2.10.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/LICENSE +21 -0
- package/README.md +696 -203
- package/components.css +107 -129
- package/dist/fig.js +74 -0
- package/fig.js +207 -118
- package/package.json +31 -8
- package/.vscode/settings.json +0 -3
- package/AUDIT.md +0 -183
- package/example.html +0 -3135
- package/glitch.html +0 -70
- package/index.ts +0 -1
- package/test.html +0 -843
- package/tracer.html +0 -584
- package/tsconfig.json +0 -27
package/test.html
DELETED
|
@@ -1,843 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>FigUI Component Tests</title>
|
|
7
|
-
<link rel="stylesheet" href="fig.css">
|
|
8
|
-
<style>
|
|
9
|
-
:root {
|
|
10
|
-
--figma-color-bg: #1e1e1e;
|
|
11
|
-
--figma-color-bg-secondary: #2c2c2c;
|
|
12
|
-
--figma-color-bg-tertiary: #383838;
|
|
13
|
-
--figma-color-bg-hover: #444;
|
|
14
|
-
--figma-color-text: #fff;
|
|
15
|
-
--figma-color-text-secondary: #b3b3b3;
|
|
16
|
-
--figma-color-text-tertiary: #808080;
|
|
17
|
-
--figma-color-border: #444;
|
|
18
|
-
--figma-color-border-selected: #0d99ff;
|
|
19
|
-
--figma-color-bg-brand: #0d99ff;
|
|
20
|
-
--figma-color-text-onbrand: #fff;
|
|
21
|
-
--figma-color-bg-danger: #f24822;
|
|
22
|
-
--figma-color-text-danger: #f24822;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
* {
|
|
26
|
-
box-sizing: border-box;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
body {
|
|
30
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
31
|
-
background: var(--figma-color-bg);
|
|
32
|
-
color: var(--figma-color-text);
|
|
33
|
-
padding: 2rem;
|
|
34
|
-
margin: 0;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
h1 {
|
|
38
|
-
margin: 0 0 1rem;
|
|
39
|
-
font-size: 1.5rem;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.test-summary {
|
|
43
|
-
display: flex;
|
|
44
|
-
gap: 1rem;
|
|
45
|
-
margin-bottom: 1.5rem;
|
|
46
|
-
padding: 1rem;
|
|
47
|
-
background: var(--figma-color-bg-secondary);
|
|
48
|
-
border-radius: 8px;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.test-stat {
|
|
52
|
-
display: flex;
|
|
53
|
-
flex-direction: column;
|
|
54
|
-
align-items: center;
|
|
55
|
-
padding: 0.5rem 1rem;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.test-stat-value {
|
|
59
|
-
font-size: 2rem;
|
|
60
|
-
font-weight: bold;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.test-stat-label {
|
|
64
|
-
font-size: 0.75rem;
|
|
65
|
-
color: var(--figma-color-text-secondary);
|
|
66
|
-
text-transform: uppercase;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
.test-stat.passed .test-stat-value { color: #30d158; }
|
|
70
|
-
.test-stat.failed .test-stat-value { color: #f24822; }
|
|
71
|
-
.test-stat.skipped .test-stat-value { color: #ff9f0a; }
|
|
72
|
-
|
|
73
|
-
.test-group {
|
|
74
|
-
margin-bottom: 1.5rem;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
.test-group-header {
|
|
78
|
-
display: flex;
|
|
79
|
-
align-items: center;
|
|
80
|
-
gap: 0.5rem;
|
|
81
|
-
padding: 0.5rem;
|
|
82
|
-
background: var(--figma-color-bg-tertiary);
|
|
83
|
-
border-radius: 4px;
|
|
84
|
-
cursor: pointer;
|
|
85
|
-
user-select: none;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
.test-group-header:hover {
|
|
89
|
-
background: var(--figma-color-bg-hover);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
.test-group-name {
|
|
93
|
-
flex: 1;
|
|
94
|
-
font-weight: 600;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.test-group-count {
|
|
98
|
-
font-size: 0.875rem;
|
|
99
|
-
color: var(--figma-color-text-secondary);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
.test-group-status {
|
|
103
|
-
width: 8px;
|
|
104
|
-
height: 8px;
|
|
105
|
-
border-radius: 50%;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
.test-group-status.passed { background: #30d158; }
|
|
109
|
-
.test-group-status.failed { background: #f24822; }
|
|
110
|
-
|
|
111
|
-
.test-list {
|
|
112
|
-
margin-top: 0.5rem;
|
|
113
|
-
padding-left: 1rem;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
.test-item {
|
|
117
|
-
display: flex;
|
|
118
|
-
align-items: center;
|
|
119
|
-
gap: 0.5rem;
|
|
120
|
-
padding: 0.25rem 0;
|
|
121
|
-
font-size: 0.875rem;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
.test-icon {
|
|
125
|
-
width: 16px;
|
|
126
|
-
height: 16px;
|
|
127
|
-
display: flex;
|
|
128
|
-
align-items: center;
|
|
129
|
-
justify-content: center;
|
|
130
|
-
font-size: 12px;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
.test-icon.passed { color: #30d158; }
|
|
134
|
-
.test-icon.failed { color: #f24822; }
|
|
135
|
-
.test-icon.skipped { color: #ff9f0a; }
|
|
136
|
-
|
|
137
|
-
.test-name {
|
|
138
|
-
flex: 1;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
.test-error {
|
|
142
|
-
color: #f24822;
|
|
143
|
-
font-size: 0.75rem;
|
|
144
|
-
margin-left: 1.5rem;
|
|
145
|
-
padding: 0.25rem 0.5rem;
|
|
146
|
-
background: rgba(242, 72, 34, 0.1);
|
|
147
|
-
border-radius: 4px;
|
|
148
|
-
font-family: monospace;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
.test-duration {
|
|
152
|
-
color: var(--figma-color-text-tertiary);
|
|
153
|
-
font-size: 0.75rem;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
#test-container {
|
|
157
|
-
position: fixed;
|
|
158
|
-
top: -9999px;
|
|
159
|
-
left: -9999px;
|
|
160
|
-
visibility: hidden;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
.run-button {
|
|
164
|
-
background: var(--figma-color-bg-brand);
|
|
165
|
-
color: var(--figma-color-text-onbrand);
|
|
166
|
-
border: none;
|
|
167
|
-
padding: 0.5rem 1rem;
|
|
168
|
-
border-radius: 4px;
|
|
169
|
-
cursor: pointer;
|
|
170
|
-
font-size: 0.875rem;
|
|
171
|
-
margin-bottom: 1rem;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
.run-button:hover {
|
|
175
|
-
filter: brightness(1.1);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
.run-button:disabled {
|
|
179
|
-
opacity: 0.5;
|
|
180
|
-
cursor: not-allowed;
|
|
181
|
-
}
|
|
182
|
-
</style>
|
|
183
|
-
</head>
|
|
184
|
-
<body>
|
|
185
|
-
<h1>FigUI Component Tests</h1>
|
|
186
|
-
|
|
187
|
-
<button class="run-button" id="run-tests">Run Tests</button>
|
|
188
|
-
|
|
189
|
-
<div class="test-summary" id="summary">
|
|
190
|
-
<div class="test-stat passed">
|
|
191
|
-
<span class="test-stat-value" id="passed-count">0</span>
|
|
192
|
-
<span class="test-stat-label">Passed</span>
|
|
193
|
-
</div>
|
|
194
|
-
<div class="test-stat failed">
|
|
195
|
-
<span class="test-stat-value" id="failed-count">0</span>
|
|
196
|
-
<span class="test-stat-label">Failed</span>
|
|
197
|
-
</div>
|
|
198
|
-
<div class="test-stat skipped">
|
|
199
|
-
<span class="test-stat-value" id="skipped-count">0</span>
|
|
200
|
-
<span class="test-stat-label">Skipped</span>
|
|
201
|
-
</div>
|
|
202
|
-
<div class="test-stat">
|
|
203
|
-
<span class="test-stat-value" id="total-count">0</span>
|
|
204
|
-
<span class="test-stat-label">Total</span>
|
|
205
|
-
</div>
|
|
206
|
-
</div>
|
|
207
|
-
|
|
208
|
-
<div id="results"></div>
|
|
209
|
-
|
|
210
|
-
<!-- Hidden container for test elements -->
|
|
211
|
-
<div id="test-container"></div>
|
|
212
|
-
|
|
213
|
-
<script src="fig.js"></script>
|
|
214
|
-
<script>
|
|
215
|
-
// Simple Test Framework
|
|
216
|
-
class TestRunner {
|
|
217
|
-
constructor() {
|
|
218
|
-
this.groups = [];
|
|
219
|
-
this.currentGroup = null;
|
|
220
|
-
this.container = document.getElementById('test-container');
|
|
221
|
-
this.results = { passed: 0, failed: 0, skipped: 0, total: 0 };
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
describe(name, fn) {
|
|
225
|
-
this.currentGroup = { name, tests: [] };
|
|
226
|
-
this.groups.push(this.currentGroup);
|
|
227
|
-
fn();
|
|
228
|
-
this.currentGroup = null;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
it(name, fn) {
|
|
232
|
-
if (this.currentGroup) {
|
|
233
|
-
this.currentGroup.tests.push({ name, fn });
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
skip(name, fn) {
|
|
238
|
-
if (this.currentGroup) {
|
|
239
|
-
this.currentGroup.tests.push({ name, fn, skipped: true });
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
createElement(tag, attrs = {}) {
|
|
244
|
-
const el = document.createElement(tag);
|
|
245
|
-
for (const [key, value] of Object.entries(attrs)) {
|
|
246
|
-
el.setAttribute(key, value);
|
|
247
|
-
}
|
|
248
|
-
this.container.appendChild(el);
|
|
249
|
-
return el;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
cleanup() {
|
|
253
|
-
this.container.innerHTML = '';
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
async waitFrame() {
|
|
257
|
-
return new Promise(resolve => requestAnimationFrame(resolve));
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
async waitFrames(n = 2) {
|
|
261
|
-
for (let i = 0; i < n; i++) {
|
|
262
|
-
await this.waitFrame();
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
async run() {
|
|
267
|
-
this.results = { passed: 0, failed: 0, skipped: 0, total: 0 };
|
|
268
|
-
const resultsEl = document.getElementById('results');
|
|
269
|
-
resultsEl.innerHTML = '';
|
|
270
|
-
|
|
271
|
-
for (const group of this.groups) {
|
|
272
|
-
const groupEl = document.createElement('div');
|
|
273
|
-
groupEl.className = 'test-group';
|
|
274
|
-
|
|
275
|
-
const headerEl = document.createElement('div');
|
|
276
|
-
headerEl.className = 'test-group-header';
|
|
277
|
-
|
|
278
|
-
const statusEl = document.createElement('div');
|
|
279
|
-
statusEl.className = 'test-group-status';
|
|
280
|
-
|
|
281
|
-
const nameEl = document.createElement('span');
|
|
282
|
-
nameEl.className = 'test-group-name';
|
|
283
|
-
nameEl.textContent = group.name;
|
|
284
|
-
|
|
285
|
-
const countEl = document.createElement('span');
|
|
286
|
-
countEl.className = 'test-group-count';
|
|
287
|
-
|
|
288
|
-
headerEl.appendChild(statusEl);
|
|
289
|
-
headerEl.appendChild(nameEl);
|
|
290
|
-
headerEl.appendChild(countEl);
|
|
291
|
-
groupEl.appendChild(headerEl);
|
|
292
|
-
|
|
293
|
-
const listEl = document.createElement('div');
|
|
294
|
-
listEl.className = 'test-list';
|
|
295
|
-
groupEl.appendChild(listEl);
|
|
296
|
-
|
|
297
|
-
let groupPassed = 0;
|
|
298
|
-
let groupFailed = 0;
|
|
299
|
-
|
|
300
|
-
for (const test of group.tests) {
|
|
301
|
-
this.results.total++;
|
|
302
|
-
const testEl = document.createElement('div');
|
|
303
|
-
|
|
304
|
-
if (test.skipped) {
|
|
305
|
-
this.results.skipped++;
|
|
306
|
-
testEl.innerHTML = `
|
|
307
|
-
<div class="test-item">
|
|
308
|
-
<span class="test-icon skipped">⊘</span>
|
|
309
|
-
<span class="test-name">${test.name}</span>
|
|
310
|
-
</div>
|
|
311
|
-
`;
|
|
312
|
-
} else {
|
|
313
|
-
try {
|
|
314
|
-
this.cleanup();
|
|
315
|
-
await test.fn();
|
|
316
|
-
this.results.passed++;
|
|
317
|
-
groupPassed++;
|
|
318
|
-
testEl.innerHTML = `
|
|
319
|
-
<div class="test-item">
|
|
320
|
-
<span class="test-icon passed">✓</span>
|
|
321
|
-
<span class="test-name">${test.name}</span>
|
|
322
|
-
</div>
|
|
323
|
-
`;
|
|
324
|
-
} catch (error) {
|
|
325
|
-
this.results.failed++;
|
|
326
|
-
groupFailed++;
|
|
327
|
-
testEl.innerHTML = `
|
|
328
|
-
<div class="test-item">
|
|
329
|
-
<span class="test-icon failed">✗</span>
|
|
330
|
-
<span class="test-name">${test.name}</span>
|
|
331
|
-
</div>
|
|
332
|
-
<div class="test-error">${error.message}</div>
|
|
333
|
-
`;
|
|
334
|
-
console.error(`Test failed: ${group.name} > ${test.name}`, error);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
listEl.appendChild(testEl);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
countEl.textContent = `${groupPassed}/${group.tests.length}`;
|
|
342
|
-
statusEl.className = `test-group-status ${groupFailed > 0 ? 'failed' : 'passed'}`;
|
|
343
|
-
|
|
344
|
-
// Toggle visibility
|
|
345
|
-
headerEl.addEventListener('click', () => {
|
|
346
|
-
listEl.style.display = listEl.style.display === 'none' ? 'block' : 'none';
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
resultsEl.appendChild(groupEl);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Update summary
|
|
353
|
-
document.getElementById('passed-count').textContent = this.results.passed;
|
|
354
|
-
document.getElementById('failed-count').textContent = this.results.failed;
|
|
355
|
-
document.getElementById('skipped-count').textContent = this.results.skipped;
|
|
356
|
-
document.getElementById('total-count').textContent = this.results.total;
|
|
357
|
-
|
|
358
|
-
this.cleanup();
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// Assertion helpers
|
|
363
|
-
function assert(condition, message = 'Assertion failed') {
|
|
364
|
-
if (!condition) throw new Error(message);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function assertEqual(actual, expected, message = '') {
|
|
368
|
-
if (actual !== expected) {
|
|
369
|
-
throw new Error(`${message} Expected "${expected}", got "${actual}"`);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
function assertExists(value, message = 'Value should exist') {
|
|
374
|
-
if (value === null || value === undefined) {
|
|
375
|
-
throw new Error(message);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function assertHasAttribute(el, attr, message = '') {
|
|
380
|
-
if (!el.hasAttribute(attr)) {
|
|
381
|
-
throw new Error(message || `Element should have attribute "${attr}"`);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
function assertNotHasAttribute(el, attr, message = '') {
|
|
386
|
-
if (el.hasAttribute(attr)) {
|
|
387
|
-
throw new Error(message || `Element should not have attribute "${attr}"`);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Initialize test runner
|
|
392
|
-
const test = new TestRunner();
|
|
393
|
-
|
|
394
|
-
// ============================================
|
|
395
|
-
// FIG-BUTTON TESTS
|
|
396
|
-
// ============================================
|
|
397
|
-
test.describe('fig-button', () => {
|
|
398
|
-
test.it('should render with shadow DOM', async () => {
|
|
399
|
-
const btn = test.createElement('fig-button');
|
|
400
|
-
btn.textContent = 'Test';
|
|
401
|
-
await test.waitFrames();
|
|
402
|
-
assertExists(btn.shadowRoot, 'Should have shadow root');
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
test.it('should reflect disabled attribute', async () => {
|
|
406
|
-
const btn = test.createElement('fig-button', { disabled: '' });
|
|
407
|
-
btn.textContent = 'Disabled';
|
|
408
|
-
await test.waitFrames();
|
|
409
|
-
assert(btn.hasAttribute('disabled'), 'Should have disabled attribute');
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
test.it('should handle type="toggle" and toggle selected', async () => {
|
|
413
|
-
const btn = test.createElement('fig-button', { type: 'toggle' });
|
|
414
|
-
btn.textContent = 'Toggle';
|
|
415
|
-
await test.waitFrames();
|
|
416
|
-
btn.click();
|
|
417
|
-
await test.waitFrames();
|
|
418
|
-
assertHasAttribute(btn, 'selected', 'Should be selected after click');
|
|
419
|
-
});
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
// ============================================
|
|
423
|
-
// FIG-DROPDOWN TESTS
|
|
424
|
-
// ============================================
|
|
425
|
-
test.describe('fig-dropdown', () => {
|
|
426
|
-
test.it('should render with options', async () => {
|
|
427
|
-
const dd = test.createElement('fig-dropdown');
|
|
428
|
-
dd.innerHTML = '<option value="a">Option A</option><option value="b">Option B</option>';
|
|
429
|
-
await test.waitFrames();
|
|
430
|
-
assertEqual(dd.value, 'a', 'Should have first option as value');
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
test.it('should have value getter/setter', async () => {
|
|
434
|
-
const dd = test.createElement('fig-dropdown');
|
|
435
|
-
dd.innerHTML = '<option value="a">A</option><option value="b">B</option>';
|
|
436
|
-
await test.waitFrames();
|
|
437
|
-
dd.value = 'b';
|
|
438
|
-
assertEqual(dd.value, 'b', 'Value should be updated');
|
|
439
|
-
});
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
// ============================================
|
|
443
|
-
// FIG-TOOLTIP TESTS
|
|
444
|
-
// ============================================
|
|
445
|
-
test.describe('fig-tooltip', () => {
|
|
446
|
-
test.it('should have role="tooltip" on popup', async () => {
|
|
447
|
-
const tt = test.createElement('fig-tooltip', { text: 'Hello' });
|
|
448
|
-
tt.innerHTML = '<button>Hover me</button>';
|
|
449
|
-
await test.waitFrames();
|
|
450
|
-
tt.render();
|
|
451
|
-
assertExists(tt.popup, 'Should have popup');
|
|
452
|
-
assertEqual(tt.popup.getAttribute('role'), 'tooltip', 'Popup should have role="tooltip"');
|
|
453
|
-
});
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
// ============================================
|
|
457
|
-
// FIG-TAB / FIG-TABS TESTS
|
|
458
|
-
// ============================================
|
|
459
|
-
test.describe('fig-tabs', () => {
|
|
460
|
-
test.it('should have role="tablist"', async () => {
|
|
461
|
-
const tabs = test.createElement('fig-tabs');
|
|
462
|
-
tabs.innerHTML = '<fig-tab value="1">Tab 1</fig-tab><fig-tab value="2">Tab 2</fig-tab>';
|
|
463
|
-
await test.waitFrames();
|
|
464
|
-
assertEqual(tabs.getAttribute('role'), 'tablist', 'Should have role="tablist"');
|
|
465
|
-
});
|
|
466
|
-
|
|
467
|
-
test.it('child tabs should have role="tab"', async () => {
|
|
468
|
-
const tabs = test.createElement('fig-tabs');
|
|
469
|
-
tabs.innerHTML = '<fig-tab value="1">Tab 1</fig-tab>';
|
|
470
|
-
await test.waitFrames();
|
|
471
|
-
const tab = tabs.querySelector('fig-tab');
|
|
472
|
-
assertEqual(tab.getAttribute('role'), 'tab', 'Tab should have role="tab"');
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
test.it('should have value getter/setter', async () => {
|
|
476
|
-
const tabs = test.createElement('fig-tabs');
|
|
477
|
-
tabs.innerHTML = '<fig-tab value="a">A</fig-tab><fig-tab value="b">B</fig-tab>';
|
|
478
|
-
await test.waitFrames();
|
|
479
|
-
tabs.value = 'b';
|
|
480
|
-
await test.waitFrames();
|
|
481
|
-
const selectedTab = tabs.querySelector('fig-tab[selected]');
|
|
482
|
-
assertEqual(selectedTab?.getAttribute('value'), 'b', 'Tab B should be selected');
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
test.it('should support disabled attribute', async () => {
|
|
486
|
-
const tabs = test.createElement('fig-tabs', { disabled: '' });
|
|
487
|
-
tabs.innerHTML = '<fig-tab value="1">Tab 1</fig-tab>';
|
|
488
|
-
await test.waitFrames();
|
|
489
|
-
const tab = tabs.querySelector('fig-tab');
|
|
490
|
-
assertHasAttribute(tab, 'disabled', 'Child tabs should be disabled');
|
|
491
|
-
});
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
// ============================================
|
|
495
|
-
// FIG-SEGMENT / FIG-SEGMENTED-CONTROL TESTS
|
|
496
|
-
// ============================================
|
|
497
|
-
test.describe('fig-segmented-control', () => {
|
|
498
|
-
test.it('should auto-select first segment if none selected', async () => {
|
|
499
|
-
const ctrl = test.createElement('fig-segmented-control');
|
|
500
|
-
ctrl.innerHTML = '<fig-segment value="a">A</fig-segment><fig-segment value="b">B</fig-segment>';
|
|
501
|
-
await test.waitFrames(3);
|
|
502
|
-
const firstSeg = ctrl.querySelector('fig-segment');
|
|
503
|
-
assertHasAttribute(firstSeg, 'selected', 'First segment should be auto-selected');
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
test.it('should respect pre-selected segment', async () => {
|
|
507
|
-
const ctrl = test.createElement('fig-segmented-control');
|
|
508
|
-
ctrl.innerHTML = '<fig-segment value="a">A</fig-segment><fig-segment value="b" selected="true">B</fig-segment>';
|
|
509
|
-
await test.waitFrames(3);
|
|
510
|
-
const secondSeg = ctrl.querySelectorAll('fig-segment')[1];
|
|
511
|
-
assertHasAttribute(secondSeg, 'selected', 'Second segment should remain selected');
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
test.it('should have selectedSegment getter', async () => {
|
|
515
|
-
const ctrl = test.createElement('fig-segmented-control');
|
|
516
|
-
ctrl.innerHTML = '<fig-segment value="a">A</fig-segment>';
|
|
517
|
-
await test.waitFrames(3);
|
|
518
|
-
assertExists(ctrl.selectedSegment, 'Should have selectedSegment');
|
|
519
|
-
});
|
|
520
|
-
});
|
|
521
|
-
|
|
522
|
-
// ============================================
|
|
523
|
-
// FIG-SLIDER TESTS
|
|
524
|
-
// ============================================
|
|
525
|
-
test.describe('fig-slider', () => {
|
|
526
|
-
test.it('should render with correct ARIA attributes', async () => {
|
|
527
|
-
const slider = test.createElement('fig-slider', { min: '0', max: '100', value: '50' });
|
|
528
|
-
await test.waitFrames();
|
|
529
|
-
const input = slider.querySelector('input[type="range"]');
|
|
530
|
-
assertExists(input, 'Should have range input');
|
|
531
|
-
assertEqual(input.getAttribute('aria-valuemin'), '0', 'Should have aria-valuemin');
|
|
532
|
-
assertEqual(input.getAttribute('aria-valuemax'), '100', 'Should have aria-valuemax');
|
|
533
|
-
assertEqual(input.getAttribute('aria-valuenow'), '50', 'Should have aria-valuenow');
|
|
534
|
-
});
|
|
535
|
-
|
|
536
|
-
test.it('should emit input and change events', async () => {
|
|
537
|
-
const slider = test.createElement('fig-slider', { value: '50' });
|
|
538
|
-
await test.waitFrames();
|
|
539
|
-
|
|
540
|
-
let inputFired = false;
|
|
541
|
-
let changeFired = false;
|
|
542
|
-
slider.addEventListener('input', () => inputFired = true);
|
|
543
|
-
slider.addEventListener('change', () => changeFired = true);
|
|
544
|
-
|
|
545
|
-
const input = slider.querySelector('input');
|
|
546
|
-
input.value = '75';
|
|
547
|
-
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
548
|
-
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
549
|
-
|
|
550
|
-
assert(inputFired, 'Input event should fire');
|
|
551
|
-
assert(changeFired, 'Change event should fire');
|
|
552
|
-
});
|
|
553
|
-
});
|
|
554
|
-
|
|
555
|
-
// ============================================
|
|
556
|
-
// FIG-INPUT-TEXT TESTS
|
|
557
|
-
// ============================================
|
|
558
|
-
test.describe('fig-input-text', () => {
|
|
559
|
-
test.it('should render input element', async () => {
|
|
560
|
-
const input = test.createElement('fig-input-text', { value: 'test', placeholder: 'Enter text' });
|
|
561
|
-
await test.waitFrames();
|
|
562
|
-
const inputEl = input.querySelector('input');
|
|
563
|
-
assertExists(inputEl, 'Should have input element');
|
|
564
|
-
assertEqual(inputEl.value, 'test', 'Should have correct value');
|
|
565
|
-
});
|
|
566
|
-
|
|
567
|
-
test.it('should support disabled attribute', async () => {
|
|
568
|
-
const input = test.createElement('fig-input-text', { disabled: '' });
|
|
569
|
-
await test.waitFrames();
|
|
570
|
-
const inputEl = input.querySelector('input');
|
|
571
|
-
assert(inputEl.disabled, 'Input should be disabled');
|
|
572
|
-
});
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
// ============================================
|
|
576
|
-
// FIG-INPUT-NUMBER TESTS
|
|
577
|
-
// ============================================
|
|
578
|
-
test.describe('fig-input-number', () => {
|
|
579
|
-
test.it('should render with units', async () => {
|
|
580
|
-
const input = test.createElement('fig-input-number', { value: '50', units: '%' });
|
|
581
|
-
await test.waitFrames();
|
|
582
|
-
const inputEl = input.querySelector('input');
|
|
583
|
-
assertExists(inputEl, 'Should have input element');
|
|
584
|
-
assert(inputEl.value.includes('50'), 'Should display value');
|
|
585
|
-
assert(inputEl.value.includes('%'), 'Should display units');
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
test.it('should have min/max constraints', async () => {
|
|
589
|
-
const input = test.createElement('fig-input-number', { min: '0', max: '100', value: '50' });
|
|
590
|
-
await test.waitFrames();
|
|
591
|
-
assertEqual(input.min, 0, 'Should have min');
|
|
592
|
-
assertEqual(input.max, 100, 'Should have max');
|
|
593
|
-
});
|
|
594
|
-
});
|
|
595
|
-
|
|
596
|
-
// ============================================
|
|
597
|
-
// FIG-CHECKBOX TESTS
|
|
598
|
-
// ============================================
|
|
599
|
-
test.describe('fig-checkbox', () => {
|
|
600
|
-
test.it('should render checkbox input', async () => {
|
|
601
|
-
const cb = test.createElement('fig-checkbox');
|
|
602
|
-
await test.waitFrames();
|
|
603
|
-
const input = cb.querySelector('input[type="checkbox"]');
|
|
604
|
-
assertExists(input, 'Should have checkbox input');
|
|
605
|
-
assertEqual(input.getAttribute('role'), 'checkbox', 'Should have role="checkbox"');
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
test.it('should have checked getter/setter', async () => {
|
|
609
|
-
const cb = test.createElement('fig-checkbox');
|
|
610
|
-
await test.waitFrames();
|
|
611
|
-
cb.checked = true;
|
|
612
|
-
assert(cb.checked, 'Should be checked');
|
|
613
|
-
assertHasAttribute(cb, 'checked', 'Should have checked attribute');
|
|
614
|
-
});
|
|
615
|
-
|
|
616
|
-
test.it('should have value getter/setter', async () => {
|
|
617
|
-
const cb = test.createElement('fig-checkbox', { value: 'test-value' });
|
|
618
|
-
await test.waitFrames();
|
|
619
|
-
assertEqual(cb.value, 'test-value', 'Should have correct value');
|
|
620
|
-
});
|
|
621
|
-
|
|
622
|
-
test.it('should emit input and change events', async () => {
|
|
623
|
-
const cb = test.createElement('fig-checkbox');
|
|
624
|
-
await test.waitFrames();
|
|
625
|
-
|
|
626
|
-
let inputFired = false;
|
|
627
|
-
let changeFired = false;
|
|
628
|
-
cb.addEventListener('input', () => inputFired = true);
|
|
629
|
-
cb.addEventListener('change', () => changeFired = true);
|
|
630
|
-
|
|
631
|
-
const input = cb.querySelector('input');
|
|
632
|
-
input.click();
|
|
633
|
-
|
|
634
|
-
assert(inputFired, 'Input event should fire');
|
|
635
|
-
assert(changeFired, 'Change event should fire');
|
|
636
|
-
});
|
|
637
|
-
});
|
|
638
|
-
|
|
639
|
-
// ============================================
|
|
640
|
-
// FIG-SWITCH TESTS
|
|
641
|
-
// ============================================
|
|
642
|
-
test.describe('fig-switch', () => {
|
|
643
|
-
test.it('should have role="switch"', async () => {
|
|
644
|
-
const sw = test.createElement('fig-switch');
|
|
645
|
-
await test.waitFrames();
|
|
646
|
-
const input = sw.querySelector('input');
|
|
647
|
-
assertEqual(input.getAttribute('role'), 'switch', 'Should have role="switch"');
|
|
648
|
-
});
|
|
649
|
-
});
|
|
650
|
-
|
|
651
|
-
// ============================================
|
|
652
|
-
// FIG-RADIO TESTS
|
|
653
|
-
// ============================================
|
|
654
|
-
test.describe('fig-radio', () => {
|
|
655
|
-
test.it('should render radio input', async () => {
|
|
656
|
-
const radio = test.createElement('fig-radio', { name: 'test-group' });
|
|
657
|
-
await test.waitFrames();
|
|
658
|
-
const input = radio.querySelector('input[type="radio"]');
|
|
659
|
-
assertExists(input, 'Should have radio input');
|
|
660
|
-
assertEqual(input.name, 'test-group', 'Should have correct name');
|
|
661
|
-
});
|
|
662
|
-
});
|
|
663
|
-
|
|
664
|
-
// ============================================
|
|
665
|
-
// FIG-COMBO-INPUT TESTS
|
|
666
|
-
// ============================================
|
|
667
|
-
test.describe('fig-combo-input', () => {
|
|
668
|
-
test.it('should render with options', async () => {
|
|
669
|
-
const combo = test.createElement('fig-combo-input', { options: 'a,b,c', placeholder: 'Select' });
|
|
670
|
-
await test.waitFrames();
|
|
671
|
-
const input = combo.querySelector('fig-input-text');
|
|
672
|
-
const dropdown = combo.querySelector('fig-dropdown');
|
|
673
|
-
assertExists(input, 'Should have text input');
|
|
674
|
-
assertExists(dropdown, 'Should have dropdown');
|
|
675
|
-
});
|
|
676
|
-
|
|
677
|
-
test.it('should support disabled attribute', async () => {
|
|
678
|
-
const combo = test.createElement('fig-combo-input', { options: 'a,b', disabled: '' });
|
|
679
|
-
await test.waitFrames(3);
|
|
680
|
-
const input = combo.querySelector('fig-input-text');
|
|
681
|
-
assertHasAttribute(input, 'disabled', 'Input should be disabled');
|
|
682
|
-
});
|
|
683
|
-
});
|
|
684
|
-
|
|
685
|
-
// ============================================
|
|
686
|
-
// FIG-CHIT TESTS
|
|
687
|
-
// ============================================
|
|
688
|
-
test.describe('fig-chit', () => {
|
|
689
|
-
test.it('should render with background color', async () => {
|
|
690
|
-
const chit = test.createElement('fig-chit', { background: '#ff0000' });
|
|
691
|
-
await test.waitFrames();
|
|
692
|
-
assertEqual(chit.background, '#ff0000', 'Should have correct background');
|
|
693
|
-
});
|
|
694
|
-
|
|
695
|
-
test.it('should support alpha attribute', async () => {
|
|
696
|
-
const chit = test.createElement('fig-chit', { background: '#ff0000', alpha: '0.5' });
|
|
697
|
-
await test.waitFrames();
|
|
698
|
-
const alphaStyle = chit.style.getPropertyValue('--alpha');
|
|
699
|
-
assertEqual(alphaStyle, '0.5', 'Should have alpha CSS variable');
|
|
700
|
-
});
|
|
701
|
-
});
|
|
702
|
-
|
|
703
|
-
// ============================================
|
|
704
|
-
// FIG-INPUT-COLOR TESTS
|
|
705
|
-
// ============================================
|
|
706
|
-
test.describe('fig-input-color', () => {
|
|
707
|
-
test.it('should parse hex color', async () => {
|
|
708
|
-
const input = test.createElement('fig-input-color', { value: '#ff0000', text: 'true' });
|
|
709
|
-
await test.waitFrames();
|
|
710
|
-
assertEqual(input.hexOpaque, '#FF0000', 'Should have correct hex');
|
|
711
|
-
});
|
|
712
|
-
|
|
713
|
-
test.it('should support alpha in hex', async () => {
|
|
714
|
-
const input = test.createElement('fig-input-color', { value: '#ff000080', text: 'true', alpha: 'true' });
|
|
715
|
-
await test.waitFrames();
|
|
716
|
-
assert(input.alpha < 100, 'Alpha should be less than 100');
|
|
717
|
-
});
|
|
718
|
-
});
|
|
719
|
-
|
|
720
|
-
// ============================================
|
|
721
|
-
// FIG-INPUT-FILL TESTS
|
|
722
|
-
// ============================================
|
|
723
|
-
test.describe('fig-input-fill', () => {
|
|
724
|
-
test.it('should parse solid fill', async () => {
|
|
725
|
-
const fill = test.createElement('fig-input-fill', {
|
|
726
|
-
value: '{"type":"solid","color":"#ff0000","opacity":100}'
|
|
727
|
-
});
|
|
728
|
-
await test.waitFrames();
|
|
729
|
-
const value = fill.value;
|
|
730
|
-
assertEqual(value.type, 'solid', 'Should be solid type');
|
|
731
|
-
assertEqual(value.color, '#ff0000', 'Should have correct color');
|
|
732
|
-
});
|
|
733
|
-
|
|
734
|
-
test.it('should parse gradient fill', async () => {
|
|
735
|
-
const fill = test.createElement('fig-input-fill', {
|
|
736
|
-
value: '{"type":"linear","angle":90,"stops":[{"color":"#ff0000","position":0},{"color":"#0000ff","position":100}]}'
|
|
737
|
-
});
|
|
738
|
-
await test.waitFrames();
|
|
739
|
-
const value = fill.value;
|
|
740
|
-
assertEqual(value.type, 'linear', 'Should be linear type');
|
|
741
|
-
});
|
|
742
|
-
});
|
|
743
|
-
|
|
744
|
-
// ============================================
|
|
745
|
-
// FIG-INPUT-JOYSTICK TESTS
|
|
746
|
-
// ============================================
|
|
747
|
-
test.describe('fig-input-joystick', () => {
|
|
748
|
-
test.it('should initialize with default position', async () => {
|
|
749
|
-
const joy = test.createElement('fig-input-joystick');
|
|
750
|
-
await test.waitFrames();
|
|
751
|
-
const value = joy.value;
|
|
752
|
-
assertEqual(value[0], 50, 'X should be 50');
|
|
753
|
-
assertEqual(value[1], 50, 'Y should be 50');
|
|
754
|
-
});
|
|
755
|
-
|
|
756
|
-
test.it('should have focusable plane', async () => {
|
|
757
|
-
const joy = test.createElement('fig-input-joystick');
|
|
758
|
-
await test.waitFrames();
|
|
759
|
-
const plane = joy.querySelector('.fig-input-joystick-plane-container');
|
|
760
|
-
assertEqual(plane.getAttribute('tabindex'), '0', 'Should be focusable');
|
|
761
|
-
});
|
|
762
|
-
});
|
|
763
|
-
|
|
764
|
-
// ============================================
|
|
765
|
-
// FIG-INPUT-ANGLE TESTS
|
|
766
|
-
// ============================================
|
|
767
|
-
test.describe('fig-input-angle', () => {
|
|
768
|
-
test.it('should initialize with value', async () => {
|
|
769
|
-
const angle = test.createElement('fig-input-angle', { value: '45' });
|
|
770
|
-
await test.waitFrames();
|
|
771
|
-
assertEqual(angle.value, 45, 'Should have correct angle');
|
|
772
|
-
});
|
|
773
|
-
|
|
774
|
-
test.it('should have value getter/setter', async () => {
|
|
775
|
-
const angle = test.createElement('fig-input-angle');
|
|
776
|
-
await test.waitFrames();
|
|
777
|
-
angle.value = 90;
|
|
778
|
-
assertEqual(angle.value, 90, 'Should update angle');
|
|
779
|
-
});
|
|
780
|
-
});
|
|
781
|
-
|
|
782
|
-
// ============================================
|
|
783
|
-
// FIG-FIELD TESTS
|
|
784
|
-
// ============================================
|
|
785
|
-
test.describe('fig-field', () => {
|
|
786
|
-
test.it('should link label to input', async () => {
|
|
787
|
-
const field = test.createElement('fig-field');
|
|
788
|
-
field.innerHTML = '<label>Name</label><fig-input-text></fig-input-text>';
|
|
789
|
-
await test.waitFrames();
|
|
790
|
-
const label = field.querySelector('label');
|
|
791
|
-
const input = field.querySelector('fig-input-text');
|
|
792
|
-
assertEqual(label.getAttribute('for'), input.getAttribute('id'), 'Label should be linked to input');
|
|
793
|
-
});
|
|
794
|
-
});
|
|
795
|
-
|
|
796
|
-
// ============================================
|
|
797
|
-
// FIG-TOAST TESTS
|
|
798
|
-
// ============================================
|
|
799
|
-
test.describe('fig-toast', () => {
|
|
800
|
-
test.it('should have duration attribute', async () => {
|
|
801
|
-
const toast = test.createElement('fig-toast', { duration: '3000' });
|
|
802
|
-
toast.textContent = 'Test toast';
|
|
803
|
-
await test.waitFrames();
|
|
804
|
-
assertEqual(toast.getAttribute('duration'), '3000', 'Should have duration');
|
|
805
|
-
});
|
|
806
|
-
});
|
|
807
|
-
|
|
808
|
-
// ============================================
|
|
809
|
-
// FIG-FILL-PICKER TESTS
|
|
810
|
-
// ============================================
|
|
811
|
-
test.describe('fig-fill-picker', () => {
|
|
812
|
-
test.it('should initialize with solid fill', async () => {
|
|
813
|
-
const picker = test.createElement('fig-fill-picker', {
|
|
814
|
-
value: '{"type":"solid","color":"#ff0000","opacity":100}'
|
|
815
|
-
});
|
|
816
|
-
await test.waitFrames();
|
|
817
|
-
const value = picker.value;
|
|
818
|
-
assertEqual(value.type, 'solid', 'Should be solid type');
|
|
819
|
-
});
|
|
820
|
-
|
|
821
|
-
test.it('should support mode attribute', async () => {
|
|
822
|
-
const picker = test.createElement('fig-fill-picker', { mode: 'solid' });
|
|
823
|
-
await test.waitFrames();
|
|
824
|
-
assertEqual(picker.getAttribute('mode'), 'solid', 'Should have mode attribute');
|
|
825
|
-
});
|
|
826
|
-
});
|
|
827
|
-
|
|
828
|
-
// Run tests on button click
|
|
829
|
-
document.getElementById('run-tests').addEventListener('click', async (e) => {
|
|
830
|
-
e.target.disabled = true;
|
|
831
|
-
e.target.textContent = 'Running...';
|
|
832
|
-
await test.run();
|
|
833
|
-
e.target.disabled = false;
|
|
834
|
-
e.target.textContent = 'Run Tests';
|
|
835
|
-
});
|
|
836
|
-
|
|
837
|
-
// Auto-run on load
|
|
838
|
-
window.addEventListener('load', () => {
|
|
839
|
-
setTimeout(() => test.run(), 100);
|
|
840
|
-
});
|
|
841
|
-
</script>
|
|
842
|
-
</body>
|
|
843
|
-
</html>
|