@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.
Files changed (48) hide show
  1. package/assets/fonts/Manrope/Manrope-Bold.ttf +0 -0
  2. package/assets/fonts/Manrope/Manrope-Regular.ttf +0 -0
  3. package/assets/fonts/Others/AbyssinicaSIL-Regular.ttf +0 -0
  4. package/assets/fonts/Others/ChirpRegular.ttf +0 -0
  5. package/assets/fonts/Poppins/Poppins-Bold.ttf +0 -0
  6. package/assets/fonts/Poppins/Poppins-Medium.ttf +0 -0
  7. package/assets/fonts/Poppins/Poppins-Regular.ttf +0 -0
  8. package/assets/placeholders/album_art.png +0 -0
  9. package/assets/placeholders/avatar.png +0 -0
  10. package/assets/placeholders/badge.jpg +0 -0
  11. package/assets/placeholders/badge.png +0 -0
  12. package/assets/placeholders/badge_2.jpg +0 -0
  13. package/assets/placeholders/badge_3.jpg +0 -0
  14. package/assets/placeholders/badge_4.jpg +0 -0
  15. package/assets/placeholders/badge_5.jpg +0 -0
  16. package/assets/placeholders/banner.jpeg +0 -0
  17. package/assets/placeholders/images.jpeg +0 -0
  18. package/index.js +153 -0
  19. package/package.json +34 -0
  20. package/src/animation-effects.js +631 -0
  21. package/src/cache-manager.js +258 -0
  22. package/src/community-banner.js +1536 -0
  23. package/src/constants.js +208 -0
  24. package/src/discord-profile.js +584 -0
  25. package/src/e-commerce-banner.js +1214 -0
  26. package/src/effects.js +355 -0
  27. package/src/error-handler.js +305 -0
  28. package/src/event-banner.js +1319 -0
  29. package/src/facebook-post.js +679 -0
  30. package/src/gradient-welcome.js +430 -0
  31. package/src/image-filters.js +1034 -0
  32. package/src/image-processor.js +1014 -0
  33. package/src/instagram-post.js +504 -0
  34. package/src/interactive-elements.js +1208 -0
  35. package/src/linkedin-post.js +658 -0
  36. package/src/marketing-banner.js +1089 -0
  37. package/src/minimalist-banner.js +892 -0
  38. package/src/modern-profile.js +755 -0
  39. package/src/performance-optimizer.js +216 -0
  40. package/src/telegram-header.js +544 -0
  41. package/src/test-runner.js +645 -0
  42. package/src/tiktok-post.js +713 -0
  43. package/src/twitter-header.js +604 -0
  44. package/src/validator.js +442 -0
  45. package/src/welcome-leave.js +445 -0
  46. package/src/whatsapp-status.js +386 -0
  47. package/src/youtube-thumbnail.js +681 -0
  48. 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
+