lib-e2e-cypress-for-dummys-ts 0.2.0 → 0.3.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/dist/index.js CHANGED
@@ -92,7 +92,9 @@ var I18N_ES = {
92
92
  NO_TAGS: "Sin etiquetas",
93
93
  SAVE_BTN: "\u{1F4BE} Guardar",
94
94
  SAVE_AND_EDIT: "\u{1F4DD} Guardar y editar",
95
- REMOVE_TAG_TITLE: "Quitar"
95
+ REMOVE_TAG_TITLE: "Quitar",
96
+ NOTES_LABEL: "Descripci\xF3n / Notas (opcional):",
97
+ NOTES_PLACEHOLDER: "Qu\xE9 valida este test, condiciones previas, etc."
96
98
  },
97
99
  TEST_EDITOR: {
98
100
  NO_TAGS: "Sin etiquetas",
@@ -141,7 +143,10 @@ var I18N_ES = {
141
143
  JSON_BAD_FORMAT: "El archivo no tiene el formato esperado.",
142
144
  SMART_SELECTOR_SECTION: "\u{1F50D} Selector inteligente",
143
145
  SMART_SELECTOR_TITLE: "Selector inteligente",
144
- SMART_SELECTOR_SUB: "Muestra un picker al hacer click en elementos sin selector v\xE1lido."
146
+ SMART_SELECTOR_SUB: "Muestra un picker al hacer click en elementos sin selector v\xE1lido.",
147
+ START_HIDDEN_SECTION: "\u{1F441} Visibilidad del widget",
148
+ START_HIDDEN_TITLE: "Iniciar oculto",
149
+ START_HIDDEN_SUB: "El widget arranca invisible. Usa Ctrl+Shift+E para mostrarlo u ocultarlo."
145
150
  },
146
151
  SELECTOR_PICKER: {
147
152
  TITLE: "Selecciona un elemento del DOM",
@@ -254,7 +259,9 @@ var I18N_EN = {
254
259
  NO_TAGS: "No tags",
255
260
  SAVE_BTN: "\u{1F4BE} Save",
256
261
  SAVE_AND_EDIT: "\u{1F4DD} Save and edit",
257
- REMOVE_TAG_TITLE: "Remove"
262
+ REMOVE_TAG_TITLE: "Remove",
263
+ NOTES_LABEL: "Description / Notes (optional):",
264
+ NOTES_PLACEHOLDER: "What this test validates, preconditions, etc."
258
265
  },
259
266
  TEST_EDITOR: {
260
267
  NO_TAGS: "No tags",
@@ -303,7 +310,10 @@ var I18N_EN = {
303
310
  JSON_BAD_FORMAT: "The file does not have the expected format.",
304
311
  SMART_SELECTOR_SECTION: "\u{1F50D} Smart selector",
305
312
  SMART_SELECTOR_TITLE: "Smart selector",
306
- SMART_SELECTOR_SUB: "Shows a picker when clicking elements with no valid selector."
313
+ SMART_SELECTOR_SUB: "Shows a picker when clicking elements with no valid selector.",
314
+ START_HIDDEN_SECTION: "\u{1F441} Widget visibility",
315
+ START_HIDDEN_TITLE: "Start hidden",
316
+ START_HIDDEN_SUB: "The widget starts invisible. Use Ctrl+Shift+E to show or hide it."
307
317
  },
308
318
  SELECTOR_PICKER: {
309
319
  TITLE: "Select a DOM element",
@@ -416,7 +426,9 @@ var I18N_FR = {
416
426
  NO_TAGS: "Sans \xE9tiquettes",
417
427
  SAVE_BTN: "\u{1F4BE} Enregistrer",
418
428
  SAVE_AND_EDIT: "\u{1F4DD} Enregistrer et \xE9diter",
419
- REMOVE_TAG_TITLE: "Retirer"
429
+ REMOVE_TAG_TITLE: "Retirer",
430
+ NOTES_LABEL: "Description / Notes (optionnel) :",
431
+ NOTES_PLACEHOLDER: "Ce que ce test valide, conditions pr\xE9alables, etc."
420
432
  },
421
433
  TEST_EDITOR: {
422
434
  NO_TAGS: "Sans \xE9tiquettes",
@@ -465,7 +477,10 @@ var I18N_FR = {
465
477
  JSON_BAD_FORMAT: "Le fichier n'a pas le format attendu.",
466
478
  SMART_SELECTOR_SECTION: "\u{1F50D} S\xE9lecteur intelligent",
467
479
  SMART_SELECTOR_TITLE: "S\xE9lecteur intelligent",
468
- SMART_SELECTOR_SUB: "Affiche un s\xE9lecteur lors du clic sur des \xE9l\xE9ments sans s\xE9lecteur valide."
480
+ SMART_SELECTOR_SUB: "Affiche un s\xE9lecteur lors du clic sur des \xE9l\xE9ments sans s\xE9lecteur valide.",
481
+ START_HIDDEN_SECTION: "\u{1F441} Visibilit\xE9 du widget",
482
+ START_HIDDEN_TITLE: "D\xE9marrer masqu\xE9",
483
+ START_HIDDEN_SUB: "Le widget d\xE9marre invisible. Utilisez Ctrl+Shift+E pour l'afficher ou le masquer."
469
484
  },
470
485
  SELECTOR_PICKER: {
471
486
  TITLE: "S\xE9lectionnez un \xE9l\xE9ment du DOM",
@@ -578,7 +593,9 @@ var I18N_IT = {
578
593
  NO_TAGS: "Nessuna etichetta",
579
594
  SAVE_BTN: "\u{1F4BE} Salva",
580
595
  SAVE_AND_EDIT: "\u{1F4DD} Salva e modifica",
581
- REMOVE_TAG_TITLE: "Rimuovi"
596
+ REMOVE_TAG_TITLE: "Rimuovi",
597
+ NOTES_LABEL: "Descrizione / Note (opzionale):",
598
+ NOTES_PLACEHOLDER: "Cosa valida questo test, condizioni preliminari, ecc."
582
599
  },
583
600
  TEST_EDITOR: {
584
601
  NO_TAGS: "Nessuna etichetta",
@@ -627,7 +644,10 @@ var I18N_IT = {
627
644
  JSON_BAD_FORMAT: "Il file non ha il formato previsto.",
628
645
  SMART_SELECTOR_SECTION: "\u{1F50D} Selettore intelligente",
629
646
  SMART_SELECTOR_TITLE: "Selettore intelligente",
630
- SMART_SELECTOR_SUB: "Mostra un selettore quando si fa clic su elementi senza selettore valido."
647
+ SMART_SELECTOR_SUB: "Mostra un selettore quando si fa clic su elementi senza selettore valido.",
648
+ START_HIDDEN_SECTION: "\u{1F441} Visibilit\xE0 del widget",
649
+ START_HIDDEN_TITLE: "Avvia nascosto",
650
+ START_HIDDEN_SUB: "Il widget si avvia invisibile. Usa Ctrl+Shift+E per mostrarlo o nasconderlo."
631
651
  },
632
652
  SELECTOR_PICKER: {
633
653
  TITLE: "Seleziona un elemento DOM",
@@ -740,7 +760,9 @@ var I18N_DE = {
740
760
  NO_TAGS: "Keine Tags",
741
761
  SAVE_BTN: "\u{1F4BE} Speichern",
742
762
  SAVE_AND_EDIT: "\u{1F4DD} Speichern und bearbeiten",
743
- REMOVE_TAG_TITLE: "Entfernen"
763
+ REMOVE_TAG_TITLE: "Entfernen",
764
+ NOTES_LABEL: "Beschreibung / Notizen (optional):",
765
+ NOTES_PLACEHOLDER: "Was dieser Test validiert, Voraussetzungen usw."
744
766
  },
745
767
  TEST_EDITOR: {
746
768
  NO_TAGS: "Keine Tags",
@@ -789,7 +811,10 @@ var I18N_DE = {
789
811
  JSON_BAD_FORMAT: "Die Datei hat nicht das erwartete Format.",
790
812
  SMART_SELECTOR_SECTION: "\u{1F50D} Intelligenter Selektor",
791
813
  SMART_SELECTOR_TITLE: "Intelligenter Selektor",
792
- SMART_SELECTOR_SUB: "Zeigt ein Auswahlfeld, wenn auf Elemente ohne g\xFCltigen Selektor geklickt wird."
814
+ SMART_SELECTOR_SUB: "Zeigt ein Auswahlfeld, wenn auf Elemente ohne g\xFCltigen Selektor geklickt wird.",
815
+ START_HIDDEN_SECTION: "\u{1F441} Widget-Sichtbarkeit",
816
+ START_HIDDEN_TITLE: "Versteckt starten",
817
+ START_HIDDEN_SUB: "Das Widget startet unsichtbar. Verwenden Sie Ctrl+Shift+E zum Ein-/Ausblenden."
793
818
  },
794
819
  SELECTOR_PICKER: {
795
820
  TITLE: "DOM-Element ausw\xE4hlen",
@@ -915,13 +940,37 @@ var TranslationService = class {
915
940
  };
916
941
  var translationService = new TranslationService();
917
942
 
943
+ // src/utils/code-format.utils.ts
944
+ function gcd(a, b) {
945
+ while (b) {
946
+ const t = b;
947
+ b = a % b;
948
+ a = t;
949
+ }
950
+ return a;
951
+ }
952
+ function normalizeBlock(code, baseIndent) {
953
+ const lines = code.split("\n").map((l) => l.trimEnd());
954
+ const nonEmpty = lines.filter((l) => l.trim().length > 0);
955
+ if (!nonEmpty.length) return "";
956
+ const indents = nonEmpty.map((l) => l.length - l.trimStart().length);
957
+ const minIndent = Math.min(...indents);
958
+ const relIndents = [...new Set(indents.map((n) => n - minIndent).filter((n) => n > 0))];
959
+ const step = relIndents.length > 0 ? relIndents.reduce(gcd) : 2;
960
+ return lines.map((l) => {
961
+ if (!l.trim()) return "";
962
+ const level = Math.round((l.length - l.trimStart().length - minIndent) / step);
963
+ return baseIndent + " ".repeat(level) + l.trimStart();
964
+ }).join("\n");
965
+ }
966
+
918
967
  // src/services/transformation.service.ts
919
968
  var TransformationService = class {
920
969
  toLang(lang) {
921
970
  return isLang(lang) ? lang : "en";
922
971
  }
923
972
  generateItDescription(description, commands) {
924
- const commandsBlock = commands.map((cmd) => ` ${cmd}`).join("\n");
973
+ const commandsBlock = commands.map((cmd) => normalizeBlock(cmd, " ")).join("\n");
925
974
  return `it('${description}', () => {
926
975
  ${commandsBlock}
927
976
  });`;
@@ -952,6 +1001,13 @@ ${interceptors} })
952
1001
  }
953
1002
  return content.slice(0, idx) + "\n" + itBlock + "\n" + content.slice(idx);
954
1003
  }
1004
+ buildBlockComment(notes) {
1005
+ if (!notes.trim()) return "";
1006
+ const lines = notes.split("\n").map((l) => ` * ${l}`).join("\n");
1007
+ return `/**
1008
+ ${lines}
1009
+ */`;
1010
+ }
955
1011
  isFile(file) {
956
1012
  return !!file && file.kind === "file";
957
1013
  }
@@ -1362,9 +1418,9 @@ var PersistenceService = class {
1362
1418
  return this._db;
1363
1419
  }
1364
1420
  // ── Tests ─────────────────────────────────────────────────────────────────
1365
- async insertTest(name, commands = [], interceptors = [], tags = []) {
1421
+ async insertTest(name, commands = [], interceptors = [], tags = [], notes) {
1366
1422
  const db = await this.getDB();
1367
- const record = { name, createdAt: Date.now(), ...tags.length ? { tags } : {} };
1423
+ const record = { name, createdAt: Date.now(), ...tags.length ? { tags } : {}, ...notes ? { notes } : {} };
1368
1424
  const id = await db.add("tests", record);
1369
1425
  if (commands.length) await this.insertCommands(commands, id);
1370
1426
  if (interceptors.length) await this.insertInterceptors(interceptors, id);
@@ -1388,9 +1444,9 @@ var PersistenceService = class {
1388
1444
  const commands = await this.getCommandStrings(testId);
1389
1445
  const interceptors = await this.getInterceptorStrings(testId);
1390
1446
  const itBlock = `it('${record.name}', () => {
1391
- ${commands.join("\n ")}
1447
+ ${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
1392
1448
  });`;
1393
- const interceptorsBlock = interceptors.length ? " // Auto-generated Cypress interceptors\n" + interceptors.map((i) => " " + i).join("\n") + "\n" : "";
1449
+ const interceptorsBlock = interceptors.length ? " // Auto-generated Cypress interceptors\n" + interceptors.map((i) => normalizeBlock(i, " ")).join("\n") + "\n" : "";
1394
1450
  return { ...record, commands, interceptors, cypressCommands: commands, itBlock, interceptorsBlock };
1395
1451
  }
1396
1452
  async deleteTest(id) {
@@ -2129,7 +2185,8 @@ if (!customElements.get("test-previsualizer")) {
2129
2185
  var SAVE_TEST_STYLES = `
2130
2186
  :host { display: block; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e6edf3; }
2131
2187
  * { box-sizing: border-box; }
2132
- .container { padding: 24px 28px; text-align: center; }
2188
+ .container { padding: 24px 28px; text-align: left; }
2189
+ .btn-row, p { text-align: center; }
2133
2190
  p { margin: 0 0 20px; font-size: 14px; color: #8b949e; line-height: 1.5; }
2134
2191
  .btn-row { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin-top: 16px; }
2135
2192
  button {
@@ -2151,6 +2208,15 @@ var SAVE_TEST_STYLES = `
2151
2208
  }
2152
2209
  input[type="text"]:focus { border-color: #2f81f7; box-shadow: 0 0 0 3px rgba(47,129,247,0.15); }
2153
2210
  input[type="text"]::placeholder { color: #484f58; }
2211
+ textarea {
2212
+ width: 100%; padding: 10px 14px; border: 1px solid #30363d;
2213
+ border-radius: 8px; background: #0d1117; color: #e6edf3;
2214
+ font-size: 14px; outline: none; margin-bottom: 4px;
2215
+ font-family: inherit; resize: vertical; line-height: 1.5;
2216
+ transition: border-color 0.15s, box-shadow 0.15s;
2217
+ }
2218
+ textarea:focus { border-color: #2f81f7; box-shadow: 0 0 0 3px rgba(47,129,247,0.15); }
2219
+ textarea::placeholder { color: #484f58; }
2154
2220
  .tag-label {
2155
2221
  font-size: 11px; color: #484f58; text-align: left; margin-bottom: 5px; display: block;
2156
2222
  }
@@ -2189,7 +2255,7 @@ function renderSaveTestAsk(t) {
2189
2255
  </div>
2190
2256
  </div>`;
2191
2257
  }
2192
- function renderSaveTestDesc(description, tags, t) {
2258
+ function renderSaveTestDesc(description, notes, tags, t) {
2193
2259
  const chipsHtml = tags.map(
2194
2260
  (tag) => `<span class="chip">${escHtml(tag)}<button class="chip-del" data-tag="${escAttr(tag)}" title="${t("SAVE_TEST.REMOVE_TAG_TITLE")}">\u2715</button></span>`
2195
2261
  ).join("");
@@ -2198,6 +2264,8 @@ function renderSaveTestDesc(description, tags, t) {
2198
2264
  <p>${t("SAVE_TEST.DESC_LABEL")} (<code>it()</code>):</p>
2199
2265
  <input id="desc-input" type="text" placeholder="${t("SAVE_TEST.DESC_PLACEHOLDER")}"
2200
2266
  value="${escAttr(description)}" autocomplete="off" />
2267
+ <span class="tag-label">${t("SAVE_TEST.NOTES_LABEL")}</span>
2268
+ <textarea id="notes-input" rows="3" placeholder="${escAttr(t("SAVE_TEST.NOTES_PLACEHOLDER"))}">${escHtml(notes)}</textarea>
2201
2269
  <span class="tag-label">${t("SAVE_TEST.TAGS_LABEL")}</span>
2202
2270
  <div class="tag-input-row">
2203
2271
  <input id="tag-input" type="text" placeholder="${t("SAVE_TEST.TAGS_PLACEHOLDER")}" autocomplete="off" />
@@ -2217,6 +2285,7 @@ var SaveTestElement = class extends HTMLElement {
2217
2285
  shadow;
2218
2286
  _step = "ask";
2219
2287
  description = "";
2288
+ notes = "";
2220
2289
  tags = [];
2221
2290
  translation = translationService;
2222
2291
  constructor() {
@@ -2234,17 +2303,18 @@ var SaveTestElement = class extends HTMLElement {
2234
2303
  this.render();
2235
2304
  }
2236
2305
  confirmSave() {
2237
- this.dispatch("savetest", { description: this.description.trim(), tags: [...this.tags] });
2306
+ this.dispatch("savetest", { description: this.description.trim(), notes: this.notes, tags: [...this.tags] });
2238
2307
  }
2239
2308
  confirmSaveAndExport() {
2240
- this.dispatch("saveandexport", { description: this.description.trim(), tags: [...this.tags] });
2309
+ this.dispatch("saveandexport", { description: this.description.trim(), notes: this.notes, tags: [...this.tags] });
2241
2310
  }
2242
2311
  cancel() {
2243
- this.dispatch("savetest", { description: null, tags: [] });
2312
+ this.dispatch("savetest", { description: null, notes: "", tags: [] });
2244
2313
  }
2245
2314
  restartComponent() {
2246
2315
  this._step = "ask";
2247
2316
  this.description = "";
2317
+ this.notes = "";
2248
2318
  this.tags = [];
2249
2319
  this.render();
2250
2320
  }
@@ -2271,12 +2341,16 @@ var SaveTestElement = class extends HTMLElement {
2271
2341
  this.shadow.getElementById("btn-yes")?.addEventListener("click", () => this.askSave());
2272
2342
  this.shadow.getElementById("btn-no")?.addEventListener("click", () => this.cancel());
2273
2343
  } else {
2274
- this.shadow.innerHTML = `<style>${SAVE_TEST_STYLES}</style>${renderSaveTestDesc(this.description, this.tags, this.t.bind(this))}`;
2344
+ this.shadow.innerHTML = `<style>${SAVE_TEST_STYLES}</style>${renderSaveTestDesc(this.description, this.notes, this.tags, this.t.bind(this))}`;
2275
2345
  const descInput = this.shadow.getElementById("desc-input");
2346
+ const notesInput = this.shadow.getElementById("notes-input");
2276
2347
  const tagInput = this.shadow.getElementById("tag-input");
2277
2348
  descInput.addEventListener("input", () => {
2278
2349
  this.description = descInput.value;
2279
2350
  });
2351
+ notesInput.addEventListener("input", () => {
2352
+ this.notes = notesInput.value;
2353
+ });
2280
2354
  const tryAddTag = () => {
2281
2355
  this.addTag(tagInput.value);
2282
2356
  tagInput.value = "";
@@ -2382,15 +2456,124 @@ var TEST_EDITOR_STYLES = `
2382
2456
  background: #0d1117; padding: 10px 14px;
2383
2457
  border-top: 1px solid #21262d;
2384
2458
  }
2385
- .section-title { font-size: 10px; color: #484f58; text-transform: uppercase;
2386
- letter-spacing: 0.8px; margin-bottom: 6px; font-weight: 600; }
2387
- .cmd-list { font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
2388
- font-size: 11px; color: #c9d1d9; line-height: 1.8; }
2389
- .icp-list { font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
2390
- font-size: 11px; color: #3fb950; line-height: 1.8; margin-top: 10px; }
2391
- .copy-row { display: flex; gap: 6px; margin-top: 10px; }
2459
+ .test-notes {
2460
+ margin: 0 0 10px; padding: 8px 12px;
2461
+ background: rgba(47,129,247,0.06); border-left: 3px solid rgba(47,129,247,0.4);
2462
+ border-radius: 0 6px 6px 0; font-size: 12px; color: #8b949e;
2463
+ line-height: 1.6; white-space: pre-wrap; word-break: break-word;
2464
+ }
2465
+ .code-preview {
2466
+ margin: 0; padding: 10px 12px; background: #161b22;
2467
+ border-radius: 6px; border: 1px solid #21262d;
2468
+ font-size: 11px; color: #c9d1d9; line-height: 1.7;
2469
+ font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
2470
+ overflow-x: auto; white-space: pre;
2471
+ scrollbar-width: thin; scrollbar-color: #30363d transparent;
2472
+ }
2473
+ .code-preview::-webkit-scrollbar { height: 4px; }
2474
+ .code-preview::-webkit-scrollbar-thumb { background: #30363d; border-radius: 2px; }
2475
+ .code-preview-icp { margin-top: 8px; }
2476
+ .sh-kw { color: #ff7b72; }
2477
+ .sh-bi { color: #d2a8ff; }
2478
+ .sh-str { color: #a5d6ff; }
2479
+ .sh-cmt { color: #8b949e; font-style: italic; }
2480
+ .sh-num { color: #79c0ff; }
2392
2481
  `;
2393
2482
 
2483
+ // src/utils/syntax-highlight.utils.ts
2484
+ var KEYWORDS = /* @__PURE__ */ new Set([
2485
+ "describe",
2486
+ "it",
2487
+ "xit",
2488
+ "fit",
2489
+ "xdescribe",
2490
+ "fdescribe",
2491
+ "beforeEach",
2492
+ "afterEach",
2493
+ "before",
2494
+ "after",
2495
+ "context",
2496
+ "const",
2497
+ "let",
2498
+ "var",
2499
+ "function",
2500
+ "return",
2501
+ "if",
2502
+ "else",
2503
+ "import",
2504
+ "export",
2505
+ "from",
2506
+ "new",
2507
+ "this",
2508
+ "null",
2509
+ "undefined",
2510
+ "true",
2511
+ "false",
2512
+ "async",
2513
+ "await",
2514
+ "of",
2515
+ "in",
2516
+ "typeof",
2517
+ "void"
2518
+ ]);
2519
+ var BUILTINS = /* @__PURE__ */ new Set(["cy", "expect", "assert", "Cypress", "chai"]);
2520
+ function syntaxHighlight(code) {
2521
+ return code.split("\n").map(highlightLine).join("\n");
2522
+ }
2523
+ function highlightLine(line) {
2524
+ let result = "";
2525
+ let i = 0;
2526
+ const len = line.length;
2527
+ while (i < len) {
2528
+ if (line[i] === "/" && i + 1 < len && line[i + 1] === "/") {
2529
+ result += `<span class="sh-cmt">${escHtml(line.slice(i))}</span>`;
2530
+ return result;
2531
+ }
2532
+ const q = line[i];
2533
+ if (q === "'" || q === '"' || q === "`") {
2534
+ let j = i + 1;
2535
+ while (j < len) {
2536
+ if (line[j] === "\\") {
2537
+ j += 2;
2538
+ continue;
2539
+ }
2540
+ if (line[j] === q) {
2541
+ j++;
2542
+ break;
2543
+ }
2544
+ j++;
2545
+ }
2546
+ result += `<span class="sh-str">${escHtml(line.slice(i, j))}</span>`;
2547
+ i = j;
2548
+ continue;
2549
+ }
2550
+ if (/[0-9]/.test(line[i]) && (i === 0 || !/[a-zA-Z_$]/.test(line[i - 1]))) {
2551
+ let j = i;
2552
+ while (j < len && /[0-9.]/.test(line[j])) j++;
2553
+ result += `<span class="sh-num">${escHtml(line.slice(i, j))}</span>`;
2554
+ i = j;
2555
+ continue;
2556
+ }
2557
+ if (/[a-zA-Z_$]/.test(line[i])) {
2558
+ let j = i;
2559
+ while (j < len && /[a-zA-Z0-9_$]/.test(line[j])) j++;
2560
+ const word = line.slice(i, j);
2561
+ if (KEYWORDS.has(word)) {
2562
+ result += `<span class="sh-kw">${escHtml(word)}</span>`;
2563
+ } else if (BUILTINS.has(word)) {
2564
+ result += `<span class="sh-bi">${escHtml(word)}</span>`;
2565
+ } else {
2566
+ result += escHtml(word);
2567
+ }
2568
+ i = j;
2569
+ continue;
2570
+ }
2571
+ result += escHtml(line[i]);
2572
+ i++;
2573
+ }
2574
+ return result;
2575
+ }
2576
+
2394
2577
  // src/components/test-editor/test-editor.template.ts
2395
2578
  function renderTestEditor(state, t) {
2396
2579
  const { tags, visible, selectedVisible, activeTag, selectMode, selectedIds, describeName, expandedIndex, interceptorsByTest } = state;
@@ -2408,18 +2591,20 @@ function renderTestEditor(state, t) {
2408
2591
  const icps = interceptorsByTest[test.id] ?? test.interceptors ?? [];
2409
2592
  const tagsHtml = (test.tags ?? []).length ? `<span class="test-tags">${(test.tags ?? []).map((tag) => `<span class="test-tag">${escHtml(tag)}</span>`).join("")}</span>` : "";
2410
2593
  const checkbox = selectMode ? `<input type="checkbox" ${selectedIds.has(test.id) ? "checked" : ""} data-select="${test.id}" />` : "";
2594
+ const hasIcps = (test.interceptors ?? []).length > 0;
2595
+ const itBlockCode = `it('${test.name}', () => {
2596
+ ${(test.commands ?? []).map((c) => normalizeBlock(c, " ")).join("\n")}
2597
+ });`;
2598
+ const icpBlockCode = icps.length ? `beforeEach(() => {
2599
+ // Auto-generated Cypress interceptors
2600
+ ${icps.map((c) => normalizeBlock(c, " ")).join("\n")}
2601
+ });` : "";
2602
+ const notesHtml = expanded && test.notes ? `<p class="test-notes">${escHtml(test.notes)}</p>` : "";
2411
2603
  const body = expanded ? `
2412
2604
  <div class="row-body">
2413
- <div class="section-title">${t("TEST_EDITOR.SECTION_COMMANDS")} (${(test.commands ?? []).length})</div>
2414
- <div class="cmd-list">${(test.commands ?? []).map(escHtml).join("<br>")}</div>
2415
- ${icps.length ? `<div class="icp-list">
2416
- <div class="section-title" style="margin-top:8px">${t("TEST_EDITOR.SECTION_INTERCEPTORS")}</div>
2417
- ${icps.map(escHtml).join("<br>")}
2418
- </div>` : ""}
2419
- <div class="copy-row">
2420
- <button class="btn-icon" data-action="copy-cmds" data-idx="${i}">${t("TEST_EDITOR.COPY_CMDS_BTN")}</button>
2421
- ${icps.length ? `<button class="btn-icon" data-action="copy-icps" data-idx="${i}">${t("TEST_EDITOR.COPY_ICPS_BTN")}</button>` : ""}
2422
- </div>
2605
+ ${notesHtml}
2606
+ <pre class="code-preview">${syntaxHighlight(itBlockCode)}</pre>
2607
+ ${icpBlockCode ? `<pre class="code-preview code-preview-icp">${syntaxHighlight(icpBlockCode)}</pre>` : ""}
2423
2608
  </div>` : "";
2424
2609
  return `
2425
2610
  <div class="row${selectedIds.has(test.id) ? " selected-row" : ""}">
@@ -2428,6 +2613,8 @@ function renderTestEditor(state, t) {
2428
2613
  <span class="test-name">${escHtml(test.name)}</span>
2429
2614
  ${tagsHtml}
2430
2615
  <span class="test-date">${date}</span>
2616
+ <button class="btn-icon" data-action="copy-cmds" data-idx="${i}" title="${t("TEST_EDITOR.COPY_CMDS_BTN")}">${t("TEST_EDITOR.COPY_CMDS_BTN")}</button>
2617
+ ${hasIcps ? `<button class="btn-icon" data-action="copy-icps" data-idx="${i}" title="${t("TEST_EDITOR.COPY_ICPS_BTN")}">${t("TEST_EDITOR.COPY_ICPS_BTN")}</button>` : ""}
2431
2618
  <button class="btn-icon btn-del" data-action="delete" data-id="${test.id}" title="${t("TEST_EDITOR.DELETE_TITLE")}">\u{1F5D1}</button>
2432
2619
  </div>
2433
2620
  ${body}
@@ -2606,7 +2793,7 @@ ${beforeEach}${itBlocks}
2606
2793
  ev.stopPropagation();
2607
2794
  const idx = Number(el.dataset["idx"]);
2608
2795
  const t = visible[idx];
2609
- this.copyToClipboard((this.interceptorsByTest[t?.id] ?? []).join("\n"));
2796
+ this.copyToClipboard((this.interceptorsByTest[t?.id] ?? t?.interceptors ?? []).join("\n"));
2610
2797
  });
2611
2798
  });
2612
2799
  }
@@ -2719,7 +2906,7 @@ var LANGS = [
2719
2906
  { value: "de", label: "Deutsch" }
2720
2907
  ];
2721
2908
  function renderConfiguration(state, t) {
2722
- const { selectedLanguage, advancedHttpConfig, selectorStrategy, filesystemGranted, cypressFolderName, smartSelectorEnabled } = state;
2909
+ const { selectedLanguage, advancedHttpConfig, selectorStrategy, filesystemGranted, cypressFolderName, smartSelectorEnabled, startHidden } = state;
2723
2910
  const langOptions = LANGS.map(
2724
2911
  (l) => `<option value="${l.value}" ${selectedLanguage === l.value ? "selected" : ""}>${l.label}</option>`
2725
2912
  ).join("");
@@ -2759,6 +2946,18 @@ function renderConfiguration(state, t) {
2759
2946
  </label>
2760
2947
  </div>
2761
2948
 
2949
+ <!-- Start Hidden -->
2950
+ <div class="card">
2951
+ <div class="card-hd">${t("CONFIG.START_HIDDEN_SECTION")}</div>
2952
+ <label class="check-row">
2953
+ <input type="checkbox" id="start-hidden-toggle" ${startHidden ? "checked" : ""} />
2954
+ <div>
2955
+ <div class="check-title">${t("CONFIG.START_HIDDEN_TITLE")}</div>
2956
+ <div class="check-sub">${t("CONFIG.START_HIDDEN_SUB")}</div>
2957
+ </div>
2958
+ </label>
2959
+ </div>
2960
+
2762
2961
  <!-- Selector Strategy -->
2763
2962
  <div class="card card-wide">
2764
2963
  <div class="card-hd">${t("CONFIG.SELECTOR_SECTION")}</div>
@@ -2821,6 +3020,7 @@ var ConfigurationElement = class extends HTMLElement {
2821
3020
  advancedHttpConfig = false;
2822
3021
  selectorStrategy = "data-cy";
2823
3022
  smartSelectorEnabled = true;
3023
+ startHidden = false;
2824
3024
  filesystemGranted = false;
2825
3025
  cypressFolderName = null;
2826
3026
  constructor() {
@@ -2846,6 +3046,7 @@ var ConfigurationElement = class extends HTMLElement {
2846
3046
  this.advancedHttpConfig = localStorage.getItem("extendedHttpCommands") === "true";
2847
3047
  this.selectorStrategy = config?.["selectorStrategy"] ?? "data-cy";
2848
3048
  this.smartSelectorEnabled = config?.["smartSelectorEnabled"] !== "false";
3049
+ this.startHidden = config?.["startHidden"] === "true";
2849
3050
  this.filesystemGranted = config?.["allowReadWriteFiles"] === "true";
2850
3051
  const handle = config?.["cypressDirectoryHandle"];
2851
3052
  this.cypressFolderName = handle?.name ?? null;
@@ -2863,6 +3064,12 @@ var ConfigurationElement = class extends HTMLElement {
2863
3064
  this.persistence.setConfig({ extendedHttpCommands: checked ? "true" : "false" });
2864
3065
  this.render();
2865
3066
  }
3067
+ async onStartHiddenChange(checked) {
3068
+ this.startHidden = checked;
3069
+ await this.persistence.setConfig({ startHidden: checked ? "true" : "false" });
3070
+ this.dispatchEvent(new CustomEvent("starthiddenchange", { detail: checked, bubbles: true, composed: true }));
3071
+ this.render();
3072
+ }
2866
3073
  async onSmartSelectorChange(enabled) {
2867
3074
  this.smartSelectorEnabled = enabled;
2868
3075
  await this.persistence.setConfig({ smartSelectorEnabled: enabled ? "true" : "false" });
@@ -2923,7 +3130,8 @@ var ConfigurationElement = class extends HTMLElement {
2923
3130
  selectorStrategy: this.selectorStrategy,
2924
3131
  filesystemGranted: this.filesystemGranted,
2925
3132
  cypressFolderName: this.cypressFolderName,
2926
- smartSelectorEnabled: this.smartSelectorEnabled
3133
+ smartSelectorEnabled: this.smartSelectorEnabled,
3134
+ startHidden: this.startHidden
2927
3135
  }, this.t.bind(this))}`;
2928
3136
  ;
2929
3137
  this.shadow.getElementById("lang-select").addEventListener(
@@ -2938,6 +3146,10 @@ var ConfigurationElement = class extends HTMLElement {
2938
3146
  "change",
2939
3147
  (e) => this.onSmartSelectorChange(e.target.checked)
2940
3148
  );
3149
+ this.shadow.getElementById("start-hidden-toggle").addEventListener(
3150
+ "change",
3151
+ (e) => this.onStartHiddenChange(e.target.checked)
3152
+ );
2941
3153
  this.shadow.getElementById("selector-strategy").addEventListener(
2942
3154
  "change",
2943
3155
  (e) => this.onSelectorStrategyChange(e.target.value)
@@ -3010,12 +3222,21 @@ var ADVANCED_TEST_EDITOR_STYLES = `
3010
3222
  .file-name { font-size: 11px; color: #484f58; margin-bottom: 8px; font-family: monospace; }
3011
3223
  pre {
3012
3224
  background: #0d1117; padding: 12px; border-radius: 8px;
3013
- font-size: 11px; color: #c9d1d9; overflow-x: auto;
3014
- white-space: pre-wrap; word-break: break-all; margin: 0;
3225
+ font-size: 11px; color: #c9d1d9; margin: 0;
3015
3226
  font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
3016
- line-height: 1.7; overflow-y: auto;
3017
- border: 1px solid #21262d;
3227
+ line-height: 1.7; border: 1px solid #21262d;
3228
+ white-space: pre; overflow-x: auto; overflow-y: auto;
3229
+ scrollbar-width: thin; scrollbar-color: #30363d transparent;
3018
3230
  }
3231
+ pre::-webkit-scrollbar { width: 4px; height: 4px; }
3232
+ pre::-webkit-scrollbar-thumb { background: #30363d; border-radius: 2px; }
3233
+ .pre-block { font-size: 10.5px; }
3234
+ .pre-icp .sh-str { color: #85e89d; }
3235
+ .sh-kw { color: #ff7b72; }
3236
+ .sh-bi { color: #d2a8ff; }
3237
+ .sh-str { color: #a5d6ff; }
3238
+ .sh-cmt { color: #8b949e; font-style: italic; }
3239
+ .sh-num { color: #79c0ff; }
3019
3240
  .footer {
3020
3241
  padding: 10px 14px; border-top: 1px solid #21262d;
3021
3242
  display: flex; gap: 8px; justify-content: flex-end; background: #161b22;
@@ -3031,6 +3252,12 @@ var ADVANCED_TEST_EDITOR_STYLES = `
3031
3252
  .btn-save:hover { background: #1f6feb; border-color: #1f6feb; color: #fff; }
3032
3253
  .btn-save:disabled { background: #21262d; border-color: #30363d; color: #8b949e; }
3033
3254
  .placeholder { color: #484f58; font-size: 13px; padding: 28px; text-align: center; }
3255
+ .test-notes {
3256
+ margin: 0 0 10px; padding: 8px 12px;
3257
+ background: rgba(47,129,247,0.06); border-left: 3px solid rgba(47,129,247,0.4);
3258
+ border-radius: 0 6px 6px 0; font-size: 12px; color: #8b949e;
3259
+ line-height: 1.6; white-space: pre-wrap; word-break: break-word;
3260
+ }
3034
3261
  .block-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; }
3035
3262
  .btn-copy {
3036
3263
  padding: 3px 10px; border: 1px solid #30363d; border-radius: 5px; cursor: pointer;
@@ -3083,23 +3310,24 @@ function renderNoPermission(needsReauth, t) {
3083
3310
  </div>`;
3084
3311
  }
3085
3312
  function renderAdvancedEditor(state, t) {
3086
- const { e2eTree, selectedFile, selectedFileContent, testItBlock, interceptorsBlock, saveButtonEnabled, isCreatingFile, collapsedDirs, sidebarWidth } = state;
3313
+ const { e2eTree, selectedFile, selectedFileContent, testItBlock, interceptorsBlock, testNotes, saveButtonEnabled, isCreatingFile, collapsedDirs, sidebarWidth } = state;
3087
3314
  const treeHtml = e2eTree.length ? renderTree(e2eTree, selectedFile, collapsedDirs) : `<div class="tree-item" style="color:#6c7a99">${t("ADVANCED_EDITOR.NO_FILES")}</div>`;
3088
3315
  const contentHtml = selectedFileContent ? `<div class="file-name">\u{1F4C4} ${escHtml(selectedFile?.name ?? "")}</div>
3089
- <pre>${escHtml(selectedFileContent.slice(0, 4e3))}${selectedFileContent.length > 4e3 ? "\n..." : ""}</pre>` : `<div class="placeholder">${t("ADVANCED_EDITOR.SELECT_FILE")}</div>`;
3316
+ <pre class="pre-file">${syntaxHighlight(selectedFileContent.slice(0, 6e3))}${selectedFileContent.length > 6e3 ? "\n..." : ""}</pre>` : `<div class="placeholder">${t("ADVANCED_EDITOR.SELECT_FILE")}</div>`;
3317
+ const notesHtml = testItBlock && testNotes ? `<p class="test-notes">${escHtml(testNotes)}</p>` : "";
3090
3318
  const itHtml = testItBlock ? `<div style="margin-top:10px">
3091
3319
  <div class="file-name block-header">
3092
3320
  <span>${t("ADVANCED_EDITOR.IT_LABEL")}</span>
3093
3321
  <button id="btn-copy-it" class="btn-copy">${t("ADVANCED_EDITOR.COPY_IT_BTN")}</button>
3094
3322
  </div>
3095
- <pre style="max-height:120px;font-size:10.5px">${escHtml(testItBlock.slice(0, 500))}</pre>
3323
+ <pre class="pre-block">${syntaxHighlight(testItBlock.slice(0, 3e3))}</pre>
3096
3324
  </div>` : "";
3097
3325
  const interceptorsHtml = interceptorsBlock ? `<div style="margin-top:10px">
3098
3326
  <div class="file-name block-header">
3099
3327
  <span>${t("ADVANCED_EDITOR.BEFORE_EACH_LABEL")}</span>
3100
3328
  <button id="btn-copy-interceptors" class="btn-copy">${t("ADVANCED_EDITOR.COPY_ICP_BTN")}</button>
3101
3329
  </div>
3102
- <pre style="max-height:120px;font-size:10.5px;color:#3fb950">${escHtml(interceptorsBlock.slice(0, 500))}</pre>
3330
+ <pre class="pre-block pre-icp">${syntaxHighlight(interceptorsBlock.slice(0, 3e3))}</pre>
3103
3331
  </div>` : "";
3104
3332
  const newFileForm = isCreatingFile ? `<div class="new-file-form">
3105
3333
  <input id="input-new-file" type="text" placeholder="${escHtml(t("ADVANCED_EDITOR.NEW_FILE_PLACEHOLDER"))}" autocomplete="off" />
@@ -3123,7 +3351,7 @@ function renderAdvancedEditor(state, t) {
3123
3351
  </div>
3124
3352
  <div id="resize-handle" class="resize-handle"></div>
3125
3353
  <div class="main">
3126
- <div class="content-area">${contentHtml}${itHtml}${interceptorsHtml}</div>
3354
+ <div class="content-area">${contentHtml}${notesHtml}${itHtml}${interceptorsHtml}</div>
3127
3355
  <div class="footer">
3128
3356
  <button id="btn-save" class="btn-save"
3129
3357
  ${!saveButtonEnabled || !testItBlock ? "disabled" : ""}>
@@ -3184,6 +3412,7 @@ var AdvancedTestEditorElement = class extends HTMLElement {
3184
3412
  selectedFileContent = null;
3185
3413
  testItBlock = "";
3186
3414
  interceptorsBlock = "";
3415
+ testNotes = "";
3187
3416
  isPreviewMode = false;
3188
3417
  previewFileName = null;
3189
3418
  previewFileContent = null;
@@ -3277,7 +3506,8 @@ var AdvancedTestEditorElement = class extends HTMLElement {
3277
3506
  if (this.interceptorsBlock) {
3278
3507
  content = this.transformationService.insertBeforeEach(content, this.interceptorsBlock);
3279
3508
  }
3280
- content = this.transformationService.insertItBlock(content, this.testItBlock);
3509
+ const comment = this.testNotes ? this.transformationService.buildBlockComment(this.testNotes) + "\n" : "";
3510
+ content = this.transformationService.insertItBlock(content, comment + this.testItBlock);
3281
3511
  if (!content) return;
3282
3512
  const writable = await this.selectedFileHandle.createWritable();
3283
3513
  await writable.write(content);
@@ -3304,9 +3534,11 @@ var AdvancedTestEditorElement = class extends HTMLElement {
3304
3534
  if (test) {
3305
3535
  this.testItBlock = test.itBlock ?? "";
3306
3536
  this.interceptorsBlock = test.interceptorsBlock ?? "";
3537
+ this.testNotes = test.notes ?? "";
3307
3538
  } else {
3308
3539
  this.testItBlock = "";
3309
3540
  this.interceptorsBlock = "";
3541
+ this.testNotes = "";
3310
3542
  }
3311
3543
  this.render();
3312
3544
  }
@@ -3396,6 +3628,7 @@ var AdvancedTestEditorElement = class extends HTMLElement {
3396
3628
  selectedFileContent: this.selectedFileContent,
3397
3629
  testItBlock: this.testItBlock,
3398
3630
  interceptorsBlock: this.interceptorsBlock,
3631
+ testNotes: this.testNotes,
3399
3632
  saveButtonEnabled: this.saveButtonEnabled,
3400
3633
  isCreatingFile: this.isCreatingFile,
3401
3634
  collapsedDirs: this.collapsedDirs,
@@ -3962,6 +4195,7 @@ var LibE2eRecorderElement = class extends HTMLElement {
3962
4195
  recording;
3963
4196
  persistence;
3964
4197
  translation;
4198
+ isVisible = false;
3965
4199
  isRecording = false;
3966
4200
  isPaused = false;
3967
4201
  cypressCommands = [];
@@ -3996,6 +4230,7 @@ var LibE2eRecorderElement = class extends HTMLElement {
3996
4230
  this.initSelectorStrategy();
3997
4231
  this.initSubscriptions();
3998
4232
  this.render();
4233
+ this.initVisibility();
3999
4234
  this.keydownHandler = (e) => this.handleKeyboardEvent(e);
4000
4235
  window.addEventListener("keydown", this.keydownHandler);
4001
4236
  }
@@ -4061,6 +4296,16 @@ var LibE2eRecorderElement = class extends HTMLElement {
4061
4296
  document.body.appendChild(picker);
4062
4297
  });
4063
4298
  }
4299
+ async initVisibility() {
4300
+ if (this.hasAttribute("start-hidden")) {
4301
+ this.isVisible = this.getAttribute("start-hidden") === "false";
4302
+ this.style.display = this.isVisible ? "" : "none";
4303
+ return;
4304
+ }
4305
+ const config = await this.persistence.getGeneralConfig();
4306
+ this.isVisible = config?.["startHidden"] !== "true";
4307
+ this.style.display = this.isVisible ? "" : "none";
4308
+ }
4064
4309
  async initSelectorStrategy() {
4065
4310
  const config = await this.persistence.getGeneralConfig();
4066
4311
  const strategy = config?.["selectorStrategy"];
@@ -4080,6 +4325,12 @@ var LibE2eRecorderElement = class extends HTMLElement {
4080
4325
  handleKeyboardEvent(event) {
4081
4326
  if (!event.ctrlKey) return;
4082
4327
  const key = event.key.toLowerCase();
4328
+ if (key === "e" && event.shiftKey) {
4329
+ event.preventDefault();
4330
+ this.toggleVisibility();
4331
+ return;
4332
+ }
4333
+ if (!this.isVisible) return;
4083
4334
  if (key === "r") {
4084
4335
  event.preventDefault();
4085
4336
  this.toggle();
@@ -4097,6 +4348,10 @@ var LibE2eRecorderElement = class extends HTMLElement {
4097
4348
  this.showSettingsDialog();
4098
4349
  }
4099
4350
  }
4351
+ toggleVisibility() {
4352
+ this.isVisible = !this.isVisible;
4353
+ this.style.display = this.isVisible ? "" : "none";
4354
+ }
4100
4355
  // ── recording history (task 5) ────────────────────────────────────────────
4101
4356
  saveRecordingHistory() {
4102
4357
  if (!this.cypressCommands.length) return;
@@ -4332,13 +4587,13 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
4332
4587
  child.translation = this.translation;
4333
4588
  container.appendChild(child);
4334
4589
  child.addEventListener("savetest", (e) => {
4335
- const { description, tags } = e.detail ?? {};
4336
- this.onSaveTest(description ?? null, tags ?? []);
4590
+ const { description, notes, tags } = e.detail ?? {};
4591
+ this.onSaveTest(description ?? null, tags ?? [], notes ?? "");
4337
4592
  Swal.close();
4338
4593
  });
4339
4594
  child.addEventListener("saveandexport", (e) => {
4340
- const { description, tags } = e.detail ?? {};
4341
- this.onSaveAndExportTest(description ?? null, tags ?? []);
4595
+ const { description, notes, tags } = e.detail ?? {};
4596
+ this.onSaveAndExportTest(description ?? null, tags ?? [], notes ?? "");
4342
4597
  Swal.close();
4343
4598
  });
4344
4599
  },
@@ -4369,6 +4624,10 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
4369
4624
  child.addEventListener("smartselectorchange", (e) => {
4370
4625
  this.smartSelectorEnabled = e.detail;
4371
4626
  });
4627
+ child.addEventListener("starthiddenchange", (e) => {
4628
+ this.isVisible = !e.detail;
4629
+ this.style.display = this.isVisible ? "" : "none";
4630
+ });
4372
4631
  },
4373
4632
  willClose: () => {
4374
4633
  this.isSettingsDialogOpen = false;
@@ -4480,18 +4739,18 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
4480
4739
  this.resizePopup();
4481
4740
  }
4482
4741
  // ── save handlers ─────────────────────────────────────────────────────────
4483
- async onSaveTest(description, tags = []) {
4742
+ async onSaveTest(description, tags = [], notes = "") {
4484
4743
  if (description) {
4485
- await this.persistence.insertTest(description, this.cypressCommands, this.interceptors, tags);
4744
+ await this.persistence.insertTest(description, this.cypressCommands, this.interceptors, tags, notes);
4486
4745
  this.recording.clearCommands();
4487
4746
  this.clearRecordingHistory();
4488
4747
  this.cypressCommands = [];
4489
4748
  this.interceptors = [];
4490
4749
  }
4491
4750
  }
4492
- async onSaveAndExportTest(description, tags = []) {
4751
+ async onSaveAndExportTest(description, tags = [], notes = "") {
4493
4752
  if (description) {
4494
- const id = await this.persistence.insertTest(description, this.cypressCommands, this.interceptors, tags);
4753
+ const id = await this.persistence.insertTest(description, this.cypressCommands, this.interceptors, tags, notes);
4495
4754
  this.recording.clearCommands();
4496
4755
  this.clearRecordingHistory();
4497
4756
  this.cypressCommands = [];
@@ -4518,6 +4777,7 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
4518
4777
  render() {
4519
4778
  const rec = this.isRecording;
4520
4779
  const paused = this.isPaused;
4780
+ this.style.display = this.isVisible ? "" : "none";
4521
4781
  this.shadow.innerHTML = `<style>${getRecorderStyles(rec, paused)}</style>${renderRecorderWidget(rec, paused, this.translation.translate.bind(this.translation))}`;
4522
4782
  this.shadow.querySelector('[data-action="toggle"]')?.addEventListener("click", () => this.toggle());
4523
4783
  this.shadow.querySelector('[data-action="pause"]')?.addEventListener("click", () => this.togglePause());