@cognima/banners 0.0.1-beta
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/assets/fonts/Manrope/Manrope-Bold.ttf +0 -0
- package/assets/fonts/Manrope/Manrope-Regular.ttf +0 -0
- package/assets/fonts/Others/AbyssinicaSIL-Regular.ttf +0 -0
- package/assets/fonts/Others/ChirpRegular.ttf +0 -0
- package/assets/fonts/Poppins/Poppins-Bold.ttf +0 -0
- package/assets/fonts/Poppins/Poppins-Medium.ttf +0 -0
- package/assets/fonts/Poppins/Poppins-Regular.ttf +0 -0
- package/assets/placeholders/album_art.png +0 -0
- package/assets/placeholders/avatar.png +0 -0
- package/assets/placeholders/badge.jpg +0 -0
- package/assets/placeholders/badge.png +0 -0
- package/assets/placeholders/badge_2.jpg +0 -0
- package/assets/placeholders/badge_3.jpg +0 -0
- package/assets/placeholders/badge_4.jpg +0 -0
- package/assets/placeholders/badge_5.jpg +0 -0
- package/assets/placeholders/banner.jpeg +0 -0
- package/assets/placeholders/images.jpeg +0 -0
- package/index.js +153 -0
- package/package.json +34 -0
- package/src/animation-effects.js +631 -0
- package/src/cache-manager.js +258 -0
- package/src/community-banner.js +1536 -0
- package/src/constants.js +208 -0
- package/src/discord-profile.js +584 -0
- package/src/e-commerce-banner.js +1214 -0
- package/src/effects.js +355 -0
- package/src/error-handler.js +305 -0
- package/src/event-banner.js +1319 -0
- package/src/facebook-post.js +679 -0
- package/src/gradient-welcome.js +430 -0
- package/src/image-filters.js +1034 -0
- package/src/image-processor.js +1014 -0
- package/src/instagram-post.js +504 -0
- package/src/interactive-elements.js +1208 -0
- package/src/linkedin-post.js +658 -0
- package/src/marketing-banner.js +1089 -0
- package/src/minimalist-banner.js +892 -0
- package/src/modern-profile.js +755 -0
- package/src/performance-optimizer.js +216 -0
- package/src/telegram-header.js +544 -0
- package/src/test-runner.js +645 -0
- package/src/tiktok-post.js +713 -0
- package/src/twitter-header.js +604 -0
- package/src/validator.js +442 -0
- package/src/welcome-leave.js +445 -0
- package/src/whatsapp-status.js +386 -0
- package/src/youtube-thumbnail.js +681 -0
- package/utils.js +710 -0
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Módulo de Execução de Testes
|
|
5
|
+
*
|
|
6
|
+
* Este módulo fornece funcionalidades para testar automaticamente
|
|
7
|
+
* os diferentes componentes do módulo de banners.
|
|
8
|
+
*
|
|
9
|
+
* @author Cognima Team (melhorado)
|
|
10
|
+
* @version 2.0.0
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
|
|
15
|
+
const fs = require("fs");
|
|
16
|
+
const path = require("path");
|
|
17
|
+
const { loadImageWithAxios, encodeToBuffer } = require("../utils");
|
|
18
|
+
const ErrorHandler = require("./error-handler");
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Classe para execução de testes
|
|
22
|
+
*/
|
|
23
|
+
class TestRunner {
|
|
24
|
+
/**
|
|
25
|
+
* Cria uma nova instância do executor de testes
|
|
26
|
+
* @param {Object} options - Opções de configuração
|
|
27
|
+
*/
|
|
28
|
+
constructor(options = {}) {
|
|
29
|
+
this.outputDir = options.outputDir || path.join(process.cwd(), "test-output");
|
|
30
|
+
this.errorHandler = new ErrorHandler(options.errorHandler);
|
|
31
|
+
this.tests = new Map();
|
|
32
|
+
this.testResults = [];
|
|
33
|
+
this.verbose = options.verbose !== false;
|
|
34
|
+
this.stopOnError = options.stopOnError === true;
|
|
35
|
+
|
|
36
|
+
// Cria o diretório de saída se não existir
|
|
37
|
+
if (!fs.existsSync(this.outputDir)) {
|
|
38
|
+
try {
|
|
39
|
+
fs.mkdirSync(this.outputDir, { recursive: true });
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error("Falha ao criar diretório de saída:", err.message);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Registra um teste
|
|
48
|
+
* @param {string} name - Nome do teste
|
|
49
|
+
* @param {Function} testFn - Função de teste
|
|
50
|
+
* @param {Object} options - Opções do teste
|
|
51
|
+
*/
|
|
52
|
+
registerTest(name, testFn, options = {}) {
|
|
53
|
+
this.tests.set(name, {
|
|
54
|
+
name,
|
|
55
|
+
fn: testFn,
|
|
56
|
+
options: {
|
|
57
|
+
timeout: options.timeout || 30000,
|
|
58
|
+
skip: options.skip === true,
|
|
59
|
+
only: options.only === true,
|
|
60
|
+
category: options.category || "general"
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Registra um conjunto de testes
|
|
67
|
+
* @param {Object} tests - Objeto com testes
|
|
68
|
+
*/
|
|
69
|
+
registerTests(tests) {
|
|
70
|
+
for (const [name, test] of Object.entries(tests)) {
|
|
71
|
+
this.registerTest(name, test.fn, test.options);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Executa todos os testes registrados
|
|
77
|
+
* @param {Object} options - Opções de execução
|
|
78
|
+
* @returns {Promise<Object>} - Resultados dos testes
|
|
79
|
+
*/
|
|
80
|
+
async runTests(options = {}) {
|
|
81
|
+
const {
|
|
82
|
+
filter = null,
|
|
83
|
+
category = null,
|
|
84
|
+
parallel = false,
|
|
85
|
+
concurrency = 5
|
|
86
|
+
} = options;
|
|
87
|
+
|
|
88
|
+
this.testResults = [];
|
|
89
|
+
|
|
90
|
+
// Filtra os testes a serem executados
|
|
91
|
+
let testsToRun = Array.from(this.tests.values());
|
|
92
|
+
|
|
93
|
+
// Verifica se há testes marcados como "only"
|
|
94
|
+
const onlyTests = testsToRun.filter(test => test.options.only);
|
|
95
|
+
|
|
96
|
+
if (onlyTests.length > 0) {
|
|
97
|
+
testsToRun = onlyTests;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Filtra por nome
|
|
101
|
+
if (filter) {
|
|
102
|
+
const filterRegex = new RegExp(filter, "i");
|
|
103
|
+
testsToRun = testsToRun.filter(test => filterRegex.test(test.name));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Filtra por categoria
|
|
107
|
+
if (category) {
|
|
108
|
+
testsToRun = testsToRun.filter(test => test.options.category === category);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Remove testes marcados como "skip"
|
|
112
|
+
testsToRun = testsToRun.filter(test => !test.options.skip);
|
|
113
|
+
|
|
114
|
+
if (testsToRun.length === 0) {
|
|
115
|
+
console.log("Nenhum teste para executar.");
|
|
116
|
+
return {
|
|
117
|
+
total: 0,
|
|
118
|
+
passed: 0,
|
|
119
|
+
failed: 0,
|
|
120
|
+
skipped: 0,
|
|
121
|
+
duration: 0,
|
|
122
|
+
results: []
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
console.log(`Executando ${testsToRun.length} testes...`);
|
|
127
|
+
|
|
128
|
+
const startTime = Date.now();
|
|
129
|
+
|
|
130
|
+
if (parallel) {
|
|
131
|
+
// Executa os testes em paralelo com limite de concorrência
|
|
132
|
+
for (let i = 0; i < testsToRun.length; i += concurrency) {
|
|
133
|
+
const batch = testsToRun.slice(i, i + concurrency);
|
|
134
|
+
const promises = batch.map(test => this._runTest(test));
|
|
135
|
+
|
|
136
|
+
await Promise.all(promises);
|
|
137
|
+
|
|
138
|
+
if (this.stopOnError && this.testResults.some(result => !result.passed)) {
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
// Executa os testes sequencialmente
|
|
144
|
+
for (const test of testsToRun) {
|
|
145
|
+
await this._runTest(test);
|
|
146
|
+
|
|
147
|
+
if (this.stopOnError && this.testResults.some(result => !result.passed)) {
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const endTime = Date.now();
|
|
154
|
+
const duration = endTime - startTime;
|
|
155
|
+
|
|
156
|
+
// Gera o relatório
|
|
157
|
+
const passed = this.testResults.filter(result => result.passed).length;
|
|
158
|
+
const failed = this.testResults.filter(result => !result.passed).length;
|
|
159
|
+
const skipped = this.tests.size - testsToRun.length;
|
|
160
|
+
|
|
161
|
+
const summary = {
|
|
162
|
+
total: this.testResults.length,
|
|
163
|
+
passed,
|
|
164
|
+
failed,
|
|
165
|
+
skipped,
|
|
166
|
+
duration,
|
|
167
|
+
results: this.testResults
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
this._printSummary(summary);
|
|
171
|
+
|
|
172
|
+
return summary;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Executa um único teste
|
|
177
|
+
* @param {Object} test - Teste a ser executado
|
|
178
|
+
* @returns {Promise<Object>} - Resultado do teste
|
|
179
|
+
* @private
|
|
180
|
+
*/
|
|
181
|
+
async _runTest(test) {
|
|
182
|
+
const { name, fn, options } = test;
|
|
183
|
+
|
|
184
|
+
if (this.verbose) {
|
|
185
|
+
console.log(`\nExecutando teste: ${name}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const result = {
|
|
189
|
+
name,
|
|
190
|
+
category: options.category,
|
|
191
|
+
passed: false,
|
|
192
|
+
duration: 0,
|
|
193
|
+
error: null,
|
|
194
|
+
output: null
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const startTime = Date.now();
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
// Executa o teste com timeout
|
|
201
|
+
const testPromise = fn.call(this);
|
|
202
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
203
|
+
setTimeout(() => reject(new Error(`Timeout: O teste excedeu o limite de ${options.timeout}ms.`)), options.timeout);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const output = await Promise.race([testPromise, timeoutPromise]);
|
|
207
|
+
|
|
208
|
+
result.passed = true;
|
|
209
|
+
result.output = output;
|
|
210
|
+
|
|
211
|
+
if (this.verbose) {
|
|
212
|
+
console.log(`✓ Teste passou: ${name}`);
|
|
213
|
+
}
|
|
214
|
+
} catch (err) {
|
|
215
|
+
result.passed = false;
|
|
216
|
+
result.error = {
|
|
217
|
+
message: err.message,
|
|
218
|
+
stack: err.stack
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
if (this.verbose) {
|
|
222
|
+
console.error(`✗ Teste falhou: ${name}`);
|
|
223
|
+
console.error(` Erro: ${err.message}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const endTime = Date.now();
|
|
228
|
+
result.duration = endTime - startTime;
|
|
229
|
+
|
|
230
|
+
this.testResults.push(result);
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Imprime o resumo dos testes
|
|
236
|
+
* @param {Object} summary - Resumo dos testes
|
|
237
|
+
* @private
|
|
238
|
+
*/
|
|
239
|
+
_printSummary(summary) {
|
|
240
|
+
console.log("\n=== Resumo dos Testes ===");
|
|
241
|
+
console.log(`Total: ${summary.total}`);
|
|
242
|
+
console.log(`Passou: ${summary.passed}`);
|
|
243
|
+
console.log(`Falhou: ${summary.failed}`);
|
|
244
|
+
console.log(`Pulou: ${summary.skipped}`);
|
|
245
|
+
console.log(`Duração: ${summary.duration}ms`);
|
|
246
|
+
|
|
247
|
+
if (summary.failed > 0) {
|
|
248
|
+
console.log("\nTestes que falharam:");
|
|
249
|
+
|
|
250
|
+
for (const result of summary.results) {
|
|
251
|
+
if (!result.passed) {
|
|
252
|
+
console.log(`- ${result.name}`);
|
|
253
|
+
console.log(` Erro: ${result.error.message}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Gera um relatório HTML dos testes
|
|
261
|
+
* @param {string} outputPath - Caminho do arquivo de saída
|
|
262
|
+
* @returns {Promise<string>} - Caminho do arquivo gerado
|
|
263
|
+
*/
|
|
264
|
+
async generateReport(outputPath = null) {
|
|
265
|
+
const reportPath = outputPath || path.join(this.outputDir, "test-report.html");
|
|
266
|
+
|
|
267
|
+
// Calcula estatísticas
|
|
268
|
+
const total = this.testResults.length;
|
|
269
|
+
const passed = this.testResults.filter(result => result.passed).length;
|
|
270
|
+
const failed = this.testResults.filter(result => !result.passed).length;
|
|
271
|
+
const passRate = total > 0 ? Math.round((passed / total) * 100) : 0;
|
|
272
|
+
|
|
273
|
+
// Agrupa por categoria
|
|
274
|
+
const categories = {};
|
|
275
|
+
|
|
276
|
+
for (const result of this.testResults) {
|
|
277
|
+
const category = result.category || "general";
|
|
278
|
+
|
|
279
|
+
if (!categories[category]) {
|
|
280
|
+
categories[category] = {
|
|
281
|
+
name: category,
|
|
282
|
+
total: 0,
|
|
283
|
+
passed: 0,
|
|
284
|
+
failed: 0,
|
|
285
|
+
results: []
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
categories[category].total++;
|
|
290
|
+
categories[category].passed += result.passed ? 1 : 0;
|
|
291
|
+
categories[category].failed += result.passed ? 0 : 1;
|
|
292
|
+
categories[category].results.push(result);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Gera o HTML
|
|
296
|
+
const html = `
|
|
297
|
+
<!DOCTYPE html>
|
|
298
|
+
<html lang="pt-BR">
|
|
299
|
+
<head>
|
|
300
|
+
<meta charset="UTF-8">
|
|
301
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
302
|
+
<title>Relatório de Testes - @cognima/banners</title>
|
|
303
|
+
<style>
|
|
304
|
+
body {
|
|
305
|
+
font-family: Arial, sans-serif;
|
|
306
|
+
line-height: 1.6;
|
|
307
|
+
color: #333;
|
|
308
|
+
max-width: 1200px;
|
|
309
|
+
margin: 0 auto;
|
|
310
|
+
padding: 20px;
|
|
311
|
+
}
|
|
312
|
+
h1, h2, h3 {
|
|
313
|
+
color: #444;
|
|
314
|
+
}
|
|
315
|
+
.summary {
|
|
316
|
+
display: flex;
|
|
317
|
+
justify-content: space-between;
|
|
318
|
+
background-color: #f5f5f5;
|
|
319
|
+
padding: 20px;
|
|
320
|
+
border-radius: 5px;
|
|
321
|
+
margin-bottom: 20px;
|
|
322
|
+
}
|
|
323
|
+
.summary-item {
|
|
324
|
+
text-align: center;
|
|
325
|
+
}
|
|
326
|
+
.summary-item h3 {
|
|
327
|
+
margin: 0;
|
|
328
|
+
}
|
|
329
|
+
.summary-item p {
|
|
330
|
+
font-size: 24px;
|
|
331
|
+
font-weight: bold;
|
|
332
|
+
margin: 10px 0;
|
|
333
|
+
}
|
|
334
|
+
.progress-bar {
|
|
335
|
+
height: 20px;
|
|
336
|
+
background-color: #e0e0e0;
|
|
337
|
+
border-radius: 10px;
|
|
338
|
+
margin-bottom: 20px;
|
|
339
|
+
overflow: hidden;
|
|
340
|
+
}
|
|
341
|
+
.progress {
|
|
342
|
+
height: 100%;
|
|
343
|
+
background-color: #4CAF50;
|
|
344
|
+
border-radius: 10px;
|
|
345
|
+
}
|
|
346
|
+
.category {
|
|
347
|
+
margin-bottom: 30px;
|
|
348
|
+
border: 1px solid #ddd;
|
|
349
|
+
border-radius: 5px;
|
|
350
|
+
overflow: hidden;
|
|
351
|
+
}
|
|
352
|
+
.category-header {
|
|
353
|
+
background-color: #f5f5f5;
|
|
354
|
+
padding: 10px 20px;
|
|
355
|
+
cursor: pointer;
|
|
356
|
+
display: flex;
|
|
357
|
+
justify-content: space-between;
|
|
358
|
+
align-items: center;
|
|
359
|
+
}
|
|
360
|
+
.category-body {
|
|
361
|
+
padding: 0 20px;
|
|
362
|
+
max-height: 0;
|
|
363
|
+
overflow: hidden;
|
|
364
|
+
transition: max-height 0.3s ease;
|
|
365
|
+
}
|
|
366
|
+
.category-body.open {
|
|
367
|
+
max-height: 2000px;
|
|
368
|
+
padding: 20px;
|
|
369
|
+
}
|
|
370
|
+
.test-item {
|
|
371
|
+
margin-bottom: 15px;
|
|
372
|
+
padding: 15px;
|
|
373
|
+
border-radius: 5px;
|
|
374
|
+
}
|
|
375
|
+
.test-passed {
|
|
376
|
+
background-color: #e8f5e9;
|
|
377
|
+
border-left: 5px solid #4CAF50;
|
|
378
|
+
}
|
|
379
|
+
.test-failed {
|
|
380
|
+
background-color: #ffebee;
|
|
381
|
+
border-left: 5px solid #f44336;
|
|
382
|
+
}
|
|
383
|
+
.test-header {
|
|
384
|
+
display: flex;
|
|
385
|
+
justify-content: space-between;
|
|
386
|
+
margin-bottom: 10px;
|
|
387
|
+
}
|
|
388
|
+
.test-name {
|
|
389
|
+
font-weight: bold;
|
|
390
|
+
}
|
|
391
|
+
.test-duration {
|
|
392
|
+
color: #777;
|
|
393
|
+
}
|
|
394
|
+
.test-error {
|
|
395
|
+
background-color: #fff;
|
|
396
|
+
padding: 10px;
|
|
397
|
+
border-radius: 5px;
|
|
398
|
+
margin-top: 10px;
|
|
399
|
+
white-space: pre-wrap;
|
|
400
|
+
font-family: monospace;
|
|
401
|
+
}
|
|
402
|
+
.badge {
|
|
403
|
+
display: inline-block;
|
|
404
|
+
padding: 3px 8px;
|
|
405
|
+
border-radius: 12px;
|
|
406
|
+
font-size: 12px;
|
|
407
|
+
font-weight: bold;
|
|
408
|
+
color: white;
|
|
409
|
+
}
|
|
410
|
+
.badge-success {
|
|
411
|
+
background-color: #4CAF50;
|
|
412
|
+
}
|
|
413
|
+
.badge-danger {
|
|
414
|
+
background-color: #f44336;
|
|
415
|
+
}
|
|
416
|
+
.timestamp {
|
|
417
|
+
text-align: right;
|
|
418
|
+
color: #777;
|
|
419
|
+
margin-top: 30px;
|
|
420
|
+
}
|
|
421
|
+
</style>
|
|
422
|
+
</head>
|
|
423
|
+
<body>
|
|
424
|
+
<h1>Relatório de Testes - @cognima/banners</h1>
|
|
425
|
+
|
|
426
|
+
<div class="summary">
|
|
427
|
+
<div class="summary-item">
|
|
428
|
+
<h3>Total</h3>
|
|
429
|
+
<p>${total}</p>
|
|
430
|
+
</div>
|
|
431
|
+
<div class="summary-item">
|
|
432
|
+
<h3>Passou</h3>
|
|
433
|
+
<p style="color: #4CAF50">${passed}</p>
|
|
434
|
+
</div>
|
|
435
|
+
<div class="summary-item">
|
|
436
|
+
<h3>Falhou</h3>
|
|
437
|
+
<p style="color: #f44336">${failed}</p>
|
|
438
|
+
</div>
|
|
439
|
+
<div class="summary-item">
|
|
440
|
+
<h3>Taxa de Sucesso</h3>
|
|
441
|
+
<p>${passRate}%</p>
|
|
442
|
+
</div>
|
|
443
|
+
</div>
|
|
444
|
+
|
|
445
|
+
<div class="progress-bar">
|
|
446
|
+
<div class="progress" style="width: ${passRate}%"></div>
|
|
447
|
+
</div>
|
|
448
|
+
|
|
449
|
+
<h2>Resultados por Categoria</h2>
|
|
450
|
+
|
|
451
|
+
${Object.values(categories).map(category => `
|
|
452
|
+
<div class="category">
|
|
453
|
+
<div class="category-header" onclick="toggleCategory('${category.name}')">
|
|
454
|
+
<h3>${category.name}</h3>
|
|
455
|
+
<div>
|
|
456
|
+
<span class="badge badge-success">${category.passed}</span>
|
|
457
|
+
<span class="badge badge-danger">${category.failed}</span>
|
|
458
|
+
</div>
|
|
459
|
+
</div>
|
|
460
|
+
<div id="${category.name}" class="category-body">
|
|
461
|
+
${category.results.map(result => `
|
|
462
|
+
<div class="test-item ${result.passed ? 'test-passed' : 'test-failed'}">
|
|
463
|
+
<div class="test-header">
|
|
464
|
+
<span class="test-name">${result.name}</span>
|
|
465
|
+
<span class="test-duration">${result.duration}ms</span>
|
|
466
|
+
</div>
|
|
467
|
+
${!result.passed ? `
|
|
468
|
+
<div class="test-error">
|
|
469
|
+
${result.error.message}
|
|
470
|
+
</div>
|
|
471
|
+
` : ''}
|
|
472
|
+
</div>
|
|
473
|
+
`).join('')}
|
|
474
|
+
</div>
|
|
475
|
+
</div>
|
|
476
|
+
`).join('')}
|
|
477
|
+
|
|
478
|
+
<div class="timestamp">
|
|
479
|
+
Gerado em: ${new Date().toLocaleString()}
|
|
480
|
+
</div>
|
|
481
|
+
|
|
482
|
+
<script>
|
|
483
|
+
function toggleCategory(id) {
|
|
484
|
+
const element = document.getElementById(id);
|
|
485
|
+
element.classList.toggle('open');
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Abre categorias com falhas automaticamente
|
|
489
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
490
|
+
${Object.values(categories)
|
|
491
|
+
.filter(category => category.failed > 0)
|
|
492
|
+
.map(category => `document.getElementById('${category.name}').classList.add('open');`)
|
|
493
|
+
.join('\n ')}
|
|
494
|
+
});
|
|
495
|
+
</script>
|
|
496
|
+
</body>
|
|
497
|
+
</html>
|
|
498
|
+
`;
|
|
499
|
+
|
|
500
|
+
// Salva o relatório
|
|
501
|
+
fs.writeFileSync(reportPath, html);
|
|
502
|
+
|
|
503
|
+
return reportPath;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Salva uma imagem de teste
|
|
508
|
+
* @param {Buffer} imageData - Dados da imagem
|
|
509
|
+
* @param {string} name - Nome do arquivo
|
|
510
|
+
* @returns {Promise<string>} - Caminho do arquivo salvo
|
|
511
|
+
*/
|
|
512
|
+
async saveTestImage(imageData, name) {
|
|
513
|
+
const fileName = `${name}-${Date.now()}.png`;
|
|
514
|
+
const filePath = path.join(this.outputDir, fileName);
|
|
515
|
+
|
|
516
|
+
fs.writeFileSync(filePath, imageData);
|
|
517
|
+
|
|
518
|
+
return filePath;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Compara duas imagens
|
|
523
|
+
* @param {Buffer|string} image1 - Primeira imagem (buffer ou caminho)
|
|
524
|
+
* @param {Buffer|string} image2 - Segunda imagem (buffer ou caminho)
|
|
525
|
+
* @param {Object} options - Opções de comparação
|
|
526
|
+
* @returns {Promise<Object>} - Resultado da comparação
|
|
527
|
+
*/
|
|
528
|
+
async compareImages(image1, image2, options = {}) {
|
|
529
|
+
// Implementação simplificada - em um cenário real, usaríamos
|
|
530
|
+
// bibliotecas como pixelmatch ou resemblejs para comparação real
|
|
531
|
+
|
|
532
|
+
// Carrega as imagens se forem caminhos
|
|
533
|
+
const img1Buffer = typeof image1 === "string" ? fs.readFileSync(image1) : image1;
|
|
534
|
+
const img2Buffer = typeof image2 === "string" ? fs.readFileSync(image2) : image2;
|
|
535
|
+
|
|
536
|
+
// Compara os tamanhos dos buffers como uma aproximação muito básica
|
|
537
|
+
const sizeDiff = Math.abs(img1Buffer.length - img2Buffer.length);
|
|
538
|
+
const sizeRatio = Math.min(img1Buffer.length, img2Buffer.length) / Math.max(img1Buffer.length, img2Buffer.length);
|
|
539
|
+
|
|
540
|
+
// Considera as imagens "iguais" se a diferença de tamanho for pequena
|
|
541
|
+
const threshold = options.threshold || 0.05; // 5% de diferença por padrão
|
|
542
|
+
const match = sizeRatio > (1 - threshold);
|
|
543
|
+
|
|
544
|
+
return {
|
|
545
|
+
match,
|
|
546
|
+
difference: 1 - sizeRatio,
|
|
547
|
+
details: {
|
|
548
|
+
size1: img1Buffer.length,
|
|
549
|
+
size2: img2Buffer.length,
|
|
550
|
+
sizeDiff
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Cria um teste de banner
|
|
557
|
+
* @param {Function} bannerGenerator - Função geradora de banner
|
|
558
|
+
* @param {Object} params - Parâmetros para o banner
|
|
559
|
+
* @param {Object} options - Opções do teste
|
|
560
|
+
* @returns {Function} - Função de teste
|
|
561
|
+
*/
|
|
562
|
+
createBannerTest(bannerGenerator, params, options = {}) {
|
|
563
|
+
const {
|
|
564
|
+
name = "Banner Test",
|
|
565
|
+
saveOutput = true,
|
|
566
|
+
compareWithReference = false,
|
|
567
|
+
referencePath = null,
|
|
568
|
+
threshold = 0.05
|
|
569
|
+
} = options;
|
|
570
|
+
|
|
571
|
+
return async () => {
|
|
572
|
+
try {
|
|
573
|
+
// Gera o banner
|
|
574
|
+
const buffer = await bannerGenerator(params);
|
|
575
|
+
|
|
576
|
+
if (!buffer || !(buffer instanceof Buffer)) {
|
|
577
|
+
throw new Error("O gerador de banner não retornou um buffer válido.");
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Salva a saída se necessário
|
|
581
|
+
let outputPath = null;
|
|
582
|
+
|
|
583
|
+
if (saveOutput) {
|
|
584
|
+
outputPath = await this.saveTestImage(buffer, name.replace(/\s+/g, "-").toLowerCase());
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Compara com a referência se necessário
|
|
588
|
+
let comparisonResult = null;
|
|
589
|
+
|
|
590
|
+
if (compareWithReference && referencePath) {
|
|
591
|
+
comparisonResult = await this.compareImages(buffer, referencePath, { threshold });
|
|
592
|
+
|
|
593
|
+
if (!comparisonResult.match) {
|
|
594
|
+
throw new Error(`O banner gerado não corresponde à referência. Diferença: ${comparisonResult.difference * 100}%`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
return {
|
|
599
|
+
success: true,
|
|
600
|
+
outputPath,
|
|
601
|
+
comparisonResult
|
|
602
|
+
};
|
|
603
|
+
} catch (err) {
|
|
604
|
+
this.errorHandler.log("error", `Falha no teste de banner: ${name}`, {
|
|
605
|
+
message: err.message,
|
|
606
|
+
params
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
throw err;
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Cria um conjunto de testes de banner
|
|
616
|
+
* @param {Function} bannerGenerator - Função geradora de banner
|
|
617
|
+
* @param {Array<Object>} testCases - Casos de teste
|
|
618
|
+
* @param {Object} options - Opções dos testes
|
|
619
|
+
* @returns {Object} - Conjunto de testes
|
|
620
|
+
*/
|
|
621
|
+
createBannerTestSuite(bannerGenerator, testCases, options = {}) {
|
|
622
|
+
const tests = {};
|
|
623
|
+
|
|
624
|
+
for (const testCase of testCases) {
|
|
625
|
+
const { name, params, ...testOptions } = testCase;
|
|
626
|
+
|
|
627
|
+
tests[name] = {
|
|
628
|
+
fn: this.createBannerTest(bannerGenerator, params, {
|
|
629
|
+
name,
|
|
630
|
+
...options,
|
|
631
|
+
...testOptions
|
|
632
|
+
}),
|
|
633
|
+
options: {
|
|
634
|
+
category: options.category || "banner",
|
|
635
|
+
...testOptions
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return tests;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
module.exports = TestRunner;
|
|
645
|
+
|