@vizzly-testing/cli 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +55 -9
  2. package/dist/cli.js +15 -2
  3. package/dist/commands/finalize.js +72 -0
  4. package/dist/commands/run.js +59 -19
  5. package/dist/commands/tdd.js +6 -13
  6. package/dist/commands/upload.js +1 -0
  7. package/dist/server/handlers/tdd-handler.js +82 -8
  8. package/dist/services/api-service.js +14 -0
  9. package/dist/services/html-report-generator.js +377 -0
  10. package/dist/services/report-generator/report.css +355 -0
  11. package/dist/services/report-generator/viewer.js +100 -0
  12. package/dist/services/server-manager.js +3 -2
  13. package/dist/services/tdd-service.js +436 -66
  14. package/dist/services/test-runner.js +56 -28
  15. package/dist/services/uploader.js +3 -2
  16. package/dist/types/commands/finalize.d.ts +13 -0
  17. package/dist/types/server/handlers/tdd-handler.d.ts +18 -1
  18. package/dist/types/services/api-service.d.ts +6 -0
  19. package/dist/types/services/html-report-generator.d.ts +52 -0
  20. package/dist/types/services/report-generator/viewer.d.ts +0 -0
  21. package/dist/types/services/server-manager.d.ts +19 -1
  22. package/dist/types/services/tdd-service.d.ts +24 -3
  23. package/dist/types/services/uploader.d.ts +2 -1
  24. package/dist/types/utils/config-loader.d.ts +3 -0
  25. package/dist/types/utils/environment-config.d.ts +5 -0
  26. package/dist/types/utils/security.d.ts +29 -0
  27. package/dist/utils/config-loader.js +11 -1
  28. package/dist/utils/environment-config.js +9 -0
  29. package/dist/utils/security.js +154 -0
  30. package/docs/api-reference.md +27 -0
  31. package/docs/tdd-mode.md +58 -12
  32. package/docs/test-integration.md +69 -0
  33. package/package.json +3 -2
