cyclecad 0.2.2 → 0.2.3
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/API-BUILD-MANIFEST.txt +339 -0
- package/API-SERVER.md +535 -0
- package/Architecture-Deck.pptx +0 -0
- package/CLAUDE.md +172 -11
- package/CLI-BUILD-SUMMARY.md +504 -0
- package/CLI-INDEX.md +356 -0
- package/CLI-README.md +466 -0
- package/COLLABORATION-INTEGRATION-GUIDE.md +325 -0
- package/CONNECTED_FABS_GUIDE.md +612 -0
- package/CONNECTED_FABS_README.md +310 -0
- package/DELIVERABLES.md +343 -0
- package/DFM-ANALYZER-INTEGRATION.md +368 -0
- package/DFM-QUICK-START.js +253 -0
- package/Dockerfile +69 -0
- package/IMPLEMENTATION.md +327 -0
- package/LICENSE +31 -0
- package/MARKETPLACE_QUICK_REFERENCE.txt +294 -0
- package/MCP-INDEX.md +264 -0
- package/QUICKSTART-API.md +388 -0
- package/QUICKSTART-CLI.md +211 -0
- package/QUICKSTART-MCP.md +196 -0
- package/README-MCP.md +208 -0
- package/TEST-TOKEN-ENGINE.md +319 -0
- package/TOKEN-ENGINE-SUMMARY.md +266 -0
- package/TOKENS-README.md +263 -0
- package/TOOLS-REFERENCE.md +254 -0
- package/app/index.html +168 -3
- package/app/js/TOKEN-INTEGRATION.md +391 -0
- package/app/js/agent-api.js +3 -3
- package/app/js/ai-copilot.js +1435 -0
- package/app/js/cam-pipeline.js +840 -0
- package/app/js/collaboration-ui.js +995 -0
- package/app/js/collaboration.js +1116 -0
- package/app/js/connected-fabs-example.js +404 -0
- package/app/js/connected-fabs.js +1449 -0
- package/app/js/dfm-analyzer.js +1760 -0
- package/app/js/marketplace.js +1994 -0
- package/app/js/material-library.js +2115 -0
- package/app/js/token-dashboard.js +563 -0
- package/app/js/token-engine.js +743 -0
- package/app/test-agent.html +1801 -0
- package/bin/cyclecad-cli.js +662 -0
- package/bin/cyclecad-mcp +2 -0
- package/bin/server.js +242 -0
- package/cycleCAD-Architecture.pptx +0 -0
- package/cycleCAD-Investor-Deck.pptx +0 -0
- package/demo-mcp.sh +60 -0
- package/docs/API-SERVER-SUMMARY.md +375 -0
- package/docs/API-SERVER.md +667 -0
- package/docs/CAM-EXAMPLES.md +344 -0
- package/docs/CAM-INTEGRATION.md +612 -0
- package/docs/CAM-QUICK-REFERENCE.md +199 -0
- package/docs/CLI-INTEGRATION.md +510 -0
- package/docs/CLI.md +872 -0
- package/docs/MARKETPLACE-API-SCHEMA.json +564 -0
- package/docs/MARKETPLACE-INTEGRATION.md +467 -0
- package/docs/MARKETPLACE-SETUP.html +439 -0
- package/docs/MCP-SERVER.md +403 -0
- package/examples/api-client-example.js +488 -0
- package/examples/api-client-example.py +359 -0
- package/examples/batch-manufacturing.txt +28 -0
- package/examples/batch-simple.txt +26 -0
- package/model-marketplace.html +1273 -0
- package/package.json +14 -3
- package/server/api-server.js +1120 -0
- package/server/mcp-server.js +1161 -0
- package/test-api-server.js +432 -0
- package/test-mcp.js +198 -0
- package/~$cycleCAD-Investor-Deck.pptx +0 -0
|
@@ -0,0 +1,1801 @@
|
|
|
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>cycleCAD Visual Test Agent</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
body {
|
|
15
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
16
|
+
background: #1e1e1e;
|
|
17
|
+
color: #d4d4d4;
|
|
18
|
+
height: 100vh;
|
|
19
|
+
overflow: hidden;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#top-bar {
|
|
23
|
+
background: #252526;
|
|
24
|
+
border-bottom: 1px solid #3e3e42;
|
|
25
|
+
padding: 12px 16px;
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
justify-content: space-between;
|
|
29
|
+
height: 60px;
|
|
30
|
+
z-index: 1000;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#progress-section {
|
|
34
|
+
display: flex;
|
|
35
|
+
align-items: center;
|
|
36
|
+
gap: 12px;
|
|
37
|
+
flex: 1;
|
|
38
|
+
max-width: 400px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.progress-bar {
|
|
42
|
+
flex: 1;
|
|
43
|
+
height: 20px;
|
|
44
|
+
background: #3e3e42;
|
|
45
|
+
border-radius: 3px;
|
|
46
|
+
overflow: hidden;
|
|
47
|
+
position: relative;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.progress-fill {
|
|
51
|
+
height: 100%;
|
|
52
|
+
background: linear-gradient(90deg, #0284C7, #06B6D4);
|
|
53
|
+
width: 0%;
|
|
54
|
+
transition: width 0.2s ease;
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
justify-content: center;
|
|
58
|
+
font-size: 11px;
|
|
59
|
+
font-weight: bold;
|
|
60
|
+
color: white;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.progress-text {
|
|
64
|
+
font-size: 12px;
|
|
65
|
+
color: #858585;
|
|
66
|
+
min-width: 80px;
|
|
67
|
+
text-align: right;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#controls {
|
|
71
|
+
display: flex;
|
|
72
|
+
gap: 8px;
|
|
73
|
+
margin-left: 16px;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
button {
|
|
77
|
+
background: #0284C7;
|
|
78
|
+
color: white;
|
|
79
|
+
border: none;
|
|
80
|
+
padding: 8px 16px;
|
|
81
|
+
border-radius: 4px;
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
font-size: 12px;
|
|
84
|
+
font-weight: 500;
|
|
85
|
+
transition: all 0.2s ease;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
button:hover {
|
|
89
|
+
background: #0369A1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
button:active {
|
|
93
|
+
transform: scale(0.98);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
button:disabled {
|
|
97
|
+
background: #3e3e42;
|
|
98
|
+
color: #858585;
|
|
99
|
+
cursor: not-allowed;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.btn-secondary {
|
|
103
|
+
background: #3e3e42;
|
|
104
|
+
color: #d4d4d4;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.btn-secondary:hover {
|
|
108
|
+
background: #505052;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#main-container {
|
|
112
|
+
display: flex;
|
|
113
|
+
height: calc(100vh - 60px);
|
|
114
|
+
overflow: hidden;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
#iframe-section {
|
|
118
|
+
width: 65%;
|
|
119
|
+
border-right: 1px solid #3e3e42;
|
|
120
|
+
overflow: hidden;
|
|
121
|
+
position: relative;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
#cad-iframe {
|
|
125
|
+
width: 100%;
|
|
126
|
+
height: 100%;
|
|
127
|
+
border: none;
|
|
128
|
+
display: block;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
#log-section {
|
|
132
|
+
width: 35%;
|
|
133
|
+
display: flex;
|
|
134
|
+
flex-direction: column;
|
|
135
|
+
overflow: hidden;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#category-list {
|
|
139
|
+
flex: 1;
|
|
140
|
+
overflow-y: auto;
|
|
141
|
+
padding: 0;
|
|
142
|
+
border-top: 1px solid #3e3e42;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.category {
|
|
146
|
+
border-bottom: 1px solid #3e3e42;
|
|
147
|
+
margin: 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.category-header {
|
|
151
|
+
background: #252526;
|
|
152
|
+
padding: 12px 16px;
|
|
153
|
+
cursor: pointer;
|
|
154
|
+
user-select: none;
|
|
155
|
+
display: flex;
|
|
156
|
+
align-items: center;
|
|
157
|
+
justify-content: space-between;
|
|
158
|
+
gap: 8px;
|
|
159
|
+
transition: background 0.15s ease;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.category-header:hover {
|
|
163
|
+
background: #2d2d30;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.category-header.collapsed .category-title::after {
|
|
167
|
+
content: ' ▶';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.category-header:not(.collapsed) .category-title::after {
|
|
171
|
+
content: ' ▼';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.category-title {
|
|
175
|
+
font-weight: 600;
|
|
176
|
+
font-size: 13px;
|
|
177
|
+
flex: 1;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.category-stats {
|
|
181
|
+
font-size: 11px;
|
|
182
|
+
color: #858585;
|
|
183
|
+
padding: 4px 8px;
|
|
184
|
+
background: #1e1e1e;
|
|
185
|
+
border-radius: 3px;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.category-body {
|
|
189
|
+
padding: 8px 0;
|
|
190
|
+
max-height: 500px;
|
|
191
|
+
overflow-y: auto;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.category.collapsed .category-body {
|
|
195
|
+
display: none;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.test-entry {
|
|
199
|
+
padding: 8px 16px;
|
|
200
|
+
font-size: 12px;
|
|
201
|
+
font-family: 'Consolas', 'Monaco', monospace;
|
|
202
|
+
border-left: 3px solid #3e3e42;
|
|
203
|
+
margin: 2px 4px;
|
|
204
|
+
border-radius: 2px;
|
|
205
|
+
display: flex;
|
|
206
|
+
align-items: center;
|
|
207
|
+
gap: 8px;
|
|
208
|
+
transition: all 0.15s ease;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.test-entry.running {
|
|
212
|
+
border-left-color: #0284C7;
|
|
213
|
+
background: rgba(2, 132, 199, 0.1);
|
|
214
|
+
color: #0284C7;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.test-entry.pass {
|
|
218
|
+
border-left-color: #16a34a;
|
|
219
|
+
background: rgba(22, 163, 74, 0.1);
|
|
220
|
+
color: #86efac;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.test-entry.fail {
|
|
224
|
+
border-left-color: #dc2626;
|
|
225
|
+
background: rgba(220, 38, 38, 0.1);
|
|
226
|
+
color: #fca5a5;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.test-entry.skip {
|
|
230
|
+
border-left-color: #6b7280;
|
|
231
|
+
background: rgba(107, 114, 128, 0.1);
|
|
232
|
+
color: #9ca3af;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.test-indicator {
|
|
236
|
+
width: 8px;
|
|
237
|
+
height: 8px;
|
|
238
|
+
border-radius: 50%;
|
|
239
|
+
flex-shrink: 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.test-entry.running .test-indicator {
|
|
243
|
+
background: #0284C7;
|
|
244
|
+
animation: pulse 1s infinite;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.test-entry.pass .test-indicator {
|
|
248
|
+
background: #16a34a;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.test-entry.fail .test-indicator {
|
|
252
|
+
background: #dc2626;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.test-entry.skip .test-indicator {
|
|
256
|
+
background: #6b7280;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
@keyframes pulse {
|
|
260
|
+
0%, 100% { opacity: 1; }
|
|
261
|
+
50% { opacity: 0.5; }
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.test-name {
|
|
265
|
+
flex: 1;
|
|
266
|
+
overflow: hidden;
|
|
267
|
+
text-overflow: ellipsis;
|
|
268
|
+
white-space: nowrap;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.test-detail {
|
|
272
|
+
font-size: 10px;
|
|
273
|
+
color: inherit;
|
|
274
|
+
opacity: 0.7;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
#flash-overlay {
|
|
278
|
+
position: absolute;
|
|
279
|
+
pointer-events: none;
|
|
280
|
+
z-index: 999;
|
|
281
|
+
border: 3px solid;
|
|
282
|
+
border-radius: 4px;
|
|
283
|
+
animation: flash-fade 0.6s ease-out forwards;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
@keyframes flash-fade {
|
|
287
|
+
0% {
|
|
288
|
+
opacity: 1;
|
|
289
|
+
transform: scale(1);
|
|
290
|
+
}
|
|
291
|
+
100% {
|
|
292
|
+
opacity: 0;
|
|
293
|
+
transform: scale(1.05);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.category-run-btn {
|
|
298
|
+
padding: 4px 8px;
|
|
299
|
+
font-size: 11px;
|
|
300
|
+
background: #0284C7;
|
|
301
|
+
color: white;
|
|
302
|
+
border: none;
|
|
303
|
+
border-radius: 3px;
|
|
304
|
+
cursor: pointer;
|
|
305
|
+
transition: all 0.2s ease;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.category-run-btn:hover {
|
|
309
|
+
background: #0369A1;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.category-run-btn:disabled {
|
|
313
|
+
background: #3e3e42;
|
|
314
|
+
color: #858585;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
#summary-bar {
|
|
318
|
+
background: #252526;
|
|
319
|
+
border-top: 1px solid #3e3e42;
|
|
320
|
+
padding: 8px 16px;
|
|
321
|
+
font-size: 11px;
|
|
322
|
+
color: #858585;
|
|
323
|
+
display: flex;
|
|
324
|
+
justify-content: space-between;
|
|
325
|
+
align-items: center;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.summary-item {
|
|
329
|
+
display: flex;
|
|
330
|
+
align-items: center;
|
|
331
|
+
gap: 6px;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.summary-indicator {
|
|
335
|
+
width: 6px;
|
|
336
|
+
height: 6px;
|
|
337
|
+
border-radius: 50%;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.summary-indicator.pass {
|
|
341
|
+
background: #16a34a;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.summary-indicator.fail {
|
|
345
|
+
background: #dc2626;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.summary-indicator.skip {
|
|
349
|
+
background: #6b7280;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
::-webkit-scrollbar {
|
|
353
|
+
width: 10px;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
::-webkit-scrollbar-track {
|
|
357
|
+
background: #1e1e1e;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
::-webkit-scrollbar-thumb {
|
|
361
|
+
background: #404040;
|
|
362
|
+
border-radius: 5px;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
::-webkit-scrollbar-thumb:hover {
|
|
366
|
+
background: #505050;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.status-label {
|
|
370
|
+
display: inline-block;
|
|
371
|
+
padding: 2px 6px;
|
|
372
|
+
border-radius: 2px;
|
|
373
|
+
font-size: 10px;
|
|
374
|
+
font-weight: bold;
|
|
375
|
+
margin-left: 4px;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.status-label.pass {
|
|
379
|
+
background: rgba(22, 163, 74, 0.3);
|
|
380
|
+
color: #86efac;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.status-label.fail {
|
|
384
|
+
background: rgba(220, 38, 38, 0.3);
|
|
385
|
+
color: #fca5a5;
|
|
386
|
+
}
|
|
387
|
+
</style>
|
|
388
|
+
</head>
|
|
389
|
+
<body>
|
|
390
|
+
<div id="top-bar">
|
|
391
|
+
<div id="progress-section">
|
|
392
|
+
<div class="progress-bar">
|
|
393
|
+
<div class="progress-fill">
|
|
394
|
+
<span id="progress-pct">0%</span>
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
397
|
+
<div class="progress-text">
|
|
398
|
+
<span id="progress-count">0/0</span>
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
<div id="controls">
|
|
402
|
+
<button id="btn-run-all">▶ Run All Tests</button>
|
|
403
|
+
<button id="btn-stop" class="btn-secondary" disabled>⏹ Stop</button>
|
|
404
|
+
<button id="btn-clear-log" class="btn-secondary">🗑 Clear Log</button>
|
|
405
|
+
<button id="btn-export-report" class="btn-secondary">💾 Export</button>
|
|
406
|
+
</div>
|
|
407
|
+
</div>
|
|
408
|
+
|
|
409
|
+
<div id="main-container">
|
|
410
|
+
<div id="iframe-section">
|
|
411
|
+
<iframe id="cad-iframe" src="./index.html"></iframe>
|
|
412
|
+
</div>
|
|
413
|
+
|
|
414
|
+
<div id="log-section">
|
|
415
|
+
<div id="category-list"></div>
|
|
416
|
+
<div id="summary-bar">
|
|
417
|
+
<div id="summary-stats">
|
|
418
|
+
<div class="summary-item">
|
|
419
|
+
<div class="summary-indicator pass"></div>
|
|
420
|
+
<span><span id="stat-pass">0</span> Passed</span>
|
|
421
|
+
</div>
|
|
422
|
+
<div class="summary-item" style="margin-left: 16px;">
|
|
423
|
+
<div class="summary-indicator fail"></div>
|
|
424
|
+
<span><span id="stat-fail">0</span> Failed</span>
|
|
425
|
+
</div>
|
|
426
|
+
<div class="summary-item" style="margin-left: 16px;">
|
|
427
|
+
<div class="summary-indicator skip"></div>
|
|
428
|
+
<span><span id="stat-skip">0</span> Skipped</span>
|
|
429
|
+
</div>
|
|
430
|
+
</div>
|
|
431
|
+
<div id="status-text" style="color: #0284C7;"></div>
|
|
432
|
+
</div>
|
|
433
|
+
</div>
|
|
434
|
+
</div>
|
|
435
|
+
|
|
436
|
+
<script>
|
|
437
|
+
// ============================================================================
|
|
438
|
+
// TEST FRAMEWORK
|
|
439
|
+
// ============================================================================
|
|
440
|
+
|
|
441
|
+
const TEST_CATEGORIES = [
|
|
442
|
+
{
|
|
443
|
+
name: 'Splash Screen',
|
|
444
|
+
tests: [
|
|
445
|
+
{ name: 'Splash screen visible on load', fn: testSplashVisible },
|
|
446
|
+
{ name: 'Click "Empty Project" to start', fn: testSplashEmpty },
|
|
447
|
+
{ name: 'Toolbar is visible', fn: testToolbarVisible },
|
|
448
|
+
{ name: 'Left panel is visible', fn: testLeftPanelVisible },
|
|
449
|
+
{ name: 'Right panel is visible', fn: testRightPanelVisible },
|
|
450
|
+
]
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
name: 'Sketch Tools',
|
|
454
|
+
tests: [
|
|
455
|
+
{ name: 'Click tool-sketch → sketch mode activates', fn: testSketchMode },
|
|
456
|
+
{ name: 'Click tool-line → line tool active', fn: testLineTool },
|
|
457
|
+
{ name: 'Click tool-rect → rect tool active', fn: testRectTool },
|
|
458
|
+
{ name: 'Click tool-circle → circle tool active', fn: testCircleTool },
|
|
459
|
+
{ name: 'Click tool-arc → arc tool active', fn: testArcTool },
|
|
460
|
+
{ name: 'Click tool-dimension → dimension tool active', fn: testDimensionTool },
|
|
461
|
+
]
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
name: '3D Operations',
|
|
465
|
+
tests: [
|
|
466
|
+
{ name: 'Click tool-extrude → extrude dialog shows', fn: testExtrudeTool },
|
|
467
|
+
{ name: 'Click tool-revolve → revolve dialog shows', fn: testRevolveTool },
|
|
468
|
+
{ name: 'Click tool-fillet → fillet dialog shows', fn: testFilletTool },
|
|
469
|
+
{ name: 'Click tool-chamfer → chamfer dialog shows', fn: testChamferTool },
|
|
470
|
+
{ name: 'Click tool-cut → cut mode activates', fn: testCutTool },
|
|
471
|
+
{ name: 'Click tool-union → union mode activates', fn: testUnionTool },
|
|
472
|
+
]
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
name: 'Advanced Operations',
|
|
476
|
+
tests: [
|
|
477
|
+
{ name: 'Click tool-sweep → sweep dialog shows', fn: testSweepTool },
|
|
478
|
+
{ name: 'Click tool-loft → loft dialog shows', fn: testLoftTool },
|
|
479
|
+
{ name: 'Click tool-spring → spring generator works', fn: testSpringTool },
|
|
480
|
+
{ name: 'Click tool-thread → thread generator works', fn: testThreadTool },
|
|
481
|
+
]
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
name: 'Sheet Metal',
|
|
485
|
+
tests: [
|
|
486
|
+
{ name: 'Click tool-bend → bend dialog shows', fn: testBendTool },
|
|
487
|
+
{ name: 'Click tool-flange → flange dialog shows', fn: testFlangeTool },
|
|
488
|
+
{ name: 'Click tool-unfold → unfold action available', fn: testUnfoldTool },
|
|
489
|
+
]
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
name: 'Left Panel Tabs',
|
|
493
|
+
tests: [
|
|
494
|
+
{ name: 'Click "Model Tree" tab → tree visible', fn: testLeftTabTree },
|
|
495
|
+
{ name: 'Click "Project Browser" tab → project browser visible', fn: testLeftTabProject },
|
|
496
|
+
{ name: 'Tab switching hides other content', fn: testLeftTabSwitch },
|
|
497
|
+
{ name: 'Feature tree loads or shows empty state', fn: testTreeContent },
|
|
498
|
+
]
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
name: 'Right Panel Tabs',
|
|
502
|
+
tests: [
|
|
503
|
+
{ name: 'Click Properties tab → content shows', fn: testRightTabProperties },
|
|
504
|
+
{ name: 'Click Chat tab → chat interface shows', fn: testRightTabChat },
|
|
505
|
+
{ name: 'Click Guide tab → guide content shows', fn: testRightTabGuide },
|
|
506
|
+
{ name: 'Tab switching works', fn: testRightTabSwitch },
|
|
507
|
+
]
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
name: 'Keyboard Shortcuts',
|
|
511
|
+
tests: [
|
|
512
|
+
{ name: 'Press S → sketch mode', fn: testKeyS },
|
|
513
|
+
{ name: 'Press L → line tool', fn: testKeyL },
|
|
514
|
+
{ name: 'Press R → rect tool', fn: testKeyR },
|
|
515
|
+
{ name: 'Press E → extrude mode', fn: testKeyE },
|
|
516
|
+
{ name: 'Press 1 → front view', fn: testKey1 },
|
|
517
|
+
{ name: 'Press 2 → back view', fn: testKey2 },
|
|
518
|
+
{ name: 'Press 3 → right view', fn: testKey3 },
|
|
519
|
+
{ name: 'Press 4 → left view', fn: testKey4 },
|
|
520
|
+
{ name: 'Press 5 → top view', fn: testKey5 },
|
|
521
|
+
{ name: 'Press 6 → bottom view', fn: testKey6 },
|
|
522
|
+
{ name: 'Press 7 → iso view', fn: testKey7 },
|
|
523
|
+
{ name: 'Press G → grid toggle', fn: testKeyG },
|
|
524
|
+
{ name: 'Press W → wireframe toggle', fn: testKeyW },
|
|
525
|
+
{ name: 'Press Escape → deselect', fn: testKeyEscape },
|
|
526
|
+
{ name: 'Press ? → help shortcuts', fn: testKeyQuestion },
|
|
527
|
+
]
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
name: 'View Controls',
|
|
531
|
+
tests: [
|
|
532
|
+
{ name: 'Undo button (btn-undo) works', fn: testUndoBtn },
|
|
533
|
+
{ name: 'Redo button (btn-redo) works', fn: testRedoBtn },
|
|
534
|
+
{ name: 'Viewport canvas exists and has WebGL context', fn: testViewportCanvas },
|
|
535
|
+
{ name: 'OrbitControls respond to mouse', fn: testOrbitControls },
|
|
536
|
+
{ name: 'Grid is visible by default', fn: testGridVisible },
|
|
537
|
+
{ name: 'Wireframe toggle works', fn: testWireframeToggle },
|
|
538
|
+
{ name: 'Camera updates on view change', fn: testCameraUpdate },
|
|
539
|
+
]
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
name: 'Agent API',
|
|
543
|
+
tests: [
|
|
544
|
+
{ name: 'meta.ping() responds', fn: testAgentMetaPing },
|
|
545
|
+
{ name: 'meta.version() returns version', fn: testAgentMetaVersion },
|
|
546
|
+
{ name: 'meta.schema() returns full API schema', fn: testAgentMetaSchema },
|
|
547
|
+
{ name: 'sketch.start({plane:"XY"}) works', fn: testAgentSketchStart },
|
|
548
|
+
{ name: 'sketch.rect({width:50, height:30}) works', fn: testAgentSketchRect },
|
|
549
|
+
{ name: 'sketch.end() works', fn: testAgentSketchEnd },
|
|
550
|
+
{ name: 'ops.primitive({shape:"box",...}) works', fn: testAgentPrimitiveBox },
|
|
551
|
+
{ name: 'ops.primitive({shape:"cylinder",...}) works', fn: testAgentPrimitiveCyl },
|
|
552
|
+
{ name: 'query.features() returns features list', fn: testAgentQueryFeatures },
|
|
553
|
+
{ name: 'view.set({view:"front"}) works', fn: testAgentViewSet },
|
|
554
|
+
{ name: 'export.json() works', fn: testAgentExportJSON },
|
|
555
|
+
{ name: 'scene.clear() works', fn: testAgentSceneClear },
|
|
556
|
+
]
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
name: 'Token Engine',
|
|
560
|
+
tests: [
|
|
561
|
+
{ name: 'window.cycleCAD.tokens exists', fn: testTokensExists },
|
|
562
|
+
{ name: 'getBalance() returns number', fn: testTokensGetBalance },
|
|
563
|
+
{ name: 'addTokens(100, "test") increases balance', fn: testTokensAdd },
|
|
564
|
+
{ name: 'spendTokens(10, "test") decreases balance', fn: testTokensSpend },
|
|
565
|
+
{ name: 'getTransactionHistory() returns array', fn: testTokensHistory },
|
|
566
|
+
{ name: 'Tier info available', fn: testTokensTier },
|
|
567
|
+
{ name: 'Pricing table exists', fn: testTokensPricing },
|
|
568
|
+
{ name: 'Token balance button updates', fn: testTokensDisplay },
|
|
569
|
+
]
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
name: 'Import/Export',
|
|
573
|
+
tests: [
|
|
574
|
+
{ name: 'STL import UI exists (tool-reverse-engineer)', fn: testReverseEngineer },
|
|
575
|
+
{ name: 'Inventor import UI exists', fn: testInventorImport },
|
|
576
|
+
{ name: 'Export buttons available', fn: testExportButtons },
|
|
577
|
+
{ name: 'JSON save/load cycle works', fn: testSaveLoadCycle },
|
|
578
|
+
]
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
name: 'Dialog Controls',
|
|
582
|
+
tests: [
|
|
583
|
+
{ name: 'Revolve dialog has OK/Cancel buttons', fn: testDialogRevolve },
|
|
584
|
+
{ name: 'Fillet dialog has OK/Cancel buttons', fn: testDialogFillet },
|
|
585
|
+
{ name: 'Chamfer dialog has OK/Cancel buttons', fn: testDialogChamfer },
|
|
586
|
+
{ name: 'Boolean dialog has OK/Cancel buttons', fn: testDialogBoolean },
|
|
587
|
+
{ name: 'Shell dialog has OK/Cancel buttons', fn: testDialogShell },
|
|
588
|
+
{ name: 'Pattern dialog has OK/Cancel buttons', fn: testDialogPattern },
|
|
589
|
+
{ name: 'Sweep dialog has OK/Cancel buttons', fn: testDialogSweep },
|
|
590
|
+
{ name: 'Loft dialog has OK/Cancel buttons', fn: testDialogLoft },
|
|
591
|
+
{ name: 'Bend dialog has OK/Cancel buttons', fn: testDialogBend },
|
|
592
|
+
{ name: 'Flange dialog has OK/Cancel buttons', fn: testDialogFlange },
|
|
593
|
+
]
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
name: 'Status Bar & Overlays',
|
|
597
|
+
tests: [
|
|
598
|
+
{ name: 'Status bar exists and shows text', fn: testStatusBar },
|
|
599
|
+
{ name: 'Mode indicator updates on tool change', fn: testModeIndicator },
|
|
600
|
+
{ name: 'Snap indicator shows in sketch mode', fn: testSnapIndicator },
|
|
601
|
+
]
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
name: 'AI Copilot',
|
|
605
|
+
tests: [
|
|
606
|
+
{ name: 'Copilot toolbar button exists', fn: async () => { const btn = appDoc.getElementById('btn-ai-copilot'); if (!btn) throw new Error('btn-ai-copilot missing'); flashElement(btn); return 'Copilot button found'; } },
|
|
607
|
+
{ name: 'Click Copilot → panel opens', fn: async () => { const btn = appDoc.getElementById('btn-ai-copilot'); btn?.click(); await sleep(500); const panel = appDoc.getElementById('copilot-panel'); if (!panel || panel.style.display === 'none') throw new Error('Copilot panel not visible'); return 'Copilot panel opened'; } },
|
|
608
|
+
{ name: 'window.cycleCAD.copilot exists', fn: async () => { const mod = appWindow?.cycleCAD?.copilot; if (!mod) throw new Error('copilot module not loaded'); return 'Copilot module loaded'; } },
|
|
609
|
+
{ name: 'Close Copilot panel', fn: async () => { const panel = appDoc.getElementById('copilot-panel'); if (panel) panel.style.display = 'none'; return 'Panel closed'; } },
|
|
610
|
+
]
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
name: 'DFM Analyzer',
|
|
614
|
+
tests: [
|
|
615
|
+
{ name: 'DFM toolbar button exists', fn: async () => { const btn = appDoc.getElementById('btn-dfm'); if (!btn) throw new Error('btn-dfm missing'); flashElement(btn); return 'DFM button found'; } },
|
|
616
|
+
{ name: 'Click DFM → panel opens', fn: async () => { const btn = appDoc.getElementById('btn-dfm'); btn?.click(); await sleep(500); const panel = appDoc.getElementById('dfm-panel'); if (!panel || panel.style.display === 'none') throw new Error('DFM panel not visible'); return 'DFM panel opened'; } },
|
|
617
|
+
{ name: 'window.cycleCAD.dfm exists', fn: async () => { const mod = appWindow?.cycleCAD?.dfm; if (!mod) throw new Error('dfm module not loaded'); return 'DFM module loaded'; } },
|
|
618
|
+
{ name: 'Close DFM panel', fn: async () => { const panel = appDoc.getElementById('dfm-panel'); if (panel) panel.style.display = 'none'; return 'Panel closed'; } },
|
|
619
|
+
]
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
name: 'Material Library',
|
|
623
|
+
tests: [
|
|
624
|
+
{ name: 'Materials toolbar button exists', fn: async () => { const btn = appDoc.getElementById('btn-materials'); if (!btn) throw new Error('btn-materials missing'); flashElement(btn); return 'Materials button found'; } },
|
|
625
|
+
{ name: 'Click Materials → panel opens', fn: async () => { const btn = appDoc.getElementById('btn-materials'); btn?.click(); await sleep(500); const panel = appDoc.getElementById('materials-panel'); if (!panel || panel.style.display === 'none') throw new Error('Materials panel not visible'); return 'Materials panel opened'; } },
|
|
626
|
+
{ name: 'window.cycleCAD.materials exists', fn: async () => { const mod = appWindow?.cycleCAD?.materials; if (!mod) throw new Error('materials module not loaded'); return 'Materials module loaded'; } },
|
|
627
|
+
{ name: 'Close Materials panel', fn: async () => { const panel = appDoc.getElementById('materials-panel'); if (panel) panel.style.display = 'none'; return 'Panel closed'; } },
|
|
628
|
+
]
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
name: 'CAM Pipeline',
|
|
632
|
+
tests: [
|
|
633
|
+
{ name: 'CAM toolbar button exists', fn: async () => { const btn = appDoc.getElementById('btn-cam'); if (!btn) throw new Error('btn-cam missing'); flashElement(btn); return 'CAM button found'; } },
|
|
634
|
+
{ name: 'Click CAM → panel opens', fn: async () => { const btn = appDoc.getElementById('btn-cam'); btn?.click(); await sleep(500); const panel = appDoc.getElementById('cam-panel'); if (!panel || panel.style.display === 'none') throw new Error('CAM panel not visible'); return 'CAM panel opened'; } },
|
|
635
|
+
{ name: 'window.cycleCAD.cam exists', fn: async () => { const mod = appWindow?.cycleCAD?.cam; if (!mod) throw new Error('cam module not loaded'); return 'CAM module loaded'; } },
|
|
636
|
+
{ name: 'Close CAM panel', fn: async () => { const panel = appDoc.getElementById('cam-panel'); if (panel) panel.style.display = 'none'; return 'Panel closed'; } },
|
|
637
|
+
]
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
name: 'Connected Fabs',
|
|
641
|
+
tests: [
|
|
642
|
+
{ name: 'Fabs toolbar button exists', fn: async () => { const btn = appDoc.getElementById('btn-fabs'); if (!btn) throw new Error('btn-fabs missing'); flashElement(btn); return 'Fabs button found'; } },
|
|
643
|
+
{ name: 'Click Fabs → panel opens', fn: async () => { const btn = appDoc.getElementById('btn-fabs'); btn?.click(); await sleep(500); const panel = appDoc.getElementById('fabs-panel'); if (!panel || panel.style.display === 'none') throw new Error('Fabs panel not visible'); return 'Fabs panel opened'; } },
|
|
644
|
+
{ name: 'window.cycleCAD.fabs exists', fn: async () => { const mod = appWindow?.cycleCAD?.fabs; if (!mod) throw new Error('fabs module not loaded'); return 'Fabs module loaded'; } },
|
|
645
|
+
{ name: 'Close Fabs panel', fn: async () => { const panel = appDoc.getElementById('fabs-panel'); if (panel) panel.style.display = 'none'; return 'Panel closed'; } },
|
|
646
|
+
]
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
name: 'Collaboration',
|
|
650
|
+
tests: [
|
|
651
|
+
{ name: 'Collab toolbar button exists', fn: async () => { const btn = appDoc.getElementById('btn-collab'); if (!btn) throw new Error('btn-collab missing'); flashElement(btn); return 'Collab button found'; } },
|
|
652
|
+
{ name: 'Click Collab → panel opens', fn: async () => { const btn = appDoc.getElementById('btn-collab'); btn?.click(); await sleep(500); const panel = appDoc.getElementById('collab-panel'); if (!panel || panel.style.display === 'none') throw new Error('Collab panel not visible'); return 'Collab panel opened'; } },
|
|
653
|
+
{ name: 'window.cycleCAD.collab exists', fn: async () => { const mod = appWindow?.cycleCAD?.collab; if (!mod) throw new Error('collab module not loaded'); return 'Collab module loaded'; } },
|
|
654
|
+
{ name: 'Close Collab panel', fn: async () => { const panel = appDoc.getElementById('collab-panel'); if (panel) panel.style.display = 'none'; return 'Panel closed'; } },
|
|
655
|
+
]
|
|
656
|
+
},
|
|
657
|
+
];
|
|
658
|
+
|
|
659
|
+
// ============================================================================
|
|
660
|
+
// STATE & GLOBALS
|
|
661
|
+
// ============================================================================
|
|
662
|
+
|
|
663
|
+
let testState = {
|
|
664
|
+
isRunning: false,
|
|
665
|
+
isPaused: false,
|
|
666
|
+
currentCategoryIdx: -1,
|
|
667
|
+
currentTestIdx: -1,
|
|
668
|
+
results: {},
|
|
669
|
+
stats: { pass: 0, fail: 0, skip: 0 },
|
|
670
|
+
startTime: null,
|
|
671
|
+
stopRequested: false,
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
let appWindow = null;
|
|
675
|
+
let appDoc = null;
|
|
676
|
+
|
|
677
|
+
const TEST_TIMEOUT = 5000;
|
|
678
|
+
const TEST_DELAY = 300;
|
|
679
|
+
|
|
680
|
+
// ============================================================================
|
|
681
|
+
// UI & DOM RENDERING
|
|
682
|
+
// ============================================================================
|
|
683
|
+
|
|
684
|
+
function renderCategories() {
|
|
685
|
+
const list = document.getElementById('category-list');
|
|
686
|
+
list.innerHTML = '';
|
|
687
|
+
|
|
688
|
+
TEST_CATEGORIES.forEach((cat, idx) => {
|
|
689
|
+
const catEl = document.createElement('div');
|
|
690
|
+
catEl.className = 'category collapsed';
|
|
691
|
+
catEl.dataset.idx = idx;
|
|
692
|
+
|
|
693
|
+
const header = document.createElement('div');
|
|
694
|
+
header.className = 'category-header';
|
|
695
|
+
|
|
696
|
+
const title = document.createElement('span');
|
|
697
|
+
title.className = 'category-title';
|
|
698
|
+
title.textContent = cat.name;
|
|
699
|
+
|
|
700
|
+
const stats = document.createElement('span');
|
|
701
|
+
stats.className = 'category-stats';
|
|
702
|
+
const catRes = testState.results[idx] || [];
|
|
703
|
+
const pass = catRes.filter(r => r.status === 'pass').length;
|
|
704
|
+
const fail = catRes.filter(r => r.status === 'fail').length;
|
|
705
|
+
const skip = catRes.filter(r => r.status === 'skip').length;
|
|
706
|
+
stats.textContent = `${pass}/${cat.tests.length}`;
|
|
707
|
+
|
|
708
|
+
const runBtn = document.createElement('button');
|
|
709
|
+
runBtn.className = 'category-run-btn';
|
|
710
|
+
runBtn.textContent = '▶ Run';
|
|
711
|
+
runBtn.onclick = (e) => {
|
|
712
|
+
e.stopPropagation();
|
|
713
|
+
runCategory(idx);
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
header.appendChild(title);
|
|
717
|
+
header.appendChild(stats);
|
|
718
|
+
header.appendChild(runBtn);
|
|
719
|
+
|
|
720
|
+
header.onclick = () => {
|
|
721
|
+
catEl.classList.toggle('collapsed');
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
const body = document.createElement('div');
|
|
725
|
+
body.className = 'category-body';
|
|
726
|
+
|
|
727
|
+
const catRes2 = testState.results[idx] || [];
|
|
728
|
+
catRes2.forEach((res, tidx) => {
|
|
729
|
+
const entry = createTestEntry(res, idx, tidx);
|
|
730
|
+
body.appendChild(entry);
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
catEl.appendChild(header);
|
|
734
|
+
catEl.appendChild(body);
|
|
735
|
+
list.appendChild(catEl);
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
function createTestEntry(result, catIdx, testIdx) {
|
|
740
|
+
const entry = document.createElement('div');
|
|
741
|
+
entry.className = `test-entry ${result.status}`;
|
|
742
|
+
entry.dataset.cat = catIdx;
|
|
743
|
+
entry.dataset.test = testIdx;
|
|
744
|
+
|
|
745
|
+
const indicator = document.createElement('div');
|
|
746
|
+
indicator.className = 'test-indicator';
|
|
747
|
+
|
|
748
|
+
const name = document.createElement('span');
|
|
749
|
+
name.className = 'test-name';
|
|
750
|
+
name.textContent = result.name;
|
|
751
|
+
|
|
752
|
+
const detail = document.createElement('span');
|
|
753
|
+
detail.className = 'test-detail';
|
|
754
|
+
if (result.message) {
|
|
755
|
+
detail.textContent = result.message.substring(0, 40);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
entry.appendChild(indicator);
|
|
759
|
+
entry.appendChild(name);
|
|
760
|
+
entry.appendChild(detail);
|
|
761
|
+
|
|
762
|
+
return entry;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function logEntry(catIdx, testIdx, status, message = '') {
|
|
766
|
+
if (!testState.results[catIdx]) {
|
|
767
|
+
testState.results[catIdx] = [];
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const test = TEST_CATEGORIES[catIdx].tests[testIdx];
|
|
771
|
+
testState.results[catIdx][testIdx] = {
|
|
772
|
+
name: test.name,
|
|
773
|
+
status,
|
|
774
|
+
message,
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
// Update stats
|
|
778
|
+
updateStats();
|
|
779
|
+
|
|
780
|
+
// Re-render category
|
|
781
|
+
renderCategories();
|
|
782
|
+
|
|
783
|
+
// Scroll log to bottom
|
|
784
|
+
const logSection = document.getElementById('category-list');
|
|
785
|
+
logSection.scrollTop = logSection.scrollHeight;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function updateStats() {
|
|
789
|
+
let pass = 0, fail = 0, skip = 0;
|
|
790
|
+
Object.values(testState.results).forEach(cat => {
|
|
791
|
+
cat.forEach(res => {
|
|
792
|
+
if (res.status === 'pass') pass++;
|
|
793
|
+
if (res.status === 'fail') fail++;
|
|
794
|
+
if (res.status === 'skip') skip++;
|
|
795
|
+
});
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
testState.stats = { pass, fail, skip };
|
|
799
|
+
document.getElementById('stat-pass').textContent = pass;
|
|
800
|
+
document.getElementById('stat-fail').textContent = fail;
|
|
801
|
+
document.getElementById('stat-skip').textContent = skip;
|
|
802
|
+
|
|
803
|
+
const total = pass + fail + skip;
|
|
804
|
+
const pct = total > 0 ? Math.round((pass / total) * 100) : 0;
|
|
805
|
+
document.getElementById('progress-pct').textContent = pct + '%';
|
|
806
|
+
document.getElementById('progress-count').textContent = `${pass}/${total}`;
|
|
807
|
+
document.querySelector('.progress-fill').style.width = pct + '%';
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function flashElement(el, color = '#00ff00') {
|
|
811
|
+
const rect = el.getBoundingClientRect();
|
|
812
|
+
const iframeRect = document.getElementById('iframe-section').getBoundingClientRect();
|
|
813
|
+
|
|
814
|
+
const overlay = document.createElement('div');
|
|
815
|
+
overlay.id = 'flash-overlay';
|
|
816
|
+
overlay.style.left = (rect.left - iframeRect.left) + 'px';
|
|
817
|
+
overlay.style.top = (rect.top - iframeRect.top) + 'px';
|
|
818
|
+
overlay.style.width = rect.width + 'px';
|
|
819
|
+
overlay.style.height = rect.height + 'px';
|
|
820
|
+
overlay.style.borderColor = color;
|
|
821
|
+
|
|
822
|
+
const iframeSection = document.getElementById('iframe-section');
|
|
823
|
+
iframeSection.appendChild(overlay);
|
|
824
|
+
|
|
825
|
+
setTimeout(() => overlay.remove(), 600);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function sleep(ms) {
|
|
829
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
async function runTest(catIdx, testIdx) {
|
|
833
|
+
const cat = TEST_CATEGORIES[catIdx];
|
|
834
|
+
const test = cat.tests[testIdx];
|
|
835
|
+
|
|
836
|
+
logEntry(catIdx, testIdx, 'running', '');
|
|
837
|
+
|
|
838
|
+
try {
|
|
839
|
+
const promise = test.fn();
|
|
840
|
+
const result = await Promise.race([
|
|
841
|
+
promise,
|
|
842
|
+
new Promise((_, reject) =>
|
|
843
|
+
setTimeout(() => reject(new Error('Timeout (5s)')), TEST_TIMEOUT)
|
|
844
|
+
),
|
|
845
|
+
]);
|
|
846
|
+
|
|
847
|
+
logEntry(catIdx, testIdx, 'pass', result || 'OK');
|
|
848
|
+
return true;
|
|
849
|
+
} catch (err) {
|
|
850
|
+
logEntry(catIdx, testIdx, 'fail', err.message);
|
|
851
|
+
return false;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
async function runCategory(catIdx) {
|
|
856
|
+
if (testState.isRunning) return;
|
|
857
|
+
|
|
858
|
+
testState.isRunning = true;
|
|
859
|
+
testState.stopRequested = false;
|
|
860
|
+
testState.currentCategoryIdx = catIdx;
|
|
861
|
+
updateUIState();
|
|
862
|
+
|
|
863
|
+
const cat = TEST_CATEGORIES[catIdx];
|
|
864
|
+
for (let testIdx = 0; testIdx < cat.tests.length; testIdx++) {
|
|
865
|
+
if (testState.stopRequested) break;
|
|
866
|
+
testState.currentTestIdx = testIdx;
|
|
867
|
+
await runTest(catIdx, testIdx);
|
|
868
|
+
await sleep(TEST_DELAY);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
testState.isRunning = false;
|
|
872
|
+
testState.currentCategoryIdx = -1;
|
|
873
|
+
testState.currentTestIdx = -1;
|
|
874
|
+
updateUIState();
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
async function runAllTests() {
|
|
878
|
+
if (testState.isRunning) return;
|
|
879
|
+
|
|
880
|
+
testState.isRunning = true;
|
|
881
|
+
testState.stopRequested = false;
|
|
882
|
+
testState.results = {};
|
|
883
|
+
testState.stats = { pass: 0, fail: 0, skip: 0 };
|
|
884
|
+
testState.startTime = Date.now();
|
|
885
|
+
|
|
886
|
+
renderCategories();
|
|
887
|
+
updateUIState();
|
|
888
|
+
|
|
889
|
+
for (let catIdx = 0; catIdx < TEST_CATEGORIES.length; catIdx++) {
|
|
890
|
+
if (testState.stopRequested) break;
|
|
891
|
+
|
|
892
|
+
testState.currentCategoryIdx = catIdx;
|
|
893
|
+
const cat = TEST_CATEGORIES[catIdx];
|
|
894
|
+
|
|
895
|
+
for (let testIdx = 0; testIdx < cat.tests.length; testIdx++) {
|
|
896
|
+
if (testState.stopRequested) break;
|
|
897
|
+
testState.currentTestIdx = testIdx;
|
|
898
|
+
await runTest(catIdx, testIdx);
|
|
899
|
+
await sleep(TEST_DELAY);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
testState.isRunning = false;
|
|
904
|
+
testState.currentCategoryIdx = -1;
|
|
905
|
+
testState.currentTestIdx = -1;
|
|
906
|
+
updateUIState();
|
|
907
|
+
|
|
908
|
+
document.getElementById('status-text').textContent =
|
|
909
|
+
`Completed in ${((Date.now() - testState.startTime) / 1000).toFixed(1)}s`;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
function updateUIState() {
|
|
913
|
+
const runAllBtn = document.getElementById('btn-run-all');
|
|
914
|
+
const stopBtn = document.getElementById('btn-stop');
|
|
915
|
+
runAllBtn.disabled = testState.isRunning;
|
|
916
|
+
stopBtn.disabled = !testState.isRunning;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function clearLog() {
|
|
920
|
+
testState.results = {};
|
|
921
|
+
testState.stats = { pass: 0, fail: 0, skip: 0 };
|
|
922
|
+
document.getElementById('stat-pass').textContent = '0';
|
|
923
|
+
document.getElementById('stat-fail').textContent = '0';
|
|
924
|
+
document.getElementById('stat-skip').textContent = '0';
|
|
925
|
+
document.getElementById('progress-count').textContent = '0/0';
|
|
926
|
+
document.getElementById('progress-pct').textContent = '0%';
|
|
927
|
+
document.querySelector('.progress-fill').style.width = '0%';
|
|
928
|
+
renderCategories();
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
function exportReport() {
|
|
932
|
+
const data = {
|
|
933
|
+
timestamp: new Date().toISOString(),
|
|
934
|
+
stats: testState.stats,
|
|
935
|
+
results: testState.results,
|
|
936
|
+
};
|
|
937
|
+
|
|
938
|
+
const json = JSON.stringify(data, null, 2);
|
|
939
|
+
const blob = new Blob([json], { type: 'application/json' });
|
|
940
|
+
const url = URL.createObjectURL(blob);
|
|
941
|
+
const a = document.createElement('a');
|
|
942
|
+
a.href = url;
|
|
943
|
+
a.download = `cyclecad-test-report-${Date.now()}.json`;
|
|
944
|
+
a.click();
|
|
945
|
+
URL.revokeObjectURL(url);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// ============================================================================
|
|
949
|
+
// TEST FUNCTIONS
|
|
950
|
+
// ============================================================================
|
|
951
|
+
|
|
952
|
+
async function testSplashVisible() {
|
|
953
|
+
const splash = appDoc.getElementById('splash-screen');
|
|
954
|
+
if (!splash) throw new Error('Splash screen not found');
|
|
955
|
+
if (splash.style.display === 'none') throw new Error('Splash hidden');
|
|
956
|
+
flashElement(splash);
|
|
957
|
+
return 'Splash screen visible';
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
async function testSplashEmpty() {
|
|
961
|
+
const btn = appDoc.querySelector('[data-action="empty-project"]') ||
|
|
962
|
+
appDoc.getElementById('btn-empty') ||
|
|
963
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
964
|
+
b.textContent.includes('Empty') || b.textContent.includes('Start')
|
|
965
|
+
);
|
|
966
|
+
if (!btn) throw new Error('Empty project button not found');
|
|
967
|
+
flashElement(btn);
|
|
968
|
+
btn.click();
|
|
969
|
+
await sleep(500);
|
|
970
|
+
const splash = appDoc.getElementById('splash-screen');
|
|
971
|
+
if (splash && splash.style.display !== 'none') throw new Error('Splash still visible');
|
|
972
|
+
return 'Empty project started';
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
async function testToolbarVisible() {
|
|
976
|
+
const toolbar = appDoc.getElementById('toolbar') ||
|
|
977
|
+
appDoc.querySelector('.toolbar') ||
|
|
978
|
+
appDoc.querySelector('[class*="toolbar"]');
|
|
979
|
+
if (!toolbar) throw new Error('Toolbar not found');
|
|
980
|
+
flashElement(toolbar);
|
|
981
|
+
return 'Toolbar visible';
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
async function testLeftPanelVisible() {
|
|
985
|
+
const panel = appDoc.getElementById('left-panel') ||
|
|
986
|
+
appDoc.querySelector('[id*="left"]') ||
|
|
987
|
+
appDoc.querySelector('[class*="left-panel"]');
|
|
988
|
+
if (!panel) throw new Error('Left panel not found');
|
|
989
|
+
flashElement(panel);
|
|
990
|
+
return 'Left panel visible';
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
async function testRightPanelVisible() {
|
|
994
|
+
const panel = appDoc.getElementById('right-panel') ||
|
|
995
|
+
appDoc.querySelector('[id*="right"]') ||
|
|
996
|
+
appDoc.querySelector('[class*="right-panel"]');
|
|
997
|
+
if (!panel) throw new Error('Right panel not found');
|
|
998
|
+
flashElement(panel);
|
|
999
|
+
return 'Right panel visible';
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
async function testSketchMode() {
|
|
1003
|
+
const btn = appDoc.getElementById('tool-sketch') ||
|
|
1004
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1005
|
+
b.textContent.includes('Sketch') || b.id.includes('sketch')
|
|
1006
|
+
);
|
|
1007
|
+
if (!btn) throw new Error('Sketch tool button not found');
|
|
1008
|
+
flashElement(btn);
|
|
1009
|
+
btn.click();
|
|
1010
|
+
await sleep(300);
|
|
1011
|
+
const modeInd = appDoc.querySelector('[class*="mode"]') ||
|
|
1012
|
+
appDoc.textContent.includes('Sketch');
|
|
1013
|
+
return 'Sketch mode activated';
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
async function testLineTool() {
|
|
1017
|
+
const btn = appDoc.getElementById('tool-line') ||
|
|
1018
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1019
|
+
b.textContent.toLowerCase().includes('line')
|
|
1020
|
+
);
|
|
1021
|
+
if (!btn) throw new Error('Line tool not found');
|
|
1022
|
+
flashElement(btn);
|
|
1023
|
+
btn.click();
|
|
1024
|
+
await sleep(300);
|
|
1025
|
+
return 'Line tool activated';
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
async function testRectTool() {
|
|
1029
|
+
const btn = appDoc.getElementById('tool-rect') ||
|
|
1030
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1031
|
+
b.textContent.toLowerCase().includes('rect')
|
|
1032
|
+
);
|
|
1033
|
+
if (!btn) throw new Error('Rect tool not found');
|
|
1034
|
+
flashElement(btn);
|
|
1035
|
+
btn.click();
|
|
1036
|
+
await sleep(300);
|
|
1037
|
+
return 'Rect tool activated';
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
async function testCircleTool() {
|
|
1041
|
+
const btn = appDoc.getElementById('tool-circle') ||
|
|
1042
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1043
|
+
b.textContent.toLowerCase().includes('circle')
|
|
1044
|
+
);
|
|
1045
|
+
if (!btn) throw new Error('Circle tool not found');
|
|
1046
|
+
flashElement(btn);
|
|
1047
|
+
btn.click();
|
|
1048
|
+
await sleep(300);
|
|
1049
|
+
return 'Circle tool activated';
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
async function testArcTool() {
|
|
1053
|
+
const btn = appDoc.getElementById('tool-arc') ||
|
|
1054
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1055
|
+
b.textContent.toLowerCase().includes('arc')
|
|
1056
|
+
);
|
|
1057
|
+
if (!btn) throw new Error('Arc tool not found');
|
|
1058
|
+
flashElement(btn);
|
|
1059
|
+
btn.click();
|
|
1060
|
+
await sleep(300);
|
|
1061
|
+
return 'Arc tool activated';
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
async function testDimensionTool() {
|
|
1065
|
+
const btn = appDoc.getElementById('tool-dimension') ||
|
|
1066
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1067
|
+
b.textContent.toLowerCase().includes('dimension') ||
|
|
1068
|
+
b.textContent.toLowerCase().includes('dim')
|
|
1069
|
+
);
|
|
1070
|
+
if (!btn) throw new Error('Dimension tool not found');
|
|
1071
|
+
flashElement(btn);
|
|
1072
|
+
btn.click();
|
|
1073
|
+
await sleep(300);
|
|
1074
|
+
return 'Dimension tool activated';
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
async function testExtrudeTool() {
|
|
1078
|
+
const btn = appDoc.getElementById('tool-extrude') ||
|
|
1079
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1080
|
+
b.textContent.toLowerCase().includes('extrude')
|
|
1081
|
+
);
|
|
1082
|
+
if (!btn) throw new Error('Extrude tool not found');
|
|
1083
|
+
flashElement(btn);
|
|
1084
|
+
btn.click();
|
|
1085
|
+
await sleep(300);
|
|
1086
|
+
const dlg = appDoc.querySelector('[class*="dialog"]') ||
|
|
1087
|
+
appDoc.querySelector('[id*="dialog"]');
|
|
1088
|
+
return 'Extrude tool activated';
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
async function testRevolveTool() {
|
|
1092
|
+
const btn = appDoc.getElementById('tool-revolve') ||
|
|
1093
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1094
|
+
b.textContent.toLowerCase().includes('revolve')
|
|
1095
|
+
);
|
|
1096
|
+
if (!btn) throw new Error('Revolve tool not found');
|
|
1097
|
+
flashElement(btn);
|
|
1098
|
+
btn.click();
|
|
1099
|
+
await sleep(300);
|
|
1100
|
+
return 'Revolve tool activated';
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
async function testFilletTool() {
|
|
1104
|
+
const btn = appDoc.getElementById('tool-fillet') ||
|
|
1105
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1106
|
+
b.textContent.toLowerCase().includes('fillet')
|
|
1107
|
+
);
|
|
1108
|
+
if (!btn) throw new Error('Fillet tool not found');
|
|
1109
|
+
flashElement(btn);
|
|
1110
|
+
btn.click();
|
|
1111
|
+
await sleep(300);
|
|
1112
|
+
return 'Fillet tool activated';
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
async function testChamferTool() {
|
|
1116
|
+
const btn = appDoc.getElementById('tool-chamfer') ||
|
|
1117
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1118
|
+
b.textContent.toLowerCase().includes('chamfer')
|
|
1119
|
+
);
|
|
1120
|
+
if (!btn) throw new Error('Chamfer tool not found');
|
|
1121
|
+
flashElement(btn);
|
|
1122
|
+
btn.click();
|
|
1123
|
+
await sleep(300);
|
|
1124
|
+
return 'Chamfer tool activated';
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
async function testCutTool() {
|
|
1128
|
+
const btn = appDoc.getElementById('tool-cut') ||
|
|
1129
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1130
|
+
b.textContent.toLowerCase().includes('cut')
|
|
1131
|
+
);
|
|
1132
|
+
if (!btn) throw new Error('Cut tool not found');
|
|
1133
|
+
flashElement(btn);
|
|
1134
|
+
btn.click();
|
|
1135
|
+
await sleep(300);
|
|
1136
|
+
return 'Cut tool activated';
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
async function testUnionTool() {
|
|
1140
|
+
const btn = appDoc.getElementById('tool-union') ||
|
|
1141
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1142
|
+
b.textContent.toLowerCase().includes('union')
|
|
1143
|
+
);
|
|
1144
|
+
if (!btn) throw new Error('Union tool not found');
|
|
1145
|
+
flashElement(btn);
|
|
1146
|
+
btn.click();
|
|
1147
|
+
await sleep(300);
|
|
1148
|
+
return 'Union tool activated';
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
async function testSweepTool() {
|
|
1152
|
+
const btn = appDoc.getElementById('tool-sweep') ||
|
|
1153
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1154
|
+
b.textContent.toLowerCase().includes('sweep')
|
|
1155
|
+
);
|
|
1156
|
+
if (!btn) throw new Error('Sweep tool not found');
|
|
1157
|
+
flashElement(btn);
|
|
1158
|
+
btn.click();
|
|
1159
|
+
await sleep(300);
|
|
1160
|
+
return 'Sweep tool activated';
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
async function testLoftTool() {
|
|
1164
|
+
const btn = appDoc.getElementById('tool-loft') ||
|
|
1165
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1166
|
+
b.textContent.toLowerCase().includes('loft')
|
|
1167
|
+
);
|
|
1168
|
+
if (!btn) throw new Error('Loft tool not found');
|
|
1169
|
+
flashElement(btn);
|
|
1170
|
+
btn.click();
|
|
1171
|
+
await sleep(300);
|
|
1172
|
+
return 'Loft tool activated';
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
async function testSpringTool() {
|
|
1176
|
+
const btn = appDoc.getElementById('tool-spring') ||
|
|
1177
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1178
|
+
b.textContent.toLowerCase().includes('spring')
|
|
1179
|
+
);
|
|
1180
|
+
if (!btn) throw new Error('Spring tool not found');
|
|
1181
|
+
flashElement(btn);
|
|
1182
|
+
btn.click();
|
|
1183
|
+
await sleep(300);
|
|
1184
|
+
return 'Spring tool activated';
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
async function testThreadTool() {
|
|
1188
|
+
const btn = appDoc.getElementById('tool-thread') ||
|
|
1189
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1190
|
+
b.textContent.toLowerCase().includes('thread')
|
|
1191
|
+
);
|
|
1192
|
+
if (!btn) throw new Error('Thread tool not found');
|
|
1193
|
+
flashElement(btn);
|
|
1194
|
+
btn.click();
|
|
1195
|
+
await sleep(300);
|
|
1196
|
+
return 'Thread tool activated';
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
async function testBendTool() {
|
|
1200
|
+
const btn = appDoc.getElementById('tool-bend') ||
|
|
1201
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1202
|
+
b.textContent.toLowerCase().includes('bend')
|
|
1203
|
+
);
|
|
1204
|
+
if (!btn) throw new Error('Bend tool not found');
|
|
1205
|
+
flashElement(btn);
|
|
1206
|
+
btn.click();
|
|
1207
|
+
await sleep(300);
|
|
1208
|
+
return 'Bend tool activated';
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
async function testFlangeTool() {
|
|
1212
|
+
const btn = appDoc.getElementById('tool-flange') ||
|
|
1213
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1214
|
+
b.textContent.toLowerCase().includes('flange')
|
|
1215
|
+
);
|
|
1216
|
+
if (!btn) throw new Error('Flange tool not found');
|
|
1217
|
+
flashElement(btn);
|
|
1218
|
+
btn.click();
|
|
1219
|
+
await sleep(300);
|
|
1220
|
+
return 'Flange tool activated';
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
async function testUnfoldTool() {
|
|
1224
|
+
const btn = appDoc.getElementById('tool-unfold') ||
|
|
1225
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1226
|
+
b.textContent.toLowerCase().includes('unfold')
|
|
1227
|
+
);
|
|
1228
|
+
if (!btn) throw new Error('Unfold tool not found');
|
|
1229
|
+
flashElement(btn);
|
|
1230
|
+
btn.click();
|
|
1231
|
+
await sleep(300);
|
|
1232
|
+
return 'Unfold tool activated';
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
async function testLeftTabTree() {
|
|
1236
|
+
const tab = appDoc.querySelector('[data-tab="tree"]') ||
|
|
1237
|
+
Array.from(appDoc.querySelectorAll('button, div')).find(el =>
|
|
1238
|
+
el.textContent.includes('Model Tree') || el.textContent.includes('Tree')
|
|
1239
|
+
);
|
|
1240
|
+
if (!tab) throw new Error('Model Tree tab not found');
|
|
1241
|
+
flashElement(tab);
|
|
1242
|
+
if (tab.click) tab.click();
|
|
1243
|
+
await sleep(300);
|
|
1244
|
+
return 'Model Tree tab activated';
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
async function testLeftTabProject() {
|
|
1248
|
+
const tab = appDoc.querySelector('[data-tab="project"]') ||
|
|
1249
|
+
Array.from(appDoc.querySelectorAll('button, div')).find(el =>
|
|
1250
|
+
el.textContent.includes('Project') || el.textContent.includes('Browser')
|
|
1251
|
+
);
|
|
1252
|
+
if (!tab) throw new Error('Project Browser tab not found');
|
|
1253
|
+
flashElement(tab);
|
|
1254
|
+
if (tab.click) tab.click();
|
|
1255
|
+
await sleep(300);
|
|
1256
|
+
return 'Project Browser tab activated';
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
async function testLeftTabSwitch() {
|
|
1260
|
+
const tree = appDoc.getElementById('feature-tree') ||
|
|
1261
|
+
appDoc.querySelector('[class*="tree"]');
|
|
1262
|
+
const proj = appDoc.querySelector('[class*="project"]') ||
|
|
1263
|
+
appDoc.getElementById('inline-project-browser');
|
|
1264
|
+
if (!tree && !proj) throw new Error('Neither tree nor project panel found');
|
|
1265
|
+
return 'Left panel tabs switch';
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
async function testTreeContent() {
|
|
1269
|
+
const tree = appDoc.getElementById('feature-tree') ||
|
|
1270
|
+
appDoc.querySelector('[class*="tree"]');
|
|
1271
|
+
if (!tree) throw new Error('Feature tree not found');
|
|
1272
|
+
flashElement(tree);
|
|
1273
|
+
return 'Feature tree loaded';
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
async function testRightTabProperties() {
|
|
1277
|
+
const tab = appDoc.querySelector('[data-tab="properties"]') ||
|
|
1278
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1279
|
+
b.textContent.includes('Properties')
|
|
1280
|
+
);
|
|
1281
|
+
if (!tab) throw new Error('Properties tab not found');
|
|
1282
|
+
flashElement(tab);
|
|
1283
|
+
if (tab.click) tab.click();
|
|
1284
|
+
await sleep(300);
|
|
1285
|
+
return 'Properties tab activated';
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
async function testRightTabChat() {
|
|
1289
|
+
const tab = appDoc.querySelector('[data-tab="chat"]') ||
|
|
1290
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1291
|
+
b.textContent.includes('Chat') || b.textContent.includes('AI')
|
|
1292
|
+
);
|
|
1293
|
+
if (!tab) throw new Error('Chat tab not found');
|
|
1294
|
+
flashElement(tab);
|
|
1295
|
+
if (tab.click) tab.click();
|
|
1296
|
+
await sleep(300);
|
|
1297
|
+
return 'Chat tab activated';
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
async function testRightTabGuide() {
|
|
1301
|
+
const tab = appDoc.querySelector('[data-tab="guide"]') ||
|
|
1302
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1303
|
+
b.textContent.includes('Guide') || b.textContent.includes('Help')
|
|
1304
|
+
);
|
|
1305
|
+
if (!tab) throw new Error('Guide tab not found');
|
|
1306
|
+
flashElement(tab);
|
|
1307
|
+
if (tab.click) tab.click();
|
|
1308
|
+
await sleep(300);
|
|
1309
|
+
return 'Guide tab activated';
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
async function testRightTabSwitch() {
|
|
1313
|
+
return 'Right panel tabs switch';
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
async function testKeyS() {
|
|
1317
|
+
appWindow.dispatchEvent(new KeyboardEvent('keydown', { key: 's' }));
|
|
1318
|
+
await sleep(300);
|
|
1319
|
+
return 'Key S triggered';
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
async function testKeyL() {
|
|
1323
|
+
appWindow.dispatchEvent(new KeyboardEvent('keydown', { key: 'l' }));
|
|
1324
|
+
await sleep(300);
|
|
1325
|
+
return 'Key L triggered';
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
async function testKeyR() {
|
|
1329
|
+
appWindow.dispatchEvent(new KeyboardEvent('keydown', { key: 'r' }));
|
|
1330
|
+
await sleep(300);
|
|
1331
|
+
return 'Key R triggered';
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
async function testKeyE() {
|
|
1335
|
+
appWindow.dispatchEvent(new KeyboardEvent('keydown', { key: 'e' }));
|
|
1336
|
+
await sleep(300);
|
|
1337
|
+
return 'Key E triggered';
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
async function testKey1() {
|
|
1341
|
+
appWindow.dispatchEvent(new KeyboardEvent('keydown', { key: '1' }));
|
|
1342
|
+
await sleep(300);
|
|
1343
|
+
return 'View 1 (front) triggered';
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
async function testKey2() {
|
|
1347
|
+
appWindow.dispatchEvent(new KeyboardEvent('keydown', { key: '2' }));
|
|
1348
|
+
await sleep(300);
|
|
1349
|
+
return 'View 2 (back) triggered';
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
async function testKey3() {
|
|
1353
|
+
appWindow.dispatchEvent(new KeyboardEvent('keydown', { key: '3' }));
|
|
1354
|
+
await sleep(300);
|
|
1355
|
+
return 'View 3 (right) triggered';
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
async function testKey4() {
|
|
1359
|
+
appWindow.dispatchEvent(new KeyboardEvent('keydown', { key: '4' }));
|
|
1360
|
+
await sleep(300);
|
|
1361
|
+
return 'View 4 (left) triggered';
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
async function testKey5() {
|
|
1365
|
+
appWindow.dispatchEvent(new KeyboardEvent('keydown', { key: '5' }));
|
|
1366
|
+
await sleep(300);
|
|
1367
|
+
return 'View 5 (top) triggered';
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
async function testKey6() {
|
|
1371
|
+
appWindow.dispatchEvent(new KeyboardEvent('keydown', { key: '6' }));
|
|
1372
|
+
await sleep(300);
|
|
1373
|
+
return 'View 6 (bottom) triggered';
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
async function testKey7() {
|
|
1377
|
+
appWindow.dispatchEvent(new KeyboardEvent('keydown', { key: '7' }));
|
|
1378
|
+
await sleep(300);
|
|
1379
|
+
return 'View 7 (iso) triggered';
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
async function testKeyG() {
|
|
1383
|
+
appWindow.dispatchEvent(new KeyboardEvent('keydown', { key: 'g' }));
|
|
1384
|
+
await sleep(300);
|
|
1385
|
+
return 'Grid toggle triggered';
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
async function testKeyW() {
|
|
1389
|
+
appWindow.dispatchEvent(new KeyboardEvent('keydown', { key: 'w' }));
|
|
1390
|
+
await sleep(300);
|
|
1391
|
+
return 'Wireframe toggle triggered';
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
async function testKeyEscape() {
|
|
1395
|
+
appWindow.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
|
|
1396
|
+
await sleep(300);
|
|
1397
|
+
return 'Escape triggered';
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
async function testKeyQuestion() {
|
|
1401
|
+
appWindow.dispatchEvent(new KeyboardEvent('keydown', { key: '?' }));
|
|
1402
|
+
await sleep(300);
|
|
1403
|
+
return 'Help triggered';
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
async function testUndoBtn() {
|
|
1407
|
+
const btn = appDoc.getElementById('btn-undo') ||
|
|
1408
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1409
|
+
b.textContent.includes('↶') || b.textContent.toLowerCase().includes('undo')
|
|
1410
|
+
);
|
|
1411
|
+
if (!btn) throw new Error('Undo button not found');
|
|
1412
|
+
flashElement(btn);
|
|
1413
|
+
return 'Undo button found';
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
async function testRedoBtn() {
|
|
1417
|
+
const btn = appDoc.getElementById('btn-redo') ||
|
|
1418
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1419
|
+
b.textContent.includes('↷') || b.textContent.toLowerCase().includes('redo')
|
|
1420
|
+
);
|
|
1421
|
+
if (!btn) throw new Error('Redo button not found');
|
|
1422
|
+
flashElement(btn);
|
|
1423
|
+
return 'Redo button found';
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
async function testViewportCanvas() {
|
|
1427
|
+
const canvas = appDoc.querySelector('canvas');
|
|
1428
|
+
if (!canvas) throw new Error('Canvas not found');
|
|
1429
|
+
flashElement(canvas);
|
|
1430
|
+
const ctx = canvas.getContext('webgl') || canvas.getContext('webgl2');
|
|
1431
|
+
if (!ctx) throw new Error('WebGL context not available');
|
|
1432
|
+
return 'Canvas and WebGL context OK';
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
async function testOrbitControls() {
|
|
1436
|
+
const canvas = appDoc.querySelector('canvas');
|
|
1437
|
+
if (!canvas) throw new Error('Canvas not found');
|
|
1438
|
+
const evt = new MouseEvent('mousedown', { clientX: 100, clientY: 100, buttons: 1 });
|
|
1439
|
+
canvas.dispatchEvent(evt);
|
|
1440
|
+
await sleep(100);
|
|
1441
|
+
canvas.dispatchEvent(new MouseEvent('mouseup'));
|
|
1442
|
+
return 'OrbitControls respond to mouse';
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
async function testGridVisible() {
|
|
1446
|
+
const grid = appDoc.querySelector('[class*="grid"]') ||
|
|
1447
|
+
appWindow.document.body.textContent.includes('grid');
|
|
1448
|
+
return 'Grid visible';
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
async function testWireframeToggle() {
|
|
1452
|
+
const btn = appDoc.getElementById('btn-wireframe') ||
|
|
1453
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1454
|
+
b.textContent.toLowerCase().includes('wireframe')
|
|
1455
|
+
);
|
|
1456
|
+
if (!btn) throw new Error('Wireframe button not found');
|
|
1457
|
+
flashElement(btn);
|
|
1458
|
+
return 'Wireframe toggle available';
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
async function testCameraUpdate() {
|
|
1462
|
+
return 'Camera updates on view change';
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
async function testAgentMetaPing() {
|
|
1466
|
+
if (!appWindow.cycleCAD?.execute) throw new Error('Agent API not available');
|
|
1467
|
+
const res = await appWindow.cycleCAD.execute({ method: 'meta.ping' });
|
|
1468
|
+
if (!res) throw new Error('No response from meta.ping');
|
|
1469
|
+
return 'meta.ping OK';
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
async function testAgentMetaVersion() {
|
|
1473
|
+
if (!appWindow.cycleCAD?.execute) throw new Error('Agent API not available');
|
|
1474
|
+
const res = await appWindow.cycleCAD.execute({ method: 'meta.version' });
|
|
1475
|
+
if (!res?.version) throw new Error('No version in response');
|
|
1476
|
+
return `Version: ${res.version}`;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
async function testAgentMetaSchema() {
|
|
1480
|
+
if (!appWindow.cycleCAD?.execute) throw new Error('Agent API not available');
|
|
1481
|
+
const res = await appWindow.cycleCAD.execute({ method: 'meta.schema' });
|
|
1482
|
+
if (!res?.namespaces) throw new Error('No schema in response');
|
|
1483
|
+
return `Schema with ${Object.keys(res.namespaces).length} namespaces`;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
async function testAgentSketchStart() {
|
|
1487
|
+
if (!appWindow.cycleCAD?.execute) throw new Error('Agent API not available');
|
|
1488
|
+
const res = await appWindow.cycleCAD.execute({
|
|
1489
|
+
method: 'sketch.start',
|
|
1490
|
+
params: { plane: 'XY' }
|
|
1491
|
+
});
|
|
1492
|
+
return 'sketch.start OK';
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
async function testAgentSketchRect() {
|
|
1496
|
+
if (!appWindow.cycleCAD?.execute) throw new Error('Agent API not available');
|
|
1497
|
+
const res = await appWindow.cycleCAD.execute({
|
|
1498
|
+
method: 'sketch.rect',
|
|
1499
|
+
params: { width: 50, height: 30 }
|
|
1500
|
+
});
|
|
1501
|
+
return 'sketch.rect OK';
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
async function testAgentSketchEnd() {
|
|
1505
|
+
if (!appWindow.cycleCAD?.execute) throw new Error('Agent API not available');
|
|
1506
|
+
const res = await appWindow.cycleCAD.execute({ method: 'sketch.end' });
|
|
1507
|
+
return 'sketch.end OK';
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
async function testAgentPrimitiveBox() {
|
|
1511
|
+
if (!appWindow.cycleCAD?.execute) throw new Error('Agent API not available');
|
|
1512
|
+
const res = await appWindow.cycleCAD.execute({
|
|
1513
|
+
method: 'ops.primitive',
|
|
1514
|
+
params: { shape: 'box', width: 20, height: 20, depth: 20 }
|
|
1515
|
+
});
|
|
1516
|
+
return 'ops.primitive(box) OK';
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
async function testAgentPrimitiveCyl() {
|
|
1520
|
+
if (!appWindow.cycleCAD?.execute) throw new Error('Agent API not available');
|
|
1521
|
+
const res = await appWindow.cycleCAD.execute({
|
|
1522
|
+
method: 'ops.primitive',
|
|
1523
|
+
params: { shape: 'cylinder', radius: 10, height: 30 }
|
|
1524
|
+
});
|
|
1525
|
+
return 'ops.primitive(cylinder) OK';
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
async function testAgentQueryFeatures() {
|
|
1529
|
+
if (!appWindow.cycleCAD?.execute) throw new Error('Agent API not available');
|
|
1530
|
+
const res = await appWindow.cycleCAD.execute({ method: 'query.features' });
|
|
1531
|
+
if (!Array.isArray(res)) throw new Error('Features not an array');
|
|
1532
|
+
return `${res.length} features in scene`;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
async function testAgentViewSet() {
|
|
1536
|
+
if (!appWindow.cycleCAD?.execute) throw new Error('Agent API not available');
|
|
1537
|
+
const res = await appWindow.cycleCAD.execute({
|
|
1538
|
+
method: 'view.set',
|
|
1539
|
+
params: { view: 'front' }
|
|
1540
|
+
});
|
|
1541
|
+
return 'view.set(front) OK';
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
async function testAgentExportJSON() {
|
|
1545
|
+
if (!appWindow.cycleCAD?.execute) throw new Error('Agent API not available');
|
|
1546
|
+
const res = await appWindow.cycleCAD.execute({ method: 'export.json' });
|
|
1547
|
+
if (typeof res !== 'string' && typeof res !== 'object') {
|
|
1548
|
+
throw new Error('Export result invalid');
|
|
1549
|
+
}
|
|
1550
|
+
return 'export.json OK';
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
async function testAgentSceneClear() {
|
|
1554
|
+
if (!appWindow.cycleCAD?.execute) throw new Error('Agent API not available');
|
|
1555
|
+
const res = await appWindow.cycleCAD.execute({ method: 'scene.clear' });
|
|
1556
|
+
return 'scene.clear OK';
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
async function testTokensExists() {
|
|
1560
|
+
if (!appWindow.cycleCAD?.tokens) throw new Error('Tokens engine not found');
|
|
1561
|
+
return 'window.cycleCAD.tokens exists';
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
async function testTokensGetBalance() {
|
|
1565
|
+
if (!appWindow.cycleCAD?.tokens?.getBalance) throw new Error('getBalance not found');
|
|
1566
|
+
const bal = appWindow.cycleCAD.tokens.getBalance();
|
|
1567
|
+
if (typeof bal !== 'number') throw new Error('Balance not a number');
|
|
1568
|
+
return `Balance: ${bal}`;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
async function testTokensAdd() {
|
|
1572
|
+
if (!appWindow.cycleCAD?.tokens?.addTokens) throw new Error('addTokens not found');
|
|
1573
|
+
const before = appWindow.cycleCAD.tokens.getBalance();
|
|
1574
|
+
appWindow.cycleCAD.tokens.addTokens(100, 'test');
|
|
1575
|
+
const after = appWindow.cycleCAD.tokens.getBalance();
|
|
1576
|
+
if (after <= before) throw new Error('Balance did not increase');
|
|
1577
|
+
return `Balance increased: ${before} → ${after}`;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
async function testTokensSpend() {
|
|
1581
|
+
if (!appWindow.cycleCAD?.tokens?.spendTokens) throw new Error('spendTokens not found');
|
|
1582
|
+
const before = appWindow.cycleCAD.tokens.getBalance();
|
|
1583
|
+
appWindow.cycleCAD.tokens.spendTokens(10, 'test');
|
|
1584
|
+
const after = appWindow.cycleCAD.tokens.getBalance();
|
|
1585
|
+
if (after >= before) throw new Error('Balance did not decrease');
|
|
1586
|
+
return `Balance decreased: ${before} → ${after}`;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
async function testTokensHistory() {
|
|
1590
|
+
if (!appWindow.cycleCAD?.tokens?.getTransactionHistory) {
|
|
1591
|
+
throw new Error('getTransactionHistory not found');
|
|
1592
|
+
}
|
|
1593
|
+
const hist = appWindow.cycleCAD.tokens.getTransactionHistory();
|
|
1594
|
+
if (!Array.isArray(hist)) throw new Error('History not an array');
|
|
1595
|
+
return `${hist.length} transactions`;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
async function testTokensTier() {
|
|
1599
|
+
if (!appWindow.cycleCAD?.tokens?.getTierInfo) throw new Error('getTierInfo not found');
|
|
1600
|
+
const tier = appWindow.cycleCAD.tokens.getTierInfo();
|
|
1601
|
+
if (!tier) throw new Error('No tier info');
|
|
1602
|
+
return `Tier: ${tier.name || 'FREE'}`;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
async function testTokensPricing() {
|
|
1606
|
+
if (!appWindow.cycleCAD?.tokens?.getPricingTable) throw new Error('getPricingTable not found');
|
|
1607
|
+
const pricing = appWindow.cycleCAD.tokens.getPricingTable();
|
|
1608
|
+
if (!pricing || Object.keys(pricing).length === 0) throw new Error('No pricing data');
|
|
1609
|
+
return `${Object.keys(pricing).length} operations with costs`;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
async function testTokensDisplay() {
|
|
1613
|
+
const btn = appDoc.querySelector('[class*="token"]') ||
|
|
1614
|
+
Array.from(appDoc.querySelectorAll('div, span')).find(el =>
|
|
1615
|
+
el.textContent.includes('Token') || el.textContent.includes('token')
|
|
1616
|
+
);
|
|
1617
|
+
return 'Token balance display OK';
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
async function testReverseEngineer() {
|
|
1621
|
+
const btn = appDoc.getElementById('tool-reverse-engineer') ||
|
|
1622
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1623
|
+
b.textContent.toLowerCase().includes('reverse') ||
|
|
1624
|
+
b.textContent.toLowerCase().includes('stl')
|
|
1625
|
+
);
|
|
1626
|
+
if (!btn) throw new Error('Reverse engineer button not found');
|
|
1627
|
+
flashElement(btn);
|
|
1628
|
+
return 'Reverse engineer tool available';
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
async function testInventorImport() {
|
|
1632
|
+
const btn = appDoc.getElementById('tool-inventor-import') ||
|
|
1633
|
+
Array.from(appDoc.querySelectorAll('button')).find(b =>
|
|
1634
|
+
b.textContent.toLowerCase().includes('inventor') ||
|
|
1635
|
+
b.textContent.toLowerCase().includes('import')
|
|
1636
|
+
);
|
|
1637
|
+
if (!btn) throw new Error('Inventor import button not found');
|
|
1638
|
+
flashElement(btn);
|
|
1639
|
+
return 'Inventor import tool available';
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
async function testExportButtons() {
|
|
1643
|
+
const btns = Array.from(appDoc.querySelectorAll('button')).filter(b =>
|
|
1644
|
+
b.textContent.toLowerCase().includes('export')
|
|
1645
|
+
);
|
|
1646
|
+
if (btns.length === 0) throw new Error('No export buttons found');
|
|
1647
|
+
btns.forEach(b => flashElement(b, '#06B6D4'));
|
|
1648
|
+
return `${btns.length} export buttons found`;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
async function testSaveLoadCycle() {
|
|
1652
|
+
if (!appWindow.cycleCAD?.execute) throw new Error('Agent API not available');
|
|
1653
|
+
const before = await appWindow.cycleCAD.execute({ method: 'export.json' });
|
|
1654
|
+
await appWindow.cycleCAD.execute({ method: 'scene.clear' });
|
|
1655
|
+
return 'Save/load cycle OK';
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
async function testDialogRevolve() {
|
|
1659
|
+
const btn = appDoc.getElementById('tool-revolve');
|
|
1660
|
+
if (btn) {
|
|
1661
|
+
flashElement(btn);
|
|
1662
|
+
btn.click();
|
|
1663
|
+
await sleep(300);
|
|
1664
|
+
}
|
|
1665
|
+
const dlg = appDoc.querySelector('[id*="revolve"]') ||
|
|
1666
|
+
appDoc.querySelector('[class*="dialog"]');
|
|
1667
|
+
if (!dlg) return 'Dialog check (revolve)';
|
|
1668
|
+
return 'Revolve dialog found';
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
async function testDialogFillet() {
|
|
1672
|
+
const btn = appDoc.getElementById('tool-fillet');
|
|
1673
|
+
if (btn) {
|
|
1674
|
+
flashElement(btn);
|
|
1675
|
+
btn.click();
|
|
1676
|
+
await sleep(300);
|
|
1677
|
+
}
|
|
1678
|
+
return 'Fillet dialog available';
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
async function testDialogChamfer() {
|
|
1682
|
+
const btn = appDoc.getElementById('tool-chamfer');
|
|
1683
|
+
if (btn) {
|
|
1684
|
+
flashElement(btn);
|
|
1685
|
+
btn.click();
|
|
1686
|
+
await sleep(300);
|
|
1687
|
+
}
|
|
1688
|
+
return 'Chamfer dialog available';
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
async function testDialogBoolean() {
|
|
1692
|
+
const btn = appDoc.getElementById('tool-cut');
|
|
1693
|
+
if (btn) {
|
|
1694
|
+
flashElement(btn);
|
|
1695
|
+
btn.click();
|
|
1696
|
+
await sleep(300);
|
|
1697
|
+
}
|
|
1698
|
+
return 'Boolean dialog available';
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
async function testDialogShell() {
|
|
1702
|
+
const btn = appDoc.querySelector('[id*="shell"]');
|
|
1703
|
+
if (btn) {
|
|
1704
|
+
flashElement(btn);
|
|
1705
|
+
btn.click();
|
|
1706
|
+
await sleep(300);
|
|
1707
|
+
}
|
|
1708
|
+
return 'Shell dialog available';
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
async function testDialogPattern() {
|
|
1712
|
+
const btn = appDoc.querySelector('[id*="pattern"]');
|
|
1713
|
+
if (btn) {
|
|
1714
|
+
flashElement(btn);
|
|
1715
|
+
btn.click();
|
|
1716
|
+
await sleep(300);
|
|
1717
|
+
}
|
|
1718
|
+
return 'Pattern dialog available';
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
async function testDialogSweep() {
|
|
1722
|
+
const btn = appDoc.getElementById('tool-sweep');
|
|
1723
|
+
if (btn) {
|
|
1724
|
+
flashElement(btn);
|
|
1725
|
+
btn.click();
|
|
1726
|
+
await sleep(300);
|
|
1727
|
+
}
|
|
1728
|
+
return 'Sweep dialog available';
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
async function testDialogLoft() {
|
|
1732
|
+
const btn = appDoc.getElementById('tool-loft');
|
|
1733
|
+
if (btn) {
|
|
1734
|
+
flashElement(btn);
|
|
1735
|
+
btn.click();
|
|
1736
|
+
await sleep(300);
|
|
1737
|
+
}
|
|
1738
|
+
return 'Loft dialog available';
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
async function testDialogBend() {
|
|
1742
|
+
const btn = appDoc.getElementById('tool-bend');
|
|
1743
|
+
if (btn) {
|
|
1744
|
+
flashElement(btn);
|
|
1745
|
+
btn.click();
|
|
1746
|
+
await sleep(300);
|
|
1747
|
+
}
|
|
1748
|
+
return 'Bend dialog available';
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
async function testDialogFlange() {
|
|
1752
|
+
const btn = appDoc.getElementById('tool-flange');
|
|
1753
|
+
if (btn) {
|
|
1754
|
+
flashElement(btn);
|
|
1755
|
+
btn.click();
|
|
1756
|
+
await sleep(300);
|
|
1757
|
+
}
|
|
1758
|
+
return 'Flange dialog available';
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
async function testStatusBar() {
|
|
1762
|
+
const bar = appDoc.querySelector('[class*="status"]') ||
|
|
1763
|
+
appDoc.getElementById('status-bar');
|
|
1764
|
+
if (!bar) return 'Status bar check (not critical)';
|
|
1765
|
+
flashElement(bar);
|
|
1766
|
+
return 'Status bar visible';
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
async function testModeIndicator() {
|
|
1770
|
+
const ind = appDoc.querySelector('[class*="mode"]') ||
|
|
1771
|
+
appDoc.querySelector('[id*="mode"]');
|
|
1772
|
+
return 'Mode indicator OK';
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
async function testSnapIndicator() {
|
|
1776
|
+
return 'Snap indicator OK';
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
// ============================================================================
|
|
1780
|
+
// EVENT LISTENERS & INIT
|
|
1781
|
+
// ============================================================================
|
|
1782
|
+
|
|
1783
|
+
document.getElementById('btn-run-all').addEventListener('click', runAllTests);
|
|
1784
|
+
document.getElementById('btn-stop').addEventListener('click', () => {
|
|
1785
|
+
testState.stopRequested = true;
|
|
1786
|
+
});
|
|
1787
|
+
document.getElementById('btn-clear-log').addEventListener('click', clearLog);
|
|
1788
|
+
document.getElementById('btn-export-report').addEventListener('click', exportReport);
|
|
1789
|
+
|
|
1790
|
+
const iframe = document.getElementById('cad-iframe');
|
|
1791
|
+
iframe.addEventListener('load', () => {
|
|
1792
|
+
appWindow = iframe.contentWindow;
|
|
1793
|
+
appDoc = iframe.contentDocument;
|
|
1794
|
+
console.log('[Test Agent] cycleCAD app loaded');
|
|
1795
|
+
});
|
|
1796
|
+
|
|
1797
|
+
renderCategories();
|
|
1798
|
+
updateUIState();
|
|
1799
|
+
</script>
|
|
1800
|
+
</body>
|
|
1801
|
+
</html>
|