cucumber-dressing 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +91 -0
- package/LICENSE +21 -0
- package/README.md +421 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +154 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +13 -0
- package/dist/parser.d.ts +39 -0
- package/dist/parser.js +140 -0
- package/dist/reporter.d.ts +74 -0
- package/dist/reporter.js +235 -0
- package/dist/statistics.d.ts +96 -0
- package/dist/statistics.js +256 -0
- package/dist/template-generator.d.ts +101 -0
- package/dist/template-generator.js +528 -0
- package/dist/templates/scripts.js.template.d.ts +3 -0
- package/dist/templates/scripts.js.template.js +297 -0
- package/dist/templates/styles.css.template.d.ts +3 -0
- package/dist/templates/styles.css.template.js +621 -0
- package/dist/types.d.ts +207 -0
- package/dist/types.js +5 -0
- package/dist/utils.d.ts +112 -0
- package/dist/utils.js +250 -0
- package/package.json +92 -0
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Generator - Creates HTML report from data
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync } from 'fs';
|
|
5
|
+
import Handlebars from 'handlebars';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import scriptsTemplate from './templates/scripts.js.template.js';
|
|
9
|
+
import stylesTemplate from './templates/styles.css.template.js';
|
|
10
|
+
import { extractErrorMessage, formatDuration, formatTimestamp, getStatusClass, getStatusIcon, markdownToHtml, parseTags, sanitizeHtml, } from './utils.js';
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = path.dirname(__filename);
|
|
13
|
+
const packageJson = JSON.parse(readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
|
|
14
|
+
export class TemplateGenerator {
|
|
15
|
+
options;
|
|
16
|
+
constructor(options) {
|
|
17
|
+
this.options = options;
|
|
18
|
+
this.registerHelpers();
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Register Handlebars helpers for formatting, status handling, and data manipulation in templates
|
|
22
|
+
*/
|
|
23
|
+
registerHelpers() {
|
|
24
|
+
Handlebars.registerHelper('formatDuration', (duration) => {
|
|
25
|
+
return formatDuration(duration, this.options.durationInMS);
|
|
26
|
+
});
|
|
27
|
+
Handlebars.registerHelper('statusIcon', (status) => {
|
|
28
|
+
return getStatusIcon(status);
|
|
29
|
+
});
|
|
30
|
+
Handlebars.registerHelper('statusClass', (status) => {
|
|
31
|
+
return getStatusClass(status);
|
|
32
|
+
});
|
|
33
|
+
Handlebars.registerHelper('sanitize', (text) => {
|
|
34
|
+
return new Handlebars.SafeString(sanitizeHtml(text));
|
|
35
|
+
});
|
|
36
|
+
Handlebars.registerHelper('markdown', (text) => {
|
|
37
|
+
return new Handlebars.SafeString(markdownToHtml(sanitizeHtml(text)));
|
|
38
|
+
});
|
|
39
|
+
Handlebars.registerHelper('parseTags', (tags) => {
|
|
40
|
+
return parseTags(tags).join(', ');
|
|
41
|
+
});
|
|
42
|
+
Handlebars.registerHelper('formatTimestamp', (timestamp) => {
|
|
43
|
+
return formatTimestamp(timestamp);
|
|
44
|
+
});
|
|
45
|
+
Handlebars.registerHelper('json', (context) => {
|
|
46
|
+
return JSON.stringify(context, null, 2);
|
|
47
|
+
});
|
|
48
|
+
Handlebars.registerHelper('percentage', (value, total) => {
|
|
49
|
+
if (total === 0) {
|
|
50
|
+
return '0';
|
|
51
|
+
}
|
|
52
|
+
return ((value / total) * 100).toFixed(1);
|
|
53
|
+
});
|
|
54
|
+
Handlebars.registerHelper('eq', (a, b) => {
|
|
55
|
+
return a === b;
|
|
56
|
+
});
|
|
57
|
+
Handlebars.registerHelper('or', (...args) => {
|
|
58
|
+
return args.slice(0, -1).some(arg => !!arg);
|
|
59
|
+
});
|
|
60
|
+
Handlebars.registerHelper('and', (...args) => {
|
|
61
|
+
return args.slice(0, -1).every(arg => !!arg);
|
|
62
|
+
});
|
|
63
|
+
Handlebars.registerHelper('gt', (a, b) => {
|
|
64
|
+
return a > b;
|
|
65
|
+
});
|
|
66
|
+
Handlebars.registerHelper('extractError', (result) => {
|
|
67
|
+
return extractErrorMessage(result);
|
|
68
|
+
});
|
|
69
|
+
Handlebars.registerHelper('hasEmbedding', (step, type) => {
|
|
70
|
+
if (!step.embeddings) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
return step.embeddings.some(e => e.mime_type.includes(type));
|
|
74
|
+
});
|
|
75
|
+
Handlebars.registerHelper('getEmbedding', (step, type) => {
|
|
76
|
+
if (!step.embeddings) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
return step.embeddings.find(e => e.mime_type.includes(type));
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Generate complete HTML report from processed data
|
|
84
|
+
* @param features - Processed features with calculated status and duration
|
|
85
|
+
* @param statistics - Report statistics including counts and pass rate
|
|
86
|
+
* @param chartData - Chart.js data for visualization
|
|
87
|
+
* @returns Complete HTML report as a string
|
|
88
|
+
*/
|
|
89
|
+
generateReport(features, statistics, chartData) {
|
|
90
|
+
const template = this.getMainTemplate();
|
|
91
|
+
const compiled = Handlebars.compile(template);
|
|
92
|
+
const data = {
|
|
93
|
+
reportTitle: this.options.reportTitle ?? 'DRESSING Test Report',
|
|
94
|
+
reportName: this.options.reportName ?? 'Test Execution Report',
|
|
95
|
+
brandTitle: this.options.brandTitle ?? 'DRESSING',
|
|
96
|
+
theme: this.options.theme ?? 'modern',
|
|
97
|
+
features,
|
|
98
|
+
statistics,
|
|
99
|
+
chartData,
|
|
100
|
+
metadata: this.options.metadata,
|
|
101
|
+
customData: this.options.customData,
|
|
102
|
+
displayDuration: this.options.displayDuration !== false,
|
|
103
|
+
scenarioTimestamp: this.options.scenarioTimestamp,
|
|
104
|
+
columnLayout: this.options.columnLayout ?? 2,
|
|
105
|
+
generatedAt: formatTimestamp(Date.now()),
|
|
106
|
+
};
|
|
107
|
+
return compiled(data);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get main HTML template structure with embedded styles and scripts
|
|
111
|
+
* @returns HTML template string with placeholders for Handlebars compilation
|
|
112
|
+
*/
|
|
113
|
+
getMainTemplate() {
|
|
114
|
+
return `<!DOCTYPE html>
|
|
115
|
+
<html lang="en">
|
|
116
|
+
<head>
|
|
117
|
+
<meta charset="UTF-8">
|
|
118
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
119
|
+
<title>{{reportTitle}}</title>
|
|
120
|
+
<style>
|
|
121
|
+
${this.getStyles()}
|
|
122
|
+
${this.getCustomColorStyles()}
|
|
123
|
+
</style>
|
|
124
|
+
</head>
|
|
125
|
+
<body class="theme-{{theme}}">
|
|
126
|
+
<div class="container">
|
|
127
|
+
${this.getHeaderTemplate()}
|
|
128
|
+
${this.getStatisticsTemplate()}
|
|
129
|
+
${this.getChartsTemplate()}
|
|
130
|
+
${this.getMetadataTemplate()}
|
|
131
|
+
${this.getFeaturesListTemplate()}
|
|
132
|
+
${this.getFooterTemplate()}
|
|
133
|
+
</div>
|
|
134
|
+
<script>
|
|
135
|
+
${this.getScripts()}
|
|
136
|
+
</script>
|
|
137
|
+
</body>
|
|
138
|
+
</html>`;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get header template with branding and report info
|
|
142
|
+
* @returns HTML template string for the report header section
|
|
143
|
+
*/
|
|
144
|
+
getHeaderTemplate() {
|
|
145
|
+
return `
|
|
146
|
+
<header class="report-header">
|
|
147
|
+
<div class="brand">
|
|
148
|
+
<h1>{{brandTitle}}</h1>
|
|
149
|
+
<span class="tagline">Detailed Report of Executed Scenarios, Steps and INsights for Gherkin</span>
|
|
150
|
+
</div>
|
|
151
|
+
<div class="report-info">
|
|
152
|
+
<h2>{{reportName}}</h2>
|
|
153
|
+
<p class="generated-time">Generated: {{generatedAt}}</p>
|
|
154
|
+
</div>
|
|
155
|
+
</header>
|
|
156
|
+
`;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Get statistics overview template with test execution summary cards
|
|
160
|
+
* @returns HTML template string for statistics display
|
|
161
|
+
*/
|
|
162
|
+
getStatisticsTemplate() {
|
|
163
|
+
return `
|
|
164
|
+
<section class="statistics-overview">
|
|
165
|
+
<h3>Test Execution Summary</h3>
|
|
166
|
+
<div class="stats-grid">
|
|
167
|
+
<div class="stat-card">
|
|
168
|
+
<div class="stat-value">{{statistics.features}}</div>
|
|
169
|
+
<div class="stat-label">Features</div>
|
|
170
|
+
</div>
|
|
171
|
+
<div class="stat-card">
|
|
172
|
+
<div class="stat-value">{{statistics.scenarios}}</div>
|
|
173
|
+
<div class="stat-label">Scenarios</div>
|
|
174
|
+
</div>
|
|
175
|
+
<div class="stat-card">
|
|
176
|
+
<div class="stat-value">{{statistics.steps}}</div>
|
|
177
|
+
<div class="stat-label">Steps</div>
|
|
178
|
+
</div>
|
|
179
|
+
<div class="stat-card status-passed">
|
|
180
|
+
<div class="stat-value">{{statistics.passed}}</div>
|
|
181
|
+
<div class="stat-label">Passed</div>
|
|
182
|
+
</div>
|
|
183
|
+
<div class="stat-card status-failed">
|
|
184
|
+
<div class="stat-value">{{statistics.failed}}</div>
|
|
185
|
+
<div class="stat-label">Failed</div>
|
|
186
|
+
</div>
|
|
187
|
+
<div class="stat-card status-skipped">
|
|
188
|
+
<div class="stat-value">{{statistics.skipped}}</div>
|
|
189
|
+
<div class="stat-label">Skipped</div>
|
|
190
|
+
</div>
|
|
191
|
+
{{#if displayDuration}}
|
|
192
|
+
<div class="stat-card">
|
|
193
|
+
<div class="stat-value">{{formatDuration statistics.duration}}</div>
|
|
194
|
+
<div class="stat-label">Duration</div>
|
|
195
|
+
</div>
|
|
196
|
+
{{/if}}
|
|
197
|
+
<div class="stat-card">
|
|
198
|
+
<div class="stat-value">{{statistics.passRate}}%</div>
|
|
199
|
+
<div class="stat-label">Pass Rate</div>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
</section>
|
|
203
|
+
`;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Get charts section template for status distribution and pass rate visualization
|
|
207
|
+
* @returns HTML template string with canvas elements for Chart.js
|
|
208
|
+
*/
|
|
209
|
+
getChartsTemplate() {
|
|
210
|
+
return `
|
|
211
|
+
<section class="charts-section">
|
|
212
|
+
<div class="charts-grid">
|
|
213
|
+
<div class="chart-container">
|
|
214
|
+
<h4>Status Distribution</h4>
|
|
215
|
+
<canvas id="statusChart"></canvas>
|
|
216
|
+
</div>
|
|
217
|
+
<div class="chart-container">
|
|
218
|
+
<h4>Pass Rate</h4>
|
|
219
|
+
<canvas id="passRateChart"></canvas>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
</section>
|
|
223
|
+
`;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Get metadata section template for browser, platform, and custom data display
|
|
227
|
+
* @returns HTML template string for optional metadata sections
|
|
228
|
+
*/
|
|
229
|
+
getMetadataTemplate() {
|
|
230
|
+
return `
|
|
231
|
+
{{#if metadata}}
|
|
232
|
+
<section class="metadata-section">
|
|
233
|
+
<h3>Test Metadata</h3>
|
|
234
|
+
<div class="metadata-grid">
|
|
235
|
+
{{#if metadata.browser}}
|
|
236
|
+
<div class="metadata-item">
|
|
237
|
+
<span class="metadata-label">Browser:</span>
|
|
238
|
+
<span class="metadata-value">{{metadata.browser.name}} {{metadata.browser.version}}</span>
|
|
239
|
+
</div>
|
|
240
|
+
{{/if}}
|
|
241
|
+
{{#if metadata.platform}}
|
|
242
|
+
<div class="metadata-item">
|
|
243
|
+
<span class="metadata-label">Platform:</span>
|
|
244
|
+
<span class="metadata-value">{{metadata.platform.name}} {{metadata.platform.version}}</span>
|
|
245
|
+
</div>
|
|
246
|
+
{{/if}}
|
|
247
|
+
{{#if metadata.device}}
|
|
248
|
+
<div class="metadata-item">
|
|
249
|
+
<span class="metadata-label">Device:</span>
|
|
250
|
+
<span class="metadata-value">{{metadata.device}}</span>
|
|
251
|
+
</div>
|
|
252
|
+
{{/if}}
|
|
253
|
+
</div>
|
|
254
|
+
</section>
|
|
255
|
+
{{/if}}
|
|
256
|
+
|
|
257
|
+
{{#if customData}}
|
|
258
|
+
<section class="custom-data-section">
|
|
259
|
+
<h3>{{customData.title}}</h3>
|
|
260
|
+
<div class="custom-data-grid">
|
|
261
|
+
{{#each customData.data}}
|
|
262
|
+
<div class="custom-data-item">
|
|
263
|
+
<span class="custom-data-label">{{this.label}}:</span>
|
|
264
|
+
<span class="custom-data-value">{{this.value}}</span>
|
|
265
|
+
</div>
|
|
266
|
+
{{/each}}
|
|
267
|
+
</div>
|
|
268
|
+
</section>
|
|
269
|
+
{{/if}}
|
|
270
|
+
`;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Get features list template with search filter and feature cards
|
|
274
|
+
* @returns HTML template string for features section
|
|
275
|
+
*/
|
|
276
|
+
getFeaturesListTemplate() {
|
|
277
|
+
return `
|
|
278
|
+
<section class="features-section">
|
|
279
|
+
<h3>Features</h3>
|
|
280
|
+
${this.getFilterTemplate()}
|
|
281
|
+
<div class="features-list">
|
|
282
|
+
{{#each features}}
|
|
283
|
+
${this.getFeatureCardTemplate()}
|
|
284
|
+
{{/each}}
|
|
285
|
+
</div>
|
|
286
|
+
</section>
|
|
287
|
+
`;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Get filter controls template for searching and status filtering
|
|
291
|
+
* @returns HTML template string with search input and filter buttons
|
|
292
|
+
*/
|
|
293
|
+
getFilterTemplate() {
|
|
294
|
+
return `
|
|
295
|
+
<div class="features-filter">
|
|
296
|
+
<input type="text" id="searchFilter" placeholder="Search features, scenarios, or tags..." />
|
|
297
|
+
<div class="status-filters">
|
|
298
|
+
<button class="filter-btn active" data-status="all">All</button>
|
|
299
|
+
<button class="filter-btn" data-status="passed">Passed</button>
|
|
300
|
+
<button class="filter-btn" data-status="failed">Failed</button>
|
|
301
|
+
<button class="filter-btn" data-status="skipped">Skipped</button>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
`;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Get feature card template with collapsible scenarios
|
|
308
|
+
* @returns HTML template string for individual feature display
|
|
309
|
+
*/
|
|
310
|
+
getFeatureCardTemplate() {
|
|
311
|
+
return `
|
|
312
|
+
<div class="feature-card {{statusClass this.status}}" data-status="{{this.status}}">
|
|
313
|
+
${this.getFeatureHeaderTemplate()}
|
|
314
|
+
{{#if this.description}}
|
|
315
|
+
<div class="feature-description">{{markdown this.description}}</div>
|
|
316
|
+
{{/if}}
|
|
317
|
+
<div class="feature-content" id="feature-{{@index}}" style="display: none;">
|
|
318
|
+
<div class="scenarios-list">
|
|
319
|
+
{{#each this.scenarios}}
|
|
320
|
+
${this.getScenarioCardTemplate()}
|
|
321
|
+
{{/each}}
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
325
|
+
`;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Get feature header template with title, tags, and toggle controls
|
|
329
|
+
* @returns HTML template string for feature header with collapsible functionality
|
|
330
|
+
*/
|
|
331
|
+
getFeatureHeaderTemplate() {
|
|
332
|
+
return `
|
|
333
|
+
<div class="feature-header" onclick="toggleFeature('feature-{{@index}}')">
|
|
334
|
+
<div class="feature-title">
|
|
335
|
+
<span class="status-icon">{{statusIcon this.status}}</span>
|
|
336
|
+
<h4>{{this.name}}</h4>
|
|
337
|
+
{{#if this.tags}}
|
|
338
|
+
<div class="tags">
|
|
339
|
+
{{#each this.tags}}
|
|
340
|
+
<span class="tag">{{this.name}}</span>
|
|
341
|
+
{{/each}}
|
|
342
|
+
</div>
|
|
343
|
+
{{/if}}
|
|
344
|
+
</div>
|
|
345
|
+
<div class="feature-stats">
|
|
346
|
+
<span class="stat">{{this.scenarios.length}} scenarios</span>
|
|
347
|
+
{{#if ../displayDuration}}
|
|
348
|
+
<span class="stat">{{formatDuration this.duration}}</span>
|
|
349
|
+
{{/if}}
|
|
350
|
+
<span class="toggle-icon">▼</span>
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
`;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Get scenario card template with collapsible steps
|
|
357
|
+
* @returns HTML template string for individual scenario display
|
|
358
|
+
*/
|
|
359
|
+
getScenarioCardTemplate() {
|
|
360
|
+
return `
|
|
361
|
+
<div class="scenario-card {{statusClass this.status}}">
|
|
362
|
+
${this.getScenarioHeaderTemplate()}
|
|
363
|
+
<div class="scenario-content" id="scenario-{{../id}}-{{@index}}" style="display: none;">
|
|
364
|
+
{{#if this.description}}
|
|
365
|
+
<div class="scenario-description">{{markdown this.description}}</div>
|
|
366
|
+
{{/if}}
|
|
367
|
+
${this.getStepsListTemplate()}
|
|
368
|
+
</div>
|
|
369
|
+
</div>
|
|
370
|
+
`;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Get scenario header template with title, tags, and toggle controls
|
|
374
|
+
* @returns HTML template string for scenario header with collapsible functionality
|
|
375
|
+
*/
|
|
376
|
+
getScenarioHeaderTemplate() {
|
|
377
|
+
return `
|
|
378
|
+
<div class="scenario-header" onclick="toggleScenario('scenario-{{../id}}-{{@index}}')">
|
|
379
|
+
<div class="scenario-title">
|
|
380
|
+
<span class="status-icon">{{statusIcon this.status}}</span>
|
|
381
|
+
<h5>{{this.keyword}}: {{this.name}}</h5>
|
|
382
|
+
{{#if this.tags}}
|
|
383
|
+
<div class="tags">
|
|
384
|
+
{{#each this.tags}}
|
|
385
|
+
<span class="tag">{{this.name}}</span>
|
|
386
|
+
{{/each}}
|
|
387
|
+
</div>
|
|
388
|
+
{{/if}}
|
|
389
|
+
</div>
|
|
390
|
+
<div class="scenario-stats">
|
|
391
|
+
{{#if ../../displayDuration}}
|
|
392
|
+
<span class="stat">{{formatDuration this.duration}}</span>
|
|
393
|
+
{{/if}}
|
|
394
|
+
<span class="toggle-icon">▼</span>
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
397
|
+
`;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Get steps list template with errors, data tables, and screenshots
|
|
401
|
+
* @returns HTML template string for scenario steps display
|
|
402
|
+
*/
|
|
403
|
+
getStepsListTemplate() {
|
|
404
|
+
return `
|
|
405
|
+
<div class="steps-list">
|
|
406
|
+
{{#each this.steps}}
|
|
407
|
+
<div class="step-item {{statusClass this.result.status}}">
|
|
408
|
+
<div class="step-header">
|
|
409
|
+
<span class="status-icon">{{statusIcon this.result.status}}</span>
|
|
410
|
+
<span class="step-keyword">{{this.keyword}}</span>
|
|
411
|
+
<span class="step-name">{{this.name}}</span>
|
|
412
|
+
{{#if ../../../displayDuration}}
|
|
413
|
+
<span class="step-duration">{{formatDuration this.result.duration}}</span>
|
|
414
|
+
{{/if}}
|
|
415
|
+
</div>
|
|
416
|
+
{{#if this.result.error_message}}
|
|
417
|
+
<div class="step-error">
|
|
418
|
+
<pre>{{this.result.error_message}}</pre>
|
|
419
|
+
</div>
|
|
420
|
+
{{/if}}
|
|
421
|
+
{{#if this.doc_string}}
|
|
422
|
+
<div class="step-doc-string">
|
|
423
|
+
<pre>{{this.doc_string.value}}</pre>
|
|
424
|
+
</div>
|
|
425
|
+
{{/if}}
|
|
426
|
+
{{#if this.rows}}
|
|
427
|
+
<div class="step-data-table">
|
|
428
|
+
<table>
|
|
429
|
+
{{#each this.rows}}
|
|
430
|
+
<tr>
|
|
431
|
+
{{#each this.cells}}
|
|
432
|
+
<td>{{this}}</td>
|
|
433
|
+
{{/each}}
|
|
434
|
+
</tr>
|
|
435
|
+
{{/each}}
|
|
436
|
+
</table>
|
|
437
|
+
</div>
|
|
438
|
+
{{/if}}
|
|
439
|
+
{{#if (hasEmbedding this "image")}}
|
|
440
|
+
<div class="step-screenshot">
|
|
441
|
+
<img src="data:{{getEmbedding this "image"}}.mime_type;base64,{{getEmbedding this "image"}}.data" alt="Screenshot" />
|
|
442
|
+
</div>
|
|
443
|
+
{{/if}}
|
|
444
|
+
</div>
|
|
445
|
+
{{/each}}
|
|
446
|
+
</div>
|
|
447
|
+
`;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Get footer template with branding and version info
|
|
451
|
+
* @returns HTML template string for report footer
|
|
452
|
+
*/
|
|
453
|
+
getFooterTemplate() {
|
|
454
|
+
return `
|
|
455
|
+
<footer class="report-footer">
|
|
456
|
+
<p>Generated by <strong>DRESSING</strong> - Detailed Report of Executed Scenarios, Steps and INsights for Gherkin</p>
|
|
457
|
+
<p class="version">v${packageJson.version}</p>
|
|
458
|
+
</footer>
|
|
459
|
+
`;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Get CSS styles from template file
|
|
463
|
+
* @returns CSS stylesheet string for report styling
|
|
464
|
+
*/
|
|
465
|
+
getStyles() {
|
|
466
|
+
return stylesTemplate;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Generate custom CSS color variables if colors option is provided
|
|
470
|
+
* @returns CSS custom properties string with color overrides, or empty string if no colors specified
|
|
471
|
+
*/
|
|
472
|
+
getCustomColorStyles() {
|
|
473
|
+
if (!this.options.colors) {
|
|
474
|
+
return '';
|
|
475
|
+
}
|
|
476
|
+
const colors = this.options.colors;
|
|
477
|
+
const cssVars = [];
|
|
478
|
+
if (colors.primary) {
|
|
479
|
+
cssVars.push(`--color-primary: ${colors.primary};`);
|
|
480
|
+
}
|
|
481
|
+
if (colors.success) {
|
|
482
|
+
cssVars.push(`--color-success: ${colors.success};`);
|
|
483
|
+
}
|
|
484
|
+
if (colors.danger) {
|
|
485
|
+
cssVars.push(`--color-danger: ${colors.danger};`);
|
|
486
|
+
}
|
|
487
|
+
if (colors.warning) {
|
|
488
|
+
cssVars.push(`--color-warning: ${colors.warning};`);
|
|
489
|
+
}
|
|
490
|
+
if (colors.info) {
|
|
491
|
+
cssVars.push(`--color-info: ${colors.info};`);
|
|
492
|
+
}
|
|
493
|
+
if (colors.muted) {
|
|
494
|
+
cssVars.push(`--color-muted: ${colors.muted};`);
|
|
495
|
+
}
|
|
496
|
+
if (colors.background) {
|
|
497
|
+
cssVars.push(`--color-bg: ${colors.background};`);
|
|
498
|
+
}
|
|
499
|
+
if (colors.backgroundSecondary) {
|
|
500
|
+
cssVars.push(`--color-bg-secondary: ${colors.backgroundSecondary};`);
|
|
501
|
+
}
|
|
502
|
+
if (colors.border) {
|
|
503
|
+
cssVars.push(`--color-border: ${colors.border};`);
|
|
504
|
+
}
|
|
505
|
+
if (colors.text) {
|
|
506
|
+
cssVars.push(`--color-text: ${colors.text};`);
|
|
507
|
+
}
|
|
508
|
+
if (colors.textSecondary) {
|
|
509
|
+
cssVars.push(`--color-text-secondary: ${colors.textSecondary};`);
|
|
510
|
+
}
|
|
511
|
+
if (cssVars.length === 0) {
|
|
512
|
+
return '';
|
|
513
|
+
}
|
|
514
|
+
return `
|
|
515
|
+
:root {
|
|
516
|
+
${cssVars.join('\n ')}
|
|
517
|
+
}
|
|
518
|
+
`;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Get JavaScript code from template file for report interactivity
|
|
522
|
+
* @returns JavaScript code string for charts, filtering, and collapsible sections
|
|
523
|
+
*/
|
|
524
|
+
getScripts() {
|
|
525
|
+
return scriptsTemplate;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
//# sourceMappingURL=template-generator.js.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const _default: "\n// DRESSING - JavaScript for interactivity\n\n// Toggle feature visibility\nfunction toggleFeature(id) {\n const element = document.getElementById(id);\n const card = element.closest('.feature-card');\n\n if (element.style.display === 'none') {\n element.style.display = 'block';\n card.classList.add('expanded');\n } else {\n element.style.display = 'none';\n card.classList.remove('expanded');\n }\n}\n\n// Toggle scenario visibility\nfunction toggleScenario(id) {\n const element = document.getElementById(id);\n const card = element.closest('.scenario-card');\n\n if (element.style.display === 'none') {\n element.style.display = 'block';\n card.classList.add('expanded');\n } else {\n element.style.display = 'none';\n card.classList.remove('expanded');\n }\n}\n\n// Search and filter functionality\ndocument.addEventListener('DOMContentLoaded', function() {\n const searchInput = document.getElementById('searchFilter');\n const filterButtons = document.querySelectorAll('.filter-btn');\n const featureCards = document.querySelectorAll('.feature-card');\n\n let currentStatusFilter = 'all';\n\n // Search functionality\n if (searchInput) {\n searchInput.addEventListener('input', function(e) {\n const searchTerm = e.target.value.toLowerCase();\n filterFeatures(searchTerm, currentStatusFilter);\n });\n }\n\n // Status filter functionality\n filterButtons.forEach(button => {\n button.addEventListener('click', function() {\n filterButtons.forEach(btn => btn.classList.remove('active'));\n this.classList.add('active');\n currentStatusFilter = this.dataset.status;\n\n const searchTerm = searchInput ? searchInput.value.toLowerCase() : '';\n filterFeatures(searchTerm, currentStatusFilter);\n });\n });\n\n // Filter features based on search and status\n function filterFeatures(searchTerm, statusFilter) {\n featureCards.forEach(card => {\n const featureName = card.querySelector('.feature-title h4')?.textContent.toLowerCase() || '';\n const tags = Array.from(card.querySelectorAll('.tag')).map(tag => tag.textContent.toLowerCase());\n const scenarios = Array.from(card.querySelectorAll('.scenario-title h5')).map(s => s.textContent.toLowerCase());\n const status = card.dataset.status;\n\n const matchesSearch = !searchTerm ||\n featureName.includes(searchTerm) ||\n tags.some(tag => tag.includes(searchTerm)) ||\n scenarios.some(scenario => scenario.includes(searchTerm));\n\n const matchesStatus = statusFilter === 'all' || status === statusFilter;\n\n if (matchesSearch && matchesStatus) {\n card.style.display = 'block';\n } else {\n card.style.display = 'none';\n }\n });\n }\n\n // Expand failed features by default\n expandFailedFeatures();\n});\n\n// Initialize Chart.js charts\nfunction initializeCharts() {\n // Status Distribution Chart\n const statusChartCanvas = document.getElementById('statusChart');\n if (statusChartCanvas) {\n const ctx = statusChartCanvas.getContext('2d');\n\n // Get data from the page\n const statsCards = document.querySelectorAll('.stat-card');\n let passed = 0, failed = 0, skipped = 0;\n\n statsCards.forEach(card => {\n const label = card.querySelector('.stat-label')?.textContent.toLowerCase();\n const value = parseInt(card.querySelector('.stat-value')?.textContent) || 0;\n\n if (label === 'passed') passed = value;\n if (label === 'failed') failed = value;\n if (label === 'skipped') skipped = value;\n });\n\n new Chart(ctx, {\n type: 'doughnut',\n data: {\n labels: ['Passed', 'Failed', 'Skipped'],\n datasets: [{\n data: [passed, failed, skipped],\n backgroundColor: [\n 'rgba(16, 185, 129, 0.8)',\n 'rgba(239, 68, 68, 0.8)',\n 'rgba(245, 158, 11, 0.8)'\n ],\n borderColor: [\n 'rgb(16, 185, 129)',\n 'rgb(239, 68, 68)',\n 'rgb(245, 158, 11)'\n ],\n borderWidth: 2\n }]\n },\n options: {\n responsive: true,\n maintainAspectRatio: true,\n plugins: {\n legend: {\n position: 'bottom',\n },\n tooltip: {\n callbacks: {\n label: function(context) {\n const label = context.label || '';\n const value = context.parsed || 0;\n const total = context.dataset.data.reduce((a, b) => a + b, 0);\n const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : 0;\n return label + ': ' + value + ' (' + percentage + '%)';\n }\n }\n }\n }\n }\n });\n }\n\n // Pass Rate Chart\n const passRateChartCanvas = document.getElementById('passRateChart');\n if (passRateChartCanvas) {\n const ctx = passRateChartCanvas.getContext('2d');\n\n // Get pass rate from the page\n const passRateCard = Array.from(document.querySelectorAll('.stat-card')).find(card =>\n card.querySelector('.stat-label')?.textContent.toLowerCase() === 'pass rate'\n );\n\n const passRate = passRateCard ? parseFloat(passRateCard.querySelector('.stat-value')?.textContent) : 0;\n const failRate = 100 - passRate;\n\n new Chart(ctx, {\n type: 'pie',\n data: {\n labels: ['Passed', 'Failed'],\n datasets: [{\n data: [passRate, failRate],\n backgroundColor: [\n 'rgba(16, 185, 129, 0.8)',\n 'rgba(239, 68, 68, 0.8)'\n ],\n borderColor: [\n 'rgb(16, 185, 129)',\n 'rgb(239, 68, 68)'\n ],\n borderWidth: 2\n }]\n },\n options: {\n responsive: true,\n maintainAspectRatio: true,\n plugins: {\n legend: {\n position: 'bottom',\n },\n tooltip: {\n callbacks: {\n label: function(context) {\n const label = context.label || '';\n const value = context.parsed || 0;\n return label + ': ' + value.toFixed(1) + '%';\n }\n }\n }\n }\n }\n });\n }\n}\n\n// Expand failed features automatically\nfunction expandFailedFeatures() {\n const failedFeatures = document.querySelectorAll('.feature-card.status-failed');\n failedFeatures.forEach(feature => {\n const featureContent = feature.querySelector('.feature-content');\n if (featureContent) {\n featureContent.style.display = 'block';\n feature.classList.add('expanded');\n\n // Also expand failed scenarios within\n const failedScenarios = feature.querySelectorAll('.scenario-card.status-failed');\n failedScenarios.forEach(scenario => {\n const scenarioContent = scenario.querySelector('.scenario-content');\n if (scenarioContent) {\n scenarioContent.style.display = 'block';\n scenario.classList.add('expanded');\n }\n });\n }\n });\n}\n\n// Keyboard shortcuts\ndocument.addEventListener('keydown', function(e) {\n // Ctrl/Cmd + F to focus search\n if ((e.ctrlKey || e.metaKey) && e.key === 'f') {\n e.preventDefault();\n const searchInput = document.getElementById('searchFilter');\n if (searchInput) {\n searchInput.focus();\n }\n }\n\n // Escape to clear search\n if (e.key === 'Escape') {\n const searchInput = document.getElementById('searchFilter');\n if (searchInput && document.activeElement === searchInput) {\n searchInput.value = '';\n searchInput.dispatchEvent(new Event('input'));\n }\n }\n\n // Ctrl/Cmd + E to expand all\n if ((e.ctrlKey || e.metaKey) && e.key === 'e') {\n e.preventDefault();\n expandAll();\n }\n\n // Ctrl/Cmd + C to collapse all\n if ((e.ctrlKey || e.metaKey) && e.key === 'c') {\n e.preventDefault();\n collapseAll();\n }\n});\n\n// Expand all features and scenarios\nfunction expandAll() {\n document.querySelectorAll('.feature-content').forEach(content => {\n content.style.display = 'block';\n content.closest('.feature-card')?.classList.add('expanded');\n });\n\n document.querySelectorAll('.scenario-content').forEach(content => {\n content.style.display = 'block';\n content.closest('.scenario-card')?.classList.add('expanded');\n });\n}\n\n// Collapse all features and scenarios\nfunction collapseAll() {\n document.querySelectorAll('.feature-content').forEach(content => {\n content.style.display = 'none';\n content.closest('.feature-card')?.classList.remove('expanded');\n });\n\n document.querySelectorAll('.scenario-content').forEach(content => {\n content.style.display = 'none';\n content.closest('.scenario-card')?.classList.remove('expanded');\n });\n}\n\n// Add Chart.js CDN and initialize charts after loading\n(function() {\n const script = document.createElement('script');\n script.src = 'https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js';\n script.onload = function() {\n console.log('Chart.js loaded successfully');\n // Initialize charts after Chart.js is loaded\n initializeCharts();\n };\n script.onerror = function() {\n console.error('Failed to load Chart.js');\n };\n document.head.appendChild(script);\n})();\n";
|
|
2
|
+
export default _default;
|
|
3
|
+
//# sourceMappingURL=scripts.js.template.d.ts.map
|