@@ -0,0 +1,355 @@
1
+ * {
2
+ box-sizing: border-box;
3
+ margin: 0;
4
+ padding: 0;
5
+ }
6
+
7
+ body {
8
+ font-family:
9
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
10
+ line-height: 1.6;
11
+ color: #e2e8f0;
12
+ background: #0f172a;
13
+ min-height: 100vh;
14
+ }
15
+
16
+ .container {
17
+ max-width: 1200px;
18
+ margin: 0 auto;
19
+ padding: 20px;
20
+ }
21
+
22
+ .header {
23
+ background: #1e293b;
24
+ border-radius: 12px;
25
+ padding: 32px;
26
+ margin-bottom: 24px;
27
+ box-shadow:
28
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
29
+ 0 2px 4px -1px rgba(0, 0, 0, 0.06);
30
+ border: 1px solid #334155;
31
+ }
32
+
33
+ .header h1 {
34
+ font-size: 2rem;
35
+ margin-bottom: 24px;
36
+ color: #f1f5f9;
37
+ font-weight: 600;
38
+ display: flex;
39
+ align-items: center;
40
+ gap: 12px;
41
+ }
42
+
43
+ .summary {
44
+ display: flex;
45
+ gap: 30px;
46
+ margin-bottom: 15px;
47
+ }
48
+
49
+ .stat {
50
+ display: flex;
51
+ flex-direction: column;
52
+ align-items: center;
53
+ }
54
+
55
+ .stat-number {
56
+ font-size: 2rem;
57
+ font-weight: bold;
58
+ color: #94a3b8;
59
+ }
60
+
61
+ .stat.passed .stat-number {
62
+ color: #22c55e;
63
+ }
64
+ .stat.failed .stat-number {
65
+ color: #f59e0b;
66
+ }
67
+
68
+ .stat-label {
69
+ font-size: 0.875rem;
70
+ color: #94a3b8;
71
+ text-transform: uppercase;
72
+ letter-spacing: 0.025em;
73
+ font-weight: 500;
74
+ }
75
+
76
+ .build-info {
77
+ color: #64748b;
78
+ font-size: 0.875rem;
79
+ }
80
+
81
+ .no-failures {
82
+ text-align: center;
83
+ padding: 48px;
84
+ background: #1e293b;
85
+ border-radius: 12px;
86
+ border: 1px solid #334155;
87
+ font-size: 1.125rem;
88
+ color: #22c55e;
89
+ }
90
+
91
+ .comparison {
92
+ background: #1e293b;
93
+ border-radius: 12px;
94
+ padding: 24px;
95
+ margin-bottom: 24px;
96
+ box-shadow:
97
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
98
+ 0 2px 4px -1px rgba(0, 0, 0, 0.06);
99
+ border: 1px solid #334155;
100
+ }
101
+
102
+ .comparison-header {
103
+ display: flex;
104
+ justify-content: space-between;
105
+ align-items: center;
106
+ margin-bottom: 20px;
107
+ padding-bottom: 16px;
108
+ border-bottom: 1px solid #334155;
109
+ }
110
+
111
+ .comparison-header h3 {
112
+ margin: 0;
113
+ font-size: 1.25rem;
114
+ color: #f1f5f9;
115
+ font-weight: 600;
116
+ }
117
+
118
+ .comparison-meta {
119
+ display: flex;
120
+ gap: 16px;
121
+ font-size: 0.875rem;
122
+ }
123
+
124
+ .diff-status {
125
+ padding: 6px 12px;
126
+ background: rgba(245, 158, 11, 0.1);
127
+ color: #f59e0b;
128
+ border-radius: 6px;
129
+ font-weight: 500;
130
+ border: 1px solid rgba(245, 158, 11, 0.2);
131
+ font-size: 0.875rem;
132
+ }
133
+
134
+ .comparison-controls {
135
+ display: flex;
136
+ gap: 10px;
137
+ margin-bottom: 20px;
138
+ }
139
+
140
+ .view-mode-btn {
141
+ padding: 8px 16px;
142
+ border: 1px solid #475569;
143
+ background: #334155;
144
+ border-radius: 8px;
145
+ cursor: pointer;
146
+ font-size: 0.875rem;
147
+ font-weight: 500;
148
+ transition: all 0.2s;
149
+ color: #cbd5e1;
150
+ }
151
+
152
+ .view-mode-btn:hover {
153
+ background: #475569;
154
+ border-color: #64748b;
155
+ color: #e2e8f0;
156
+ }
157
+
158
+ .view-mode-btn.active {
159
+ background: #f59e0b;
160
+ color: #1e293b;
161
+ border-color: #f59e0b;
162
+ box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3);
163
+ }
164
+
165
+ .comparison-viewer {
166
+ position: relative;
167
+ border: 1px solid #334155;
168
+ border-radius: 8px;
169
+ overflow: hidden;
170
+ background: #0f172a;
171
+ }
172
+
173
+ .mode-container {
174
+ position: relative;
175
+ min-height: 200px;
176
+ text-align: center;
177
+ }
178
+
179
+ .mode-container img {
180
+ max-width: 100%;
181
+ height: auto;
182
+ display: block;
183
+ }
184
+
185
+ /* Overlay Mode */
186
+ .overlay-container {
187
+ position: relative;
188
+ display: inline-block;
189
+ margin: 0 auto;
190
+ cursor: pointer;
191
+ max-width: 100%;
192
+ }
193
+
194
+ .overlay-container img {
195
+ max-width: 100%;
196
+ height: auto;
197
+ display: block;
198
+ }
199
+
200
+ .overlay-container .current-image {
201
+ position: relative;
202
+ z-index: 1;
203
+ }
204
+
205
+ .overlay-container .baseline-image {
206
+ position: absolute;
207
+ top: 0;
208
+ left: 0;
209
+ opacity: 0.5;
210
+ z-index: 2;
211
+ }
212
+
213
+ .overlay-container .diff-image {
214
+ position: absolute;
215
+ top: 0;
216
+ left: 0;
217
+ opacity: 0;
218
+ z-index: 3;
219
+ }
220
+
221
+ /* Side by Side Mode */
222
+ .side-by-side-container {
223
+ display: grid;
224
+ grid-template-columns: 1fr 1fr;
225
+ gap: 24px;
226
+ align-items: start;
227
+ padding: 16px;
228
+ }
229
+
230
+ .side-by-side-image {
231
+ text-align: center;
232
+ flex: 1;
233
+ min-width: 200px;
234
+ max-width: 400px;
235
+ }
236
+
237
+ .side-by-side-image img {
238
+ width: 100%;
239
+ height: auto;
240
+ max-width: none;
241
+ border: 2px solid #475569;
242
+ border-radius: 8px;
243
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
244
+ transition: border-color 0.2s ease;
245
+ }
246
+
247
+ .side-by-side-image img:hover {
248
+ border-color: #f59e0b;
249
+ }
250
+
251
+ .side-by-side-image label {
252
+ display: block;
253
+ margin-top: 12px;
254
+ font-size: 0.875rem;
255
+ color: #94a3b8;
256
+ font-weight: 600;
257
+ text-transform: uppercase;
258
+ letter-spacing: 0.025em;
259
+ }
260
+
261
+ /* Onion Skin Mode */
262
+ .onion-container {
263
+ position: relative;
264
+ display: inline-block;
265
+ margin: 0 auto;
266
+ cursor: ew-resize;
267
+ user-select: none;
268
+ max-width: 100%;
269
+ }
270
+
271
+ .onion-baseline {
272
+ max-width: 100%;
273
+ height: auto;
274
+ display: block;
275
+ }
276
+
277
+ .onion-current {
278
+ position: absolute;
279
+ top: 0;
280
+ left: 0;
281
+ max-width: 100%;
282
+ height: auto;
283
+ clip-path: inset(0 50% 0 0);
284
+ }
285
+
286
+ .onion-divider {
287
+ position: absolute;
288
+ top: 0;
289
+ left: 50%;
290
+ width: 2px;
291
+ height: 100%;
292
+ background: #f59e0b;
293
+ transform: translateX(-50%);
294
+ z-index: 10;
295
+ pointer-events: none;
296
+ }
297
+
298
+ .onion-divider::before {
299
+ content: '';
300
+ position: absolute;
301
+ top: 50%;
302
+ left: 50%;
303
+ transform: translate(-50%, -50%);
304
+ width: 20px;
305
+ height: 20px;
306
+ border-radius: 50%;
307
+ background: #f59e0b;
308
+ border: 2px solid #1e293b;
309
+ }
310
+
311
+ .onion-divider::after {
312
+ content: '⟷';
313
+ position: absolute;
314
+ top: 50%;
315
+ left: 50%;
316
+ transform: translate(-50%, -50%);
317
+ color: #1e293b;
318
+ font-size: 12px;
319
+ font-weight: bold;
320
+ }
321
+
322
+ /* Toggle Mode */
323
+ .toggle-container {
324
+ display: inline-block;
325
+ }
326
+
327
+ .toggle-container img {
328
+ max-width: 100%;
329
+ width: auto;
330
+ height: auto;
331
+ cursor: pointer;
332
+ }
333
+
334
+ .error {
335
+ color: #ef4444;
336
+ text-align: center;
337
+ padding: 40px;
338
+ }
339
+
340
+ @media (max-width: 768px) {
341
+ .container {
342
+ padding: 10px;
343
+ }
344
+ .summary {
345
+ flex-wrap: wrap;
346
+ gap: 15px;
347
+ }
348
+ .comparison-controls {
349
+ flex-wrap: wrap;
350
+ }
351
+ .side-by-side-container {
352
+ grid-template-columns: 1fr;
353
+ gap: 15px;
354
+ }
355
+ }
@@ -0,0 +1,100 @@
1
+ document.addEventListener('DOMContentLoaded', function () {
2
+ // Handle view mode switching
3
+ document.querySelectorAll('.view-mode-btn').forEach(btn => {
4
+ btn.addEventListener('click', function () {
5
+ let comparison = this.closest('.comparison');
6
+ let mode = this.dataset.mode;
7
+
8
+ // Update active button
9
+ comparison.querySelectorAll('.view-mode-btn').forEach(b => b.classList.remove('active'));
10
+ this.classList.add('active');
11
+
12
+ // Update viewer mode
13
+ let viewer = comparison.querySelector('.comparison-viewer');
14
+ viewer.dataset.mode = mode;
15
+
16
+ // Hide all mode containers
17
+ viewer.querySelectorAll('.mode-container').forEach(container => {
18
+ container.style.display = 'none';
19
+ });
20
+
21
+ // Show appropriate mode container
22
+ let activeContainer = viewer.querySelector('.' + mode + '-mode');
23
+ if (activeContainer) {
24
+ activeContainer.style.display = 'block';
25
+ }
26
+ });
27
+ });
28
+
29
+ // Handle onion skin drag-to-reveal
30
+ document.querySelectorAll('.onion-container').forEach(container => {
31
+ let isDragging = false;
32
+ function updateOnionSkin(x) {
33
+ let rect = container.getBoundingClientRect();
34
+ let percentage = Math.max(0, Math.min(100, (x - rect.left) / rect.width * 100));
35
+ let currentImg = container.querySelector('.onion-current');
36
+ let divider = container.querySelector('.onion-divider');
37
+ if (currentImg && divider) {
38
+ currentImg.style.clipPath = 'inset(0 ' + (100 - percentage) + '% 0 0)';
39
+ divider.style.left = percentage + '%';
40
+ }
41
+ }
42
+ container.addEventListener('mousedown', function (e) {
43
+ isDragging = true;
44
+ updateOnionSkin(e.clientX);
45
+ e.preventDefault();
46
+ });
47
+ container.addEventListener('mousemove', function (e) {
48
+ if (isDragging) {
49
+ updateOnionSkin(e.clientX);
50
+ }
51
+ });
52
+ document.addEventListener('mouseup', function () {
53
+ isDragging = false;
54
+ });
55
+
56
+ // Touch events for mobile
57
+ container.addEventListener('touchstart', function (e) {
58
+ isDragging = true;
59
+ updateOnionSkin(e.touches[0].clientX);
60
+ e.preventDefault();
61
+ });
62
+ container.addEventListener('touchmove', function (e) {
63
+ if (isDragging) {
64
+ updateOnionSkin(e.touches[0].clientX);
65
+ e.preventDefault();
66
+ }
67
+ });
68
+ document.addEventListener('touchend', function () {
69
+ isDragging = false;
70
+ });
71
+ });
72
+
73
+ // Handle overlay mode clicking
74
+ document.querySelectorAll('.overlay-container').forEach(container => {
75
+ container.addEventListener('click', function () {
76
+ let diffImage = this.querySelector('.diff-image');
77
+ if (diffImage) {
78
+ // Toggle diff visibility
79
+ let isVisible = diffImage.style.opacity === '1';
80
+ diffImage.style.opacity = isVisible ? '0' : '1';
81
+ }
82
+ });
83
+ });
84
+
85
+ // Handle toggle mode clicking
86
+ document.querySelectorAll('.toggle-container img').forEach(img => {
87
+ let isBaseline = true;
88
+ let comparison = img.closest('.comparison');
89
+ let baselineSrc = comparison.querySelector('.baseline-image').src;
90
+ let currentSrc = comparison.querySelector('.current-image').src;
91
+ img.addEventListener('click', function () {
92
+ isBaseline = !isBaseline;
93
+ this.src = isBaseline ? baselineSrc : currentSrc;
94
+
95
+ // Update cursor style to indicate interactivity
96
+ this.style.cursor = 'pointer';
97
+ });
98
+ });
99
+ console.log('Vizzly TDD Report loaded successfully');
100
+ });
@@ -17,16 +17,17 @@ export class ServerManager extends BaseService {
17
17
  this.handler = null;
18
18
  this.emitter = null;
19
19
  }
20
- async start(buildId = null, tddMode = false) {
20
+ async start(buildId = null, tddMode = false, setBaseline = false) {
21
21
  this.buildId = buildId;
22
22
  this.tddMode = tddMode;
23
+ this.setBaseline = setBaseline;
23
24
  return super.start();
24
25
  }
25
26
  async onStart() {
26
27
  this.emitter = new EventEmitter();
27
28
  const port = this.config?.server?.port || 47392;
28
29
  if (this.tddMode) {
29
- this.handler = createTddHandler(this.config, process.cwd(), this.config?.baselineBuildId, this.config?.baselineComparisonId);
30
+ this.handler = createTddHandler(this.config, process.cwd(), this.config?.baselineBuildId, this.config?.baselineComparisonId, this.setBaseline);
30
31
  await this.handler.initialize();
31
32
  if (this.buildId) {
32
33
  this.handler.registerBuild(this.buildId);