lib-e2e-cypress-for-dummys-ts 0.8.0 → 0.9.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.cjs CHANGED
@@ -463,20 +463,15 @@ __export(index_exports, {
463
463
  ACTIVE_SESSION_BREADCRUMB_KEY: () => ACTIVE_SESSION_BREADCRUMB_KEY,
464
464
  AdvancedTestEditorElement: () => AdvancedTestEditorElement,
465
465
  AdvancedTestTransformationService: () => AdvancedTestTransformationService,
466
- CHAIN_METHODS: () => CHAIN_METHODS,
467
- CYPRESS_COMPLETIONS: () => CYPRESS_COMPLETIONS,
468
- CY_METHODS: () => CY_METHODS,
469
466
  ConfigurationElement: () => ConfigurationElement,
470
467
  DB_SCHEMA: () => DB_SCHEMA,
471
468
  DB_STORE_NAMES: () => DB_STORE_NAMES,
472
- DEFAULT_ISSUE_TRACKER_PROVIDER_ID: () => DEFAULT_ISSUE_TRACKER_PROVIDER_ID,
473
469
  DEFAULT_RESUME_TTL_MINUTES: () => DEFAULT_RESUME_TTL_MINUTES,
474
470
  DRAG_THRESHOLD: () => DRAG_THRESHOLD,
475
471
  FilePreviewElement: () => FilePreviewElement,
476
472
  HelpPanelElement: () => HelpPanelElement,
477
473
  HttpMonitor: () => HttpMonitor,
478
474
  INPUT_TYPES: () => INPUT_TYPES,
479
- ISSUE_TRACKER_PROVIDERS: () => ISSUE_TRACKER_PROVIDERS,
480
475
  LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES: () => LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES,
481
476
  LOCALE_BY_LANG: () => LOCALE_BY_LANG,
482
477
  LibE2eRecorderElement: () => LibE2eRecorderElement,
@@ -489,7 +484,6 @@ __export(index_exports, {
489
484
  SelectorPickerElement: () => SelectorPickerElement,
490
485
  Subject: () => Subject,
491
486
  TOGGLE_MARGIN: () => TOGGLE_MARGIN,
492
- TOP_LEVEL: () => TOP_LEVEL,
493
487
  TestEditorElement: () => TestEditorElement,
494
488
  TestPrevisualizerElement: () => TestPrevisualizerElement,
495
489
  TransformationService: () => TransformationService,
@@ -498,14 +492,8 @@ __export(index_exports, {
498
492
  advancedTestTransformationService: () => advancedTestTransformationService,
499
493
  boxOffsetForDirection: () => boxOffsetForDirection,
500
494
  boxTopLeftFor: () => boxTopLeftFor,
501
- buildTicketComment: () => buildTicketComment,
502
- buildTicketUrl: () => buildTicketUrl,
503
495
  clampTogglePosition: () => clampTogglePosition,
504
- createCodeEditor: () => createCodeEditor,
505
496
  defaultTogglePosition: () => defaultTogglePosition,
506
- filterCompletions: () => filterCompletions,
507
- filterCypressCompletions: () => filterCypressCompletions,
508
- findIssueTrackerProvider: () => findIssueTrackerProvider,
509
497
  generateAlias: () => generateAlias,
510
498
  inferAssertionCommand: () => inferAssertionCommand,
511
499
  injectStyles: () => injectStyles,
@@ -517,7 +505,6 @@ __export(index_exports, {
517
505
  makeSwalDraggableByContentId: () => makeSwalDraggableByContentId,
518
506
  persistenceService: () => persistenceService,
519
507
  resolveExpandDirection: () => resolveExpandDirection,
520
- resolveTicketUrl: () => resolveTicketUrl,
521
508
  selectTestsForExport: () => selectTestsForExport,
522
509
  setSwal2DataCyAttribute: () => setSwal2DataCyAttribute,
523
510
  showToast: () => showToast,
@@ -616,64 +603,6 @@ var ACTIVE_SESSION_BREADCRUMB_KEY = "e2e-active-session";
616
603
  var RESUME_TTL_CONFIG_KEY = "resumeRecencyTtlMinutes";
617
604
  var DEFAULT_RESUME_TTL_MINUTES = 30;
618
605
 
619
- // src/models/issue-tracker.model.ts
620
- var ISSUE_TRACKER_PROVIDERS = [
621
- {
622
- id: "jira",
623
- labelKey: "CONFIG.ISSUE_TRACKER_PROVIDER_JIRA",
624
- urlTemplate: "{baseUrl}/browse/{id}",
625
- fields: [
626
- { key: "baseUrl", labelKey: "CONFIG.ISSUE_TRACKER_FIELD_BASEURL", placeholderKey: "CONFIG.ISSUE_TRACKER_PH_JIRA_BASEURL" }
627
- ],
628
- idPattern: "^[A-Za-z][A-Za-z0-9_]+-\\d+$",
629
- idPlaceholderKey: "SAVE_TEST.TICKET_PH_JIRA"
630
- },
631
- {
632
- id: "azure-devops",
633
- labelKey: "CONFIG.ISSUE_TRACKER_PROVIDER_AZURE",
634
- urlTemplate: "https://dev.azure.com/{org}/{project}/_workitems/edit/{id}",
635
- fields: [
636
- { key: "org", labelKey: "CONFIG.ISSUE_TRACKER_FIELD_ORG", placeholderKey: "CONFIG.ISSUE_TRACKER_PH_AZURE_ORG" },
637
- { key: "project", labelKey: "CONFIG.ISSUE_TRACKER_FIELD_PROJECT", placeholderKey: "CONFIG.ISSUE_TRACKER_PH_AZURE_PROJECT" }
638
- ],
639
- idPattern: "^\\d+$",
640
- idPlaceholderKey: "SAVE_TEST.TICKET_PH_AZURE"
641
- },
642
- {
643
- id: "github",
644
- labelKey: "CONFIG.ISSUE_TRACKER_PROVIDER_GITHUB",
645
- urlTemplate: "https://github.com/{owner}/{repo}/issues/{id}",
646
- fields: [
647
- { key: "owner", labelKey: "CONFIG.ISSUE_TRACKER_FIELD_OWNER", placeholderKey: "CONFIG.ISSUE_TRACKER_PH_GITHUB_OWNER" },
648
- { key: "repo", labelKey: "CONFIG.ISSUE_TRACKER_FIELD_REPO", placeholderKey: "CONFIG.ISSUE_TRACKER_PH_GITHUB_REPO" }
649
- ],
650
- idPattern: "^\\d+$",
651
- idPlaceholderKey: "SAVE_TEST.TICKET_PH_GITHUB"
652
- },
653
- {
654
- id: "trello",
655
- labelKey: "CONFIG.ISSUE_TRACKER_PROVIDER_TRELLO",
656
- urlTemplate: "https://trello.com/c/{id}",
657
- fields: [],
658
- idPattern: "^[A-Za-z0-9]+$",
659
- idPlaceholderKey: "SAVE_TEST.TICKET_PH_TRELLO"
660
- },
661
- {
662
- id: "custom",
663
- labelKey: "CONFIG.ISSUE_TRACKER_PROVIDER_CUSTOM",
664
- urlTemplate: "{template}",
665
- fields: [
666
- { key: "template", labelKey: "CONFIG.ISSUE_TRACKER_FIELD_TEMPLATE", placeholderKey: "CONFIG.ISSUE_TRACKER_PH_CUSTOM_TEMPLATE" }
667
- ],
668
- idPattern: ".*",
669
- idPlaceholderKey: "SAVE_TEST.TICKET_PH_CUSTOM"
670
- }
671
- ];
672
- var DEFAULT_ISSUE_TRACKER_PROVIDER_ID = "custom";
673
- function findIssueTrackerProvider(id) {
674
- return ISSUE_TRACKER_PROVIDERS.find((p) => p.id === id);
675
- }
676
-
677
606
  // src/utils/subject.ts
678
607
  var Subject = class {
679
608
  _value;
@@ -719,13 +648,9 @@ var I18N_ES = {
719
648
  REMOVE_TAG_TITLE: "Quitar",
720
649
  NOTES_LABEL: "Descripci\xF3n / Notas (opcional):",
721
650
  NOTES_PLACEHOLDER: "Qu\xE9 valida este test, condiciones previas, etc.",
722
- TICKET_LABEL: "Ticket (opcional):",
723
- TICKET_PH_JIRA: "ej. PROJ-123",
724
- TICKET_PH_AZURE: "ej. 1234",
725
- TICKET_PH_GITHUB: "ej. 42",
726
- TICKET_PH_TRELLO: "ej. aBc123",
727
- TICKET_PH_CUSTOM: "id del ticket",
728
- TICKET_INVALID: "El id del ticket no coincide con el formato esperado del gestor seleccionado."
651
+ TICKET_LABEL: "Ticket / Issue (opcional):",
652
+ TICKET_PLACEHOLDER: "Ej: PROJ-123, #42",
653
+ TICKET_WARN: "ID no v\xE1lido para el proveedor configurado (se guardar\xE1 igualmente)"
729
654
  },
730
655
  TEST_EDITOR: {
731
656
  NO_TAGS: "Sin etiquetas",
@@ -741,7 +666,9 @@ var I18N_ES = {
741
666
  NO_TESTS: "No hay tests guardados",
742
667
  DELETE_TITLE: "Eliminar",
743
668
  GROUP_BY_TICKET: "\u{1F3AB} Agrupar por ticket",
744
- NO_TICKET_GROUP: "Sin ticket"
669
+ UNGROUP_TICKET: "\u{1F3AB} Desagrupar",
670
+ NO_TICKET: "Sin ticket",
671
+ TICKET_LINK_TITLE: "Abrir ticket"
745
672
  },
746
673
  CONFIG: {
747
674
  LANG_SECTION: "\u{1F310} Idioma",
@@ -800,26 +727,12 @@ var I18N_ES = {
800
727
  WIDGET_POSITION_HINT: "Arrastra el bot\xF3n de grabar para moverlo donde no estorbe. Se recuerda su posici\xF3n.",
801
728
  WIDGET_POSITION_RESET_BTN: "\u21BA Restablecer posici\xF3n",
802
729
  WIDGET_POSITION_RESET_DONE: "\u2713 Posici\xF3n restablecida",
803
- ISSUE_TRACKER_SECTION: "\u{1F3AB} Gestor de incidencias",
804
- ISSUE_TRACKER_LABEL: "Gestor (solo enlace, sin sincronizaci\xF3n)",
805
- ISSUE_TRACKER_DISCLAIMER: "Solo genera enlaces \u2014 nunca se conecta ni sincroniza con la plataforma.",
806
- ISSUE_TRACKER_PROVIDER_JIRA: "Jira",
807
- ISSUE_TRACKER_PROVIDER_AZURE: "Azure DevOps",
808
- ISSUE_TRACKER_PROVIDER_GITHUB: "GitHub",
809
- ISSUE_TRACKER_PROVIDER_TRELLO: "Trello",
810
- ISSUE_TRACKER_PROVIDER_CUSTOM: "Personalizado",
811
- ISSUE_TRACKER_FIELD_BASEURL: "URL base",
812
- ISSUE_TRACKER_FIELD_ORG: "Organizaci\xF3n",
813
- ISSUE_TRACKER_FIELD_PROJECT: "Proyecto",
814
- ISSUE_TRACKER_FIELD_OWNER: "Propietario",
815
- ISSUE_TRACKER_FIELD_REPO: "Repositorio",
816
- ISSUE_TRACKER_FIELD_TEMPLATE: "Plantilla de URL (debe contener {id})",
817
- ISSUE_TRACKER_PH_JIRA_BASEURL: "https://tu-org.atlassian.net",
818
- ISSUE_TRACKER_PH_AZURE_ORG: "organizaci\xF3n",
819
- ISSUE_TRACKER_PH_AZURE_PROJECT: "proyecto",
820
- ISSUE_TRACKER_PH_GITHUB_OWNER: "propietario",
821
- ISSUE_TRACKER_PH_GITHUB_REPO: "repositorio",
822
- ISSUE_TRACKER_PH_CUSTOM_TEMPLATE: "https://tracker.example.com/{id}"
730
+ ISSUE_TRACKER_SECTION: "\u{1F3AB} Issue tracker",
731
+ ISSUE_TRACKER_TITLE: "Vincular tests a tickets",
732
+ ISSUE_TRACKER_SUB: "A\xF1ade un ID de ticket al guardar y genera un enlace clickable en el listado.",
733
+ ISSUE_TRACKER_PROVIDER: "Proveedor",
734
+ ISSUE_TRACKER_BASE_URL: "URL base",
735
+ ISSUE_TRACKER_BASE_URL_PH: "Ej: https://miproyecto.atlassian.net"
823
736
  },
824
737
  SELECTOR_PICKER: {
825
738
  TITLE: "Selecciona un elemento del DOM",
@@ -1022,13 +935,9 @@ var I18N_EN = {
1022
935
  REMOVE_TAG_TITLE: "Remove",
1023
936
  NOTES_LABEL: "Description / Notes (optional):",
1024
937
  NOTES_PLACEHOLDER: "What this test validates, preconditions, etc.",
1025
- TICKET_LABEL: "Ticket (optional):",
1026
- TICKET_PH_JIRA: "e.g. PROJ-123",
1027
- TICKET_PH_AZURE: "e.g. 1234",
1028
- TICKET_PH_GITHUB: "e.g. 42",
1029
- TICKET_PH_TRELLO: "e.g. aBc123",
1030
- TICKET_PH_CUSTOM: "ticket id",
1031
- TICKET_INVALID: "The ticket id does not match the expected format for the selected tracker."
938
+ TICKET_LABEL: "Ticket / Issue (optional):",
939
+ TICKET_PLACEHOLDER: "E.g.: PROJ-123, #42",
940
+ TICKET_WARN: "Invalid ID for the configured provider (will be saved anyway)"
1032
941
  },
1033
942
  TEST_EDITOR: {
1034
943
  NO_TAGS: "No tags",
@@ -1044,7 +953,9 @@ var I18N_EN = {
1044
953
  NO_TESTS: "No saved tests",
1045
954
  DELETE_TITLE: "Delete",
1046
955
  GROUP_BY_TICKET: "\u{1F3AB} Group by ticket",
1047
- NO_TICKET_GROUP: "No ticket"
956
+ UNGROUP_TICKET: "\u{1F3AB} Ungroup",
957
+ NO_TICKET: "No ticket",
958
+ TICKET_LINK_TITLE: "Open ticket"
1048
959
  },
1049
960
  CONFIG: {
1050
961
  LANG_SECTION: "\u{1F310} Language",
@@ -1104,25 +1015,11 @@ var I18N_EN = {
1104
1015
  WIDGET_POSITION_RESET_BTN: "\u21BA Reset position",
1105
1016
  WIDGET_POSITION_RESET_DONE: "\u2713 Position reset",
1106
1017
  ISSUE_TRACKER_SECTION: "\u{1F3AB} Issue tracker",
1107
- ISSUE_TRACKER_LABEL: "Tracker (link only, no sync)",
1108
- ISSUE_TRACKER_DISCLAIMER: "Builds links only \u2014 it never connects to or syncs with the platform.",
1109
- ISSUE_TRACKER_PROVIDER_JIRA: "Jira",
1110
- ISSUE_TRACKER_PROVIDER_AZURE: "Azure DevOps",
1111
- ISSUE_TRACKER_PROVIDER_GITHUB: "GitHub",
1112
- ISSUE_TRACKER_PROVIDER_TRELLO: "Trello",
1113
- ISSUE_TRACKER_PROVIDER_CUSTOM: "Custom",
1114
- ISSUE_TRACKER_FIELD_BASEURL: "Base URL",
1115
- ISSUE_TRACKER_FIELD_ORG: "Organization",
1116
- ISSUE_TRACKER_FIELD_PROJECT: "Project",
1117
- ISSUE_TRACKER_FIELD_OWNER: "Owner",
1118
- ISSUE_TRACKER_FIELD_REPO: "Repository",
1119
- ISSUE_TRACKER_FIELD_TEMPLATE: "URL template (must contain {id})",
1120
- ISSUE_TRACKER_PH_JIRA_BASEURL: "https://your-org.atlassian.net",
1121
- ISSUE_TRACKER_PH_AZURE_ORG: "organization",
1122
- ISSUE_TRACKER_PH_AZURE_PROJECT: "project",
1123
- ISSUE_TRACKER_PH_GITHUB_OWNER: "owner",
1124
- ISSUE_TRACKER_PH_GITHUB_REPO: "repository",
1125
- ISSUE_TRACKER_PH_CUSTOM_TEMPLATE: "https://tracker.example.com/{id}"
1018
+ ISSUE_TRACKER_TITLE: "Link tests to tickets",
1019
+ ISSUE_TRACKER_SUB: "Add a ticket ID when saving and generate a clickable link in the test list.",
1020
+ ISSUE_TRACKER_PROVIDER: "Provider",
1021
+ ISSUE_TRACKER_BASE_URL: "Base URL",
1022
+ ISSUE_TRACKER_BASE_URL_PH: "E.g.: https://myproject.atlassian.net"
1126
1023
  },
1127
1024
  SELECTOR_PICKER: {
1128
1025
  TITLE: "Select a DOM element",
@@ -1325,13 +1222,10 @@ var I18N_FR = {
1325
1222
  REMOVE_TAG_TITLE: "Retirer",
1326
1223
  NOTES_LABEL: "Description / Notes (optionnel) :",
1327
1224
  NOTES_PLACEHOLDER: "Ce que ce test valide, conditions pr\xE9alables, etc.",
1328
- TICKET_LABEL: "Ticket (optionnel) :",
1329
- TICKET_PH_JIRA: "p. ex. PROJ-123",
1330
- TICKET_PH_AZURE: "p. ex. 1234",
1331
- TICKET_PH_GITHUB: "p. ex. 42",
1332
- TICKET_PH_TRELLO: "p. ex. aBc123",
1333
- TICKET_PH_CUSTOM: "id du ticket",
1334
- TICKET_INVALID: "L'id du ticket ne correspond pas au format attendu par le gestionnaire s\xE9lectionn\xE9."
1225
+ TICKET_LABEL: "Ticket / Issue (optionnel) :",
1226
+ TICKET_PLACEHOLDER: "Ex : PROJ-123, #42",
1227
+ TICKET_WARN: "ID non valide pour le fournisseur configur\xE9 (sera enregistr\xE9 quand m\xEAme)"
1228
+ // TODO: translate
1335
1229
  },
1336
1230
  TEST_EDITOR: {
1337
1231
  NO_TAGS: "Sans \xE9tiquettes",
@@ -1347,7 +1241,9 @@ var I18N_FR = {
1347
1241
  NO_TESTS: "Aucun test enregistr\xE9",
1348
1242
  DELETE_TITLE: "Supprimer",
1349
1243
  GROUP_BY_TICKET: "\u{1F3AB} Grouper par ticket",
1350
- NO_TICKET_GROUP: "Sans ticket"
1244
+ UNGROUP_TICKET: "\u{1F3AB} D\xE9grouper",
1245
+ NO_TICKET: "Sans ticket",
1246
+ TICKET_LINK_TITLE: "Ouvrir le ticket"
1351
1247
  },
1352
1248
  CONFIG: {
1353
1249
  LANG_SECTION: "\u{1F310} Langue",
@@ -1407,25 +1303,11 @@ var I18N_FR = {
1407
1303
  WIDGET_POSITION_RESET_BTN: "\u21BA R\xE9initialiser la position",
1408
1304
  WIDGET_POSITION_RESET_DONE: "\u2713 Position r\xE9initialis\xE9e",
1409
1305
  ISSUE_TRACKER_SECTION: "\u{1F3AB} Gestionnaire de tickets",
1410
- ISSUE_TRACKER_LABEL: "Gestionnaire (lien uniquement, sans synchronisation)",
1411
- ISSUE_TRACKER_DISCLAIMER: "G\xE9n\xE8re uniquement des liens \u2014 ne se connecte ni ne se synchronise jamais avec la plateforme.",
1412
- ISSUE_TRACKER_PROVIDER_JIRA: "Jira",
1413
- ISSUE_TRACKER_PROVIDER_AZURE: "Azure DevOps",
1414
- ISSUE_TRACKER_PROVIDER_GITHUB: "GitHub",
1415
- ISSUE_TRACKER_PROVIDER_TRELLO: "Trello",
1416
- ISSUE_TRACKER_PROVIDER_CUSTOM: "Personnalis\xE9",
1417
- ISSUE_TRACKER_FIELD_BASEURL: "URL de base",
1418
- ISSUE_TRACKER_FIELD_ORG: "Organisation",
1419
- ISSUE_TRACKER_FIELD_PROJECT: "Projet",
1420
- ISSUE_TRACKER_FIELD_OWNER: "Propri\xE9taire",
1421
- ISSUE_TRACKER_FIELD_REPO: "D\xE9p\xF4t",
1422
- ISSUE_TRACKER_FIELD_TEMPLATE: "Mod\xE8le d'URL (doit contenir {id})",
1423
- ISSUE_TRACKER_PH_JIRA_BASEURL: "https://votre-org.atlassian.net",
1424
- ISSUE_TRACKER_PH_AZURE_ORG: "organisation",
1425
- ISSUE_TRACKER_PH_AZURE_PROJECT: "projet",
1426
- ISSUE_TRACKER_PH_GITHUB_OWNER: "propri\xE9taire",
1427
- ISSUE_TRACKER_PH_GITHUB_REPO: "d\xE9p\xF4t",
1428
- ISSUE_TRACKER_PH_CUSTOM_TEMPLATE: "https://tracker.example.com/{id}"
1306
+ ISSUE_TRACKER_TITLE: "Lier les tests aux tickets",
1307
+ ISSUE_TRACKER_SUB: "Ajoutez un ID de ticket lors de l'enregistrement et g\xE9n\xE9rez un lien cliquable.",
1308
+ ISSUE_TRACKER_PROVIDER: "Fournisseur",
1309
+ ISSUE_TRACKER_BASE_URL: "URL de base",
1310
+ ISSUE_TRACKER_BASE_URL_PH: "Ex : https://monprojet.atlassian.net"
1429
1311
  },
1430
1312
  SELECTOR_PICKER: {
1431
1313
  TITLE: "S\xE9lectionnez un \xE9l\xE9ment du DOM",
@@ -1629,13 +1511,10 @@ var I18N_IT = {
1629
1511
  REMOVE_TAG_TITLE: "Rimuovi",
1630
1512
  NOTES_LABEL: "Descrizione / Note (opzionale):",
1631
1513
  NOTES_PLACEHOLDER: "Cosa valida questo test, condizioni preliminari, ecc.",
1632
- TICKET_LABEL: "Ticket (opzionale):",
1633
- TICKET_PH_JIRA: "es. PROJ-123",
1634
- TICKET_PH_AZURE: "es. 1234",
1635
- TICKET_PH_GITHUB: "es. 42",
1636
- TICKET_PH_TRELLO: "es. aBc123",
1637
- TICKET_PH_CUSTOM: "id del ticket",
1638
- TICKET_INVALID: "L'id del ticket non corrisponde al formato previsto dal gestore selezionato."
1514
+ TICKET_LABEL: "Ticket / Issue (opzionale):",
1515
+ TICKET_PLACEHOLDER: "Es: PROJ-123, #42",
1516
+ TICKET_WARN: "ID non valido per il provider configurato (verr\xE0 salvato comunque)"
1517
+ // TODO: translate
1639
1518
  },
1640
1519
  TEST_EDITOR: {
1641
1520
  NO_TAGS: "Nessuna etichetta",
@@ -1651,7 +1530,9 @@ var I18N_IT = {
1651
1530
  NO_TESTS: "Nessun test salvato",
1652
1531
  DELETE_TITLE: "Elimina",
1653
1532
  GROUP_BY_TICKET: "\u{1F3AB} Raggruppa per ticket",
1654
- NO_TICKET_GROUP: "Senza ticket"
1533
+ UNGROUP_TICKET: "\u{1F3AB} Separa",
1534
+ NO_TICKET: "Senza ticket",
1535
+ TICKET_LINK_TITLE: "Apri ticket"
1655
1536
  },
1656
1537
  CONFIG: {
1657
1538
  LANG_SECTION: "\u{1F310} Lingua",
@@ -1710,26 +1591,12 @@ var I18N_IT = {
1710
1591
  WIDGET_POSITION_HINT: "Trascina il pulsante di registrazione per spostarlo. La posizione viene ricordata.",
1711
1592
  WIDGET_POSITION_RESET_BTN: "\u21BA Reimposta posizione",
1712
1593
  WIDGET_POSITION_RESET_DONE: "\u2713 Posizione reimpostata",
1713
- ISSUE_TRACKER_SECTION: "\u{1F3AB} Gestore ticket",
1714
- ISSUE_TRACKER_LABEL: "Gestore (solo link, senza sincronizzazione)",
1715
- ISSUE_TRACKER_DISCLAIMER: "Genera solo link \u2014 non si connette n\xE9 si sincronizza mai con la piattaforma.",
1716
- ISSUE_TRACKER_PROVIDER_JIRA: "Jira",
1717
- ISSUE_TRACKER_PROVIDER_AZURE: "Azure DevOps",
1718
- ISSUE_TRACKER_PROVIDER_GITHUB: "GitHub",
1719
- ISSUE_TRACKER_PROVIDER_TRELLO: "Trello",
1720
- ISSUE_TRACKER_PROVIDER_CUSTOM: "Personalizzato",
1721
- ISSUE_TRACKER_FIELD_BASEURL: "URL di base",
1722
- ISSUE_TRACKER_FIELD_ORG: "Organizzazione",
1723
- ISSUE_TRACKER_FIELD_PROJECT: "Progetto",
1724
- ISSUE_TRACKER_FIELD_OWNER: "Proprietario",
1725
- ISSUE_TRACKER_FIELD_REPO: "Repository",
1726
- ISSUE_TRACKER_FIELD_TEMPLATE: "Modello di URL (deve contenere {id})",
1727
- ISSUE_TRACKER_PH_JIRA_BASEURL: "https://tua-org.atlassian.net",
1728
- ISSUE_TRACKER_PH_AZURE_ORG: "organizzazione",
1729
- ISSUE_TRACKER_PH_AZURE_PROJECT: "progetto",
1730
- ISSUE_TRACKER_PH_GITHUB_OWNER: "proprietario",
1731
- ISSUE_TRACKER_PH_GITHUB_REPO: "repository",
1732
- ISSUE_TRACKER_PH_CUSTOM_TEMPLATE: "https://tracker.example.com/{id}"
1594
+ ISSUE_TRACKER_SECTION: "\u{1F3AB} Issue tracker",
1595
+ ISSUE_TRACKER_TITLE: "Collega i test ai ticket",
1596
+ ISSUE_TRACKER_SUB: "Aggiungi un ID ticket al salvataggio e genera un link cliccabile nell'elenco.",
1597
+ ISSUE_TRACKER_PROVIDER: "Provider",
1598
+ ISSUE_TRACKER_BASE_URL: "URL base",
1599
+ ISSUE_TRACKER_BASE_URL_PH: "Es: https://mioprogetto.atlassian.net"
1733
1600
  },
1734
1601
  SELECTOR_PICKER: {
1735
1602
  TITLE: "Seleziona un elemento DOM",
@@ -1933,13 +1800,10 @@ var I18N_DE = {
1933
1800
  REMOVE_TAG_TITLE: "Entfernen",
1934
1801
  NOTES_LABEL: "Beschreibung / Notizen (optional):",
1935
1802
  NOTES_PLACEHOLDER: "Was dieser Test validiert, Voraussetzungen usw.",
1936
- TICKET_LABEL: "Ticket (optional):",
1937
- TICKET_PH_JIRA: "z. B. PROJ-123",
1938
- TICKET_PH_AZURE: "z. B. 1234",
1939
- TICKET_PH_GITHUB: "z. B. 42",
1940
- TICKET_PH_TRELLO: "z. B. aBc123",
1941
- TICKET_PH_CUSTOM: "Ticket-ID",
1942
- TICKET_INVALID: "Die Ticket-ID entspricht nicht dem erwarteten Format des ausgew\xE4hlten Trackers."
1803
+ TICKET_LABEL: "Ticket / Issue (optional):",
1804
+ TICKET_PLACEHOLDER: "Z. B.: PROJ-123, #42",
1805
+ TICKET_WARN: "Ung\xFCltige ID f\xFCr den konfigurierten Anbieter (wird trotzdem gespeichert)"
1806
+ // TODO: translate
1943
1807
  },
1944
1808
  TEST_EDITOR: {
1945
1809
  NO_TAGS: "Keine Tags",
@@ -1955,7 +1819,9 @@ var I18N_DE = {
1955
1819
  NO_TESTS: "Keine gespeicherten Tests",
1956
1820
  DELETE_TITLE: "L\xF6schen",
1957
1821
  GROUP_BY_TICKET: "\u{1F3AB} Nach Ticket gruppieren",
1958
- NO_TICKET_GROUP: "Kein Ticket"
1822
+ UNGROUP_TICKET: "\u{1F3AB} Gruppierung aufheben",
1823
+ NO_TICKET: "Kein Ticket",
1824
+ TICKET_LINK_TITLE: "Ticket \xF6ffnen"
1959
1825
  },
1960
1826
  CONFIG: {
1961
1827
  LANG_SECTION: "\u{1F310} Sprache",
@@ -2015,25 +1881,11 @@ var I18N_DE = {
2015
1881
  WIDGET_POSITION_RESET_BTN: "\u21BA Position zur\xFCcksetzen",
2016
1882
  WIDGET_POSITION_RESET_DONE: "\u2713 Position zur\xFCckgesetzt",
2017
1883
  ISSUE_TRACKER_SECTION: "\u{1F3AB} Issue-Tracker",
2018
- ISSUE_TRACKER_LABEL: "Tracker (nur Link, keine Synchronisierung)",
2019
- ISSUE_TRACKER_DISCLAIMER: "Erstellt nur Links \u2014 verbindet oder synchronisiert sich nie mit der Plattform.",
2020
- ISSUE_TRACKER_PROVIDER_JIRA: "Jira",
2021
- ISSUE_TRACKER_PROVIDER_AZURE: "Azure DevOps",
2022
- ISSUE_TRACKER_PROVIDER_GITHUB: "GitHub",
2023
- ISSUE_TRACKER_PROVIDER_TRELLO: "Trello",
2024
- ISSUE_TRACKER_PROVIDER_CUSTOM: "Benutzerdefiniert",
2025
- ISSUE_TRACKER_FIELD_BASEURL: "Basis-URL",
2026
- ISSUE_TRACKER_FIELD_ORG: "Organisation",
2027
- ISSUE_TRACKER_FIELD_PROJECT: "Projekt",
2028
- ISSUE_TRACKER_FIELD_OWNER: "Eigent\xFCmer",
2029
- ISSUE_TRACKER_FIELD_REPO: "Repository",
2030
- ISSUE_TRACKER_FIELD_TEMPLATE: "URL-Vorlage (muss {id} enthalten)",
2031
- ISSUE_TRACKER_PH_JIRA_BASEURL: "https://ihre-org.atlassian.net",
2032
- ISSUE_TRACKER_PH_AZURE_ORG: "Organisation",
2033
- ISSUE_TRACKER_PH_AZURE_PROJECT: "Projekt",
2034
- ISSUE_TRACKER_PH_GITHUB_OWNER: "Eigent\xFCmer",
2035
- ISSUE_TRACKER_PH_GITHUB_REPO: "Repository",
2036
- ISSUE_TRACKER_PH_CUSTOM_TEMPLATE: "https://tracker.example.com/{id}"
1884
+ ISSUE_TRACKER_TITLE: "Tests mit Tickets verkn\xFCpfen",
1885
+ ISSUE_TRACKER_SUB: "F\xFCge beim Speichern eine Ticket-ID hinzu und erstelle einen klickbaren Link.",
1886
+ ISSUE_TRACKER_PROVIDER: "Anbieter",
1887
+ ISSUE_TRACKER_BASE_URL: "Basis-URL",
1888
+ ISSUE_TRACKER_BASE_URL_PH: "Z. B.: https://meinprojekt.atlassian.net"
2037
1889
  },
2038
1890
  SELECTOR_PICKER: {
2039
1891
  TITLE: "DOM-Element ausw\xE4hlen",
@@ -2402,6 +2254,7 @@ var RecordingService = class {
2402
2254
  }
2403
2255
  // ── Public API ────────────────────────────────────────────────────────────
2404
2256
  startRecording() {
2257
+ this.clearCommands();
2405
2258
  this.sessionId = createSessionId();
2406
2259
  this.startedAt = Date.now();
2407
2260
  this.isPaused$.next(false);
@@ -2751,6 +2604,13 @@ var RecordingService = class {
2751
2604
  }
2752
2605
  return;
2753
2606
  }
2607
+ if (!this.isInteractiveElement(target)) {
2608
+ const interactive = target.closest(
2609
+ 'button,a,input,select,textarea,label,summary,mat-option,[role="button"],[role="link"],[role="menuitem"],[role="option"],[role="tab"]'
2610
+ );
2611
+ if (!interactive) return;
2612
+ target = interactive;
2613
+ }
2754
2614
  }
2755
2615
  const tag = target.tagName.toLowerCase();
2756
2616
  if (tag === "input" || tag === "textarea" || tag === "select") {
@@ -2783,6 +2643,10 @@ var RecordingService = class {
2783
2643
  this.handleMatOptionClick(target, selector);
2784
2644
  return;
2785
2645
  }
2646
+ if (!selector) {
2647
+ this.selectorNotFound$.next({ target, action: "click" });
2648
+ return;
2649
+ }
2786
2650
  this.addGenericCommand({
2787
2651
  selector,
2788
2652
  action: (s) => `cy.get('${s}').click()`
@@ -2905,42 +2769,6 @@ var RecordingService = class {
2905
2769
 
2906
2770
  // src/services/persistence.service.ts
2907
2771
  var import_idb = require("idb");
2908
-
2909
- // src/utils/ticket.utils.ts
2910
- function buildTicketUrl(provider, params, id) {
2911
- const ticketId = (id ?? "").trim();
2912
- if (!provider || !ticketId) return null;
2913
- let url = provider.urlTemplate;
2914
- for (const field of provider.fields) {
2915
- const value = (params?.[field.key] ?? "").trim().replace(/\/+$/, "");
2916
- if (!value) return null;
2917
- url = url.split(`{${field.key}}`).join(value);
2918
- }
2919
- url = url.split("{id}").join(encodeURIComponent(ticketId));
2920
- if (url.includes("{") || url.includes("}")) return null;
2921
- return toSafeHttpUrl(url);
2922
- }
2923
- function resolveTicketUrl(providerId, allParams, ticketId) {
2924
- const id = (ticketId ?? "").trim();
2925
- if (!id) return null;
2926
- const provider = findIssueTrackerProvider(providerId);
2927
- if (!provider) return null;
2928
- return buildTicketUrl(provider, allParams?.[providerId] ?? {}, id);
2929
- }
2930
- function toSafeHttpUrl(candidate) {
2931
- try {
2932
- const parsed = new URL(candidate);
2933
- return parsed.protocol === "http:" || parsed.protocol === "https:" ? parsed.href : null;
2934
- } catch {
2935
- return null;
2936
- }
2937
- }
2938
- function buildTicketComment(id, url) {
2939
- const ticketId = (id ?? "").trim();
2940
- return url ? `// ${ticketId} \u2014 ${url}` : `// ${ticketId}`;
2941
- }
2942
-
2943
- // src/services/persistence.service.ts
2944
2772
  var ACTIVE_SESSION_ID = 1;
2945
2773
  var PersistenceService = class {
2946
2774
  // dbName is overridable so tests can use unique names for isolation.
@@ -2995,28 +2823,12 @@ var PersistenceService = class {
2995
2823
  if (!record) return null;
2996
2824
  const commands = await this.getCommandStrings(testId);
2997
2825
  const interceptors = await this.getInterceptorStrings(testId);
2998
- const ticketComment = await this.ticketCommentFor(record);
2999
- const itBody = `it('${escapeSingleQuotes(record.name)}', () => {
2826
+ const itBlock = `it('${escapeSingleQuotes(record.name)}', () => {
3000
2827
  ${commands.map((c) => normalizeBlock(c, " ")).join("\n")}
3001
2828
  });`;
3002
- const itBlock = ticketComment ? `${ticketComment}
3003
- ${itBody}` : itBody;
3004
2829
  const interceptorsBlock = interceptors.length ? " // Auto-generated Cypress interceptors\n" + interceptors.map((i) => normalizeBlock(i, " ")).join("\n") + "\n" : "";
3005
2830
  return { ...record, commands, interceptors, cypressCommands: commands, itBlock, interceptorsBlock };
3006
2831
  }
3007
- /**
3008
- * Builds the traceability comment for a test record (spec 014), reading the
3009
- * configured issue-tracker provider/params so the link — when resolvable — is
3010
- * embedded. Returns `null` when the record has no ticket id.
3011
- */
3012
- async ticketCommentFor(record) {
3013
- if (!record.ticketId) return null;
3014
- const config = await this.getGeneralConfig();
3015
- const providerId = config?.["issueTrackerProviderId"] ?? DEFAULT_ISSUE_TRACKER_PROVIDER_ID;
3016
- const params = config?.["issueTrackerParams"] ?? {};
3017
- const url = resolveTicketUrl(providerId, params, record.ticketId);
3018
- return buildTicketComment(record.ticketId, url);
3019
- }
3020
2832
  async deleteTest(id) {
3021
2833
  const db = await this.getDB();
3022
2834
  await db.delete("tests", id);
@@ -3324,39 +3136,34 @@ var HttpMonitor = class {
3324
3136
  if (!INTERCEPTED_METHODS.includes(method)) return;
3325
3137
  const url = resolveUrl(input);
3326
3138
  const alias = generateAlias(method, url);
3327
- if (this.isFixtureModeEnabled() && method === "GET") {
3328
- const pretty = prettyJsonOrNull(await responseClone.text());
3329
- const fixtureFile = `${alias}.json`;
3330
- if (pretty !== null) this.recording.registerFixture(fixtureFile, pretty);
3331
- this.recording.registerInterceptor(method, url, alias, pretty !== null ? fixtureFile : void 0);
3332
- this.recording.addCommand(`cy.wait('@${alias}').then((interception) => { })`);
3333
- return;
3334
- }
3335
3139
  this.recording.registerInterceptor(method, url, alias);
3336
3140
  const extendedHttp = this.isExtendedHttpEnabled();
3141
+ const fixtureMode = this.isFixtureModeEnabled();
3337
3142
  const requestBody = extendedHttp ? parseRequestBody(init) : null;
3143
+ let responseText = null;
3338
3144
  let responseBody = null;
3339
- if (extendedHttp) {
3145
+ if (extendedHttp || fixtureMode && method === "GET") {
3340
3146
  try {
3341
- responseBody = parseJsonObject(await responseClone.text());
3147
+ responseText = await responseClone.text();
3148
+ if (extendedHttp) responseBody = parseJsonObject(responseText);
3342
3149
  } catch {
3343
3150
  }
3344
3151
  }
3152
+ if (fixtureMode && method === "GET" && responseText !== null) {
3153
+ const pretty = prettyJsonOrNull(responseText);
3154
+ if (pretty !== null) this.recording.registerFixture(`${alias}.json`, pretty);
3155
+ }
3345
3156
  const cmd = buildCyWaitCommand(method, alias, extendedHttp, requestBody, responseBody);
3346
3157
  this.recording.addCommand(cmd);
3347
3158
  }
3348
3159
  handleXhrInterception(method, url, requestBody, responseText) {
3349
3160
  if (!INTERCEPTED_METHODS.includes(method)) return;
3350
3161
  const alias = generateAlias(method, url);
3162
+ this.recording.registerInterceptor(method, url, alias);
3351
3163
  if (this.isFixtureModeEnabled() && method === "GET") {
3352
3164
  const pretty = prettyJsonOrNull(responseText);
3353
- const fixtureFile = `${alias}.json`;
3354
- if (pretty !== null) this.recording.registerFixture(fixtureFile, pretty);
3355
- this.recording.registerInterceptor(method, url, alias, pretty !== null ? fixtureFile : void 0);
3356
- this.recording.addCommand(`cy.wait('@${alias}').then((interception) => { })`);
3357
- return;
3165
+ if (pretty !== null) this.recording.registerFixture(`${alias}.json`, pretty);
3358
3166
  }
3359
- this.recording.registerInterceptor(method, url, alias);
3360
3167
  const extendedHttp = this.isExtendedHttpEnabled();
3361
3168
  const responseBody = extendedHttp ? parseJsonObject(responseText) : null;
3362
3169
  const cmd = buildCyWaitCommand(method, alias, extendedHttp, requestBody, responseBody);
@@ -3681,177 +3488,6 @@ function boxTopLeftFor(toggleCentre, dir) {
3681
3488
  return { x: toggleCentre.x - off.x, y: toggleCentre.y - off.y };
3682
3489
  }
3683
3490
 
3684
- // src/utils/cypress-completions.ts
3685
- var CY_METHODS = [
3686
- "visit",
3687
- "get",
3688
- "contains",
3689
- "intercept",
3690
- "wait",
3691
- "request",
3692
- "url",
3693
- "location",
3694
- "viewport",
3695
- "fixture",
3696
- "wrap",
3697
- "reload",
3698
- "go",
3699
- "window",
3700
- "document",
3701
- "session",
3702
- "clearCookies",
3703
- "clearLocalStorage",
3704
- "screenshot",
3705
- "log"
3706
- ];
3707
- var CHAIN_METHODS = [
3708
- "click",
3709
- "dblclick",
3710
- "rightclick",
3711
- "type",
3712
- "clear",
3713
- "select",
3714
- "check",
3715
- "uncheck",
3716
- "should",
3717
- "and",
3718
- "find",
3719
- "first",
3720
- "last",
3721
- "eq",
3722
- "then",
3723
- "its",
3724
- "invoke",
3725
- "as",
3726
- "trigger",
3727
- "focus",
3728
- "blur",
3729
- "scrollIntoView",
3730
- "within",
3731
- "parent",
3732
- "children",
3733
- "siblings",
3734
- "contains",
3735
- "each",
3736
- "filter",
3737
- "wait"
3738
- ];
3739
- var TOP_LEVEL = [
3740
- "cy",
3741
- "describe",
3742
- "it",
3743
- "context",
3744
- "beforeEach",
3745
- "afterEach",
3746
- "before",
3747
- "after",
3748
- "Cypress"
3749
- ];
3750
- var CYPRESS_COMPLETIONS = [
3751
- ...TOP_LEVEL,
3752
- ...CY_METHODS.map((m) => `cy.${m}`),
3753
- ...CHAIN_METHODS
3754
- ];
3755
- function filterCompletions(list, prefix) {
3756
- const p = prefix.toLowerCase();
3757
- if (!p) return [...list];
3758
- return list.filter((label) => label.toLowerCase().includes(p));
3759
- }
3760
- function filterCypressCompletions(prefix) {
3761
- return filterCompletions(CYPRESS_COMPLETIONS, prefix);
3762
- }
3763
-
3764
- // src/utils/code-editor.ts
3765
- function toOptions(labels, type) {
3766
- return labels.map((label) => ({ label, type }));
3767
- }
3768
- function cypressCompletionSource(context) {
3769
- const cyMatch = context.matchBefore(/cy\.\w*$/);
3770
- if (cyMatch) {
3771
- return { from: cyMatch.from + 3, options: toOptions(filterCompletions(CY_METHODS, cyMatch.text.slice(3)), "method") };
3772
- }
3773
- const chainMatch = context.matchBefore(/\.\w*$/);
3774
- if (chainMatch) {
3775
- return { from: chainMatch.from + 1, options: toOptions(filterCompletions(CHAIN_METHODS, chainMatch.text.slice(1)), "method") };
3776
- }
3777
- const word = context.matchBefore(/\w*/);
3778
- if (!word || word.from === word.to && !context.explicit) return null;
3779
- return { from: word.from, options: toOptions(filterCompletions(TOP_LEVEL, word.text), "keyword") };
3780
- }
3781
- async function createCodeEditor(opts) {
3782
- const [view, state, language, langJs, autocomplete, commands, highlight] = await Promise.all([
3783
- import("@codemirror/view"),
3784
- import("@codemirror/state"),
3785
- import("@codemirror/language"),
3786
- import("@codemirror/lang-javascript"),
3787
- import("@codemirror/autocomplete"),
3788
- import("@codemirror/commands"),
3789
- import("@lezer/highlight")
3790
- ]);
3791
- const { EditorView, keymap, lineNumbers, highlightActiveLine, drawSelection } = view;
3792
- const { EditorState } = state;
3793
- const { syntaxHighlighting, HighlightStyle, indentOnInput, bracketMatching } = language;
3794
- const { javascript } = langJs;
3795
- const { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap } = autocomplete;
3796
- const { history: history2, defaultKeymap, historyKeymap, indentWithTab } = commands;
3797
- const { tags } = highlight;
3798
- const darkHighlight = HighlightStyle.define([
3799
- { tag: tags.keyword, color: "#ff7b72" },
3800
- { tag: tags.string, color: "#a5d6ff" },
3801
- { tag: [tags.function(tags.variableName), tags.function(tags.propertyName)], color: "#d2a8ff" },
3802
- { tag: tags.number, color: "#79c0ff" },
3803
- { tag: [tags.bool, tags.null], color: "#79c0ff" },
3804
- { tag: tags.comment, color: "#8b949e", fontStyle: "italic" },
3805
- { tag: [tags.propertyName, tags.attributeName], color: "#7ee787" },
3806
- { tag: [tags.typeName, tags.className], color: "#ffa657" },
3807
- { tag: tags.operator, color: "#ff7b72" },
3808
- { tag: [tags.brace, tags.bracket, tags.punctuation], color: "#c9d1d9" }
3809
- ]);
3810
- const theme = EditorView.theme(
3811
- {
3812
- "&": { color: "#c9d1d9", backgroundColor: "#0d1117", height: "100%", fontSize: "13px" },
3813
- ".cm-scroller": { fontFamily: "'Cascadia Code','Fira Code',Consolas,monospace", overflow: "auto" },
3814
- ".cm-gutters": { backgroundColor: "#0d1117", color: "#484f58", border: "none" },
3815
- ".cm-activeLine": { backgroundColor: "rgba(110,118,129,0.10)" },
3816
- ".cm-activeLineGutter": { backgroundColor: "transparent" },
3817
- "&.cm-focused": { outline: "none" },
3818
- ".cm-tooltip": { background: "#161b22", border: "1px solid #30363d", color: "#c9d1d9", borderRadius: "6px" },
3819
- ".cm-tooltip-autocomplete ul li[aria-selected]": { background: "#1f6feb", color: "#fff" }
3820
- },
3821
- { dark: true }
3822
- );
3823
- const editorView = new EditorView({
3824
- parent: opts.parent,
3825
- ...opts.root ? { root: opts.root } : {},
3826
- state: EditorState.create({
3827
- doc: opts.doc,
3828
- extensions: [
3829
- lineNumbers(),
3830
- highlightActiveLine(),
3831
- drawSelection(),
3832
- history2(),
3833
- indentOnInput(),
3834
- bracketMatching(),
3835
- closeBrackets(),
3836
- javascript({ typescript: true }),
3837
- syntaxHighlighting(darkHighlight),
3838
- autocompletion({ override: [cypressCompletionSource] }),
3839
- keymap.of([...closeBracketsKeymap, ...defaultKeymap, ...historyKeymap, ...completionKeymap, indentWithTab]),
3840
- theme,
3841
- EditorView.updateListener.of((update) => {
3842
- if (update.docChanged && opts.onChange) opts.onChange(update.state.doc.toString());
3843
- })
3844
- ]
3845
- })
3846
- });
3847
- return {
3848
- getValue: () => editorView.state.doc.toString(),
3849
- setValue: (value) => editorView.dispatch({ changes: { from: 0, to: editorView.state.doc.length, insert: value } }),
3850
- focus: () => editorView.focus(),
3851
- destroy: () => editorView.destroy()
3852
- };
3853
- }
3854
-
3855
3491
  // src/index.ts
3856
3492
  init_selector_picker();
3857
3493
 
@@ -4057,6 +3693,33 @@ if (!customElements.get("test-previsualizer")) {
4057
3693
  customElements.define("test-previsualizer", TestPrevisualizerElement);
4058
3694
  }
4059
3695
 
3696
+ // src/models/issue-tracker.model.ts
3697
+ var ISSUE_TRACKER_PRESETS = {
3698
+ jira: { id: "jira", label: "Jira", urlPattern: "{baseUrl}/browse/{id}", idPattern: /^[A-Z][A-Z0-9]+-\d+$/ },
3699
+ github: { id: "github", label: "GitHub Issues", urlPattern: "{baseUrl}/issues/{id}", idPattern: /^\d+$/ },
3700
+ gitlab: { id: "gitlab", label: "GitLab Issues", urlPattern: "{baseUrl}/-/issues/{id}", idPattern: /^\d+$/ },
3701
+ azure: { id: "azure", label: "Azure DevOps", urlPattern: "{baseUrl}/_workitems/edit/{id}", idPattern: /^\d+$/ },
3702
+ bitbucket: { id: "bitbucket", label: "Bitbucket Issues", urlPattern: "{baseUrl}/issues/{id}", idPattern: /^\d+$/ },
3703
+ custom: { id: "custom", label: "Custom", urlPattern: "{baseUrl}/{id}", idPattern: /.+/ }
3704
+ };
3705
+ var DEFAULT_ISSUE_TRACKER_CONFIG = {
3706
+ enabled: false,
3707
+ provider: "jira",
3708
+ baseUrl: ""
3709
+ };
3710
+
3711
+ // src/utils/ticket.utils.ts
3712
+ function buildTicketUrl(ticketId, config) {
3713
+ if (!config.enabled || !ticketId.trim() || !config.baseUrl.trim()) return null;
3714
+ const preset = ISSUE_TRACKER_PRESETS[config.provider];
3715
+ const base = config.baseUrl.replace(/\/+$/, "");
3716
+ return preset.urlPattern.replace("{baseUrl}", base).replace("{id}", encodeURIComponent(ticketId.trim()));
3717
+ }
3718
+ function isValidTicketId(ticketId, provider) {
3719
+ if (!ticketId.trim()) return false;
3720
+ return ISSUE_TRACKER_PRESETS[provider].idPattern.test(ticketId.trim());
3721
+ }
3722
+
4060
3723
  // src/components/save-test/save-test.styles.ts
4061
3724
  var SAVE_TEST_STYLES = `
4062
3725
  :host { display: block; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e6edf3; }
@@ -4132,11 +3795,15 @@ function renderSaveTestAsk(t) {
4132
3795
  </div>
4133
3796
  </div>`;
4134
3797
  }
4135
- function renderSaveTestDesc(description, notes, tags, ticket, t) {
3798
+ function renderSaveTestDesc(description, notes, tags, ticketId, issueTrackerEnabled, ticketWarn, t) {
4136
3799
  const chipsHtml = tags.map(
4137
3800
  (tag) => `<span class="chip">${escHtml(tag)}<button class="chip-del" data-tag="${escAttr(tag)}" title="${t("SAVE_TEST.REMOVE_TAG_TITLE")}">\u2715</button></span>`
4138
3801
  ).join("");
4139
- const ticketWarning = ticket.invalid ? `<div class="ticket-warning" style="color:#d29922;font-size:11px;margin-top:2px">${t("SAVE_TEST.TICKET_INVALID")}</div>` : "";
3802
+ const ticketHtml = issueTrackerEnabled ? `
3803
+ <span class="tag-label">${t("SAVE_TEST.TICKET_LABEL")}</span>
3804
+ <input id="ticket-input" type="text" placeholder="${escAttr(t("SAVE_TEST.TICKET_PLACEHOLDER"))}"
3805
+ value="${escAttr(ticketId)}" autocomplete="off" />
3806
+ ${ticketWarn ? `<span class="ticket-warn">\u26A0 ${t("SAVE_TEST.TICKET_WARN")}</span>` : ""}` : "";
4140
3807
  return `
4141
3808
  <div class="container">
4142
3809
  <p>${t("SAVE_TEST.DESC_LABEL")} (<code>it()</code>):</p>
@@ -4144,16 +3811,13 @@ function renderSaveTestDesc(description, notes, tags, ticket, t) {
4144
3811
  value="${escAttr(description)}" autocomplete="off" />
4145
3812
  <span class="tag-label">${t("SAVE_TEST.NOTES_LABEL")}</span>
4146
3813
  <textarea id="notes-input" rows="3" placeholder="${escAttr(t("SAVE_TEST.NOTES_PLACEHOLDER"))}">${escHtml(notes)}</textarea>
4147
- <span class="tag-label">${t("SAVE_TEST.TICKET_LABEL")}</span>
4148
- <input id="ticket-input" type="text" placeholder="${escAttr(t(ticket.placeholderKey))}"
4149
- value="${escAttr(ticket.id)}" autocomplete="off" />
4150
- ${ticketWarning}
4151
3814
  <span class="tag-label">${t("SAVE_TEST.TAGS_LABEL")}</span>
4152
3815
  <div class="tag-input-row">
4153
3816
  <input id="tag-input" type="text" placeholder="${t("SAVE_TEST.TAGS_PLACEHOLDER")}" autocomplete="off" />
4154
3817
  <button class="btn-tag-add" id="btn-add-tag">${t("SAVE_TEST.ADD_TAG")}</button>
4155
3818
  </div>
4156
3819
  <div class="chips" id="chips-container">${chipsHtml || `<span style="color:#484f58;font-size:11px">${t("SAVE_TEST.NO_TAGS")}</span>`}</div>
3820
+ ${ticketHtml}
4157
3821
  <div class="btn-row">
4158
3822
  <button class="btn-primary" id="btn-confirm">${t("SAVE_TEST.SAVE_BTN")}</button>
4159
3823
  <button class="btn-success" id="btn-export">${t("SAVE_TEST.SAVE_AND_EDIT")}</button>
@@ -4170,8 +3834,7 @@ var SaveTestElement = class extends HTMLElement {
4170
3834
  notes = "";
4171
3835
  tags = [];
4172
3836
  ticketId = "";
4173
- /** Set by the host from config so the ticket placeholder/validation is provider-aware. */
4174
- issueTrackerProviderId = DEFAULT_ISSUE_TRACKER_PROVIDER_ID;
3837
+ issueTrackerConfig = DEFAULT_ISSUE_TRACKER_CONFIG;
4175
3838
  translation = translationService;
4176
3839
  constructor() {
4177
3840
  super();
@@ -4188,13 +3851,13 @@ var SaveTestElement = class extends HTMLElement {
4188
3851
  this.render();
4189
3852
  }
4190
3853
  confirmSave() {
4191
- this.dispatch("savetest", { description: this.description.trim(), notes: this.notes, tags: [...this.tags], ticketId: this.ticketId.trim() || null });
3854
+ this.dispatch("savetest", { description: this.description.trim(), notes: this.notes, tags: [...this.tags], ticketId: this.ticketId.trim() });
4192
3855
  }
4193
3856
  confirmSaveAndExport() {
4194
- this.dispatch("saveandexport", { description: this.description.trim(), notes: this.notes, tags: [...this.tags], ticketId: this.ticketId.trim() || null });
3857
+ this.dispatch("saveandexport", { description: this.description.trim(), notes: this.notes, tags: [...this.tags], ticketId: this.ticketId.trim() });
4195
3858
  }
4196
3859
  cancel() {
4197
- this.dispatch("savetest", { description: null, notes: "", tags: [], ticketId: null });
3860
+ this.dispatch("savetest", { description: null, notes: "", tags: [], ticketId: "" });
4198
3861
  }
4199
3862
  restartComponent() {
4200
3863
  this._step = "ask";
@@ -4218,19 +3881,9 @@ var SaveTestElement = class extends HTMLElement {
4218
3881
  t(key) {
4219
3882
  return this.translation.translate(key);
4220
3883
  }
4221
- /** Builds the ticket field view: provider-aware placeholder + soft (non-blocking) format check. */
4222
- ticketView() {
4223
- const provider = findIssueTrackerProvider(this.issueTrackerProviderId) ?? ISSUE_TRACKER_PROVIDERS[0];
4224
- const trimmed = this.ticketId.trim();
4225
- let invalid = false;
4226
- if (trimmed) {
4227
- try {
4228
- invalid = !new RegExp(provider.idPattern).test(trimmed);
4229
- } catch {
4230
- invalid = false;
4231
- }
4232
- }
4233
- return { id: this.ticketId, placeholderKey: provider.idPlaceholderKey, invalid };
3884
+ get ticketIdWarning() {
3885
+ if (!this.ticketId.trim() || !this.issueTrackerConfig.enabled) return false;
3886
+ return !isValidTicketId(this.ticketId, this.issueTrackerConfig.provider);
4234
3887
  }
4235
3888
  dispatch(type, detail) {
4236
3889
  this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
@@ -4241,21 +3894,18 @@ var SaveTestElement = class extends HTMLElement {
4241
3894
  this.shadow.getElementById("btn-yes")?.addEventListener("click", () => this.askSave());
4242
3895
  this.shadow.getElementById("btn-no")?.addEventListener("click", () => this.cancel());
4243
3896
  } else {
4244
- this.shadow.innerHTML = `<style>${SAVE_TEST_STYLES}</style>${renderSaveTestDesc(this.description, this.notes, this.tags, this.ticketView(), this.t.bind(this))}`;
3897
+ this.shadow.innerHTML = `<style>${SAVE_TEST_STYLES}</style>${renderSaveTestDesc(this.description, this.notes, this.tags, this.ticketId, this.issueTrackerConfig.enabled, this.ticketIdWarning, this.t.bind(this))}`;
4245
3898
  const descInput = this.shadow.getElementById("desc-input");
4246
3899
  const notesInput = this.shadow.getElementById("notes-input");
4247
- const ticketInput = this.shadow.getElementById("ticket-input");
4248
3900
  const tagInput = this.shadow.getElementById("tag-input");
3901
+ const ticketInput = this.shadow.getElementById("ticket-input");
4249
3902
  descInput.addEventListener("input", () => {
4250
3903
  this.description = descInput.value;
4251
3904
  });
4252
3905
  notesInput.addEventListener("input", () => {
4253
3906
  this.notes = notesInput.value;
4254
3907
  });
4255
- ticketInput.addEventListener("input", () => {
4256
- this.ticketId = ticketInput.value;
4257
- });
4258
- ticketInput.addEventListener("change", () => {
3908
+ ticketInput?.addEventListener("input", () => {
4259
3909
  this.ticketId = ticketInput.value;
4260
3910
  this.render();
4261
3911
  });
@@ -4350,6 +4000,18 @@ var TEST_EDITOR_STYLES = `
4350
4000
  padding: 1px 7px; border-radius: 20px; font-size: 10px;
4351
4001
  background: rgba(47,129,247,0.1); color: #8b949e; border: 1px solid rgba(47,129,247,0.2);
4352
4002
  }
4003
+ .ticket-badge, .ticket-link {
4004
+ padding: 1px 7px; border-radius: 20px; font-size: 10px; font-weight: 500;
4005
+ background: rgba(210,153,34,0.12); color: #d29922; border: 1px solid rgba(210,153,34,0.3);
4006
+ white-space: nowrap; flex-shrink: 0;
4007
+ }
4008
+ .ticket-link {
4009
+ text-decoration: none; cursor: pointer;
4010
+ transition: background 0.12s, border-color 0.12s;
4011
+ }
4012
+ .ticket-link::before { content: '\u{1F3AB} '; font-size: 9px; }
4013
+ .ticket-link:hover { background: rgba(210,153,34,0.22); border-color: rgba(210,153,34,0.55); color: #e3b341; }
4014
+ .ticket-badge::before { content: '\u{1F3AB} '; font-size: 9px; }
4353
4015
  .btn-icon {
4354
4016
  padding: 3px 8px; border: none; border-radius: 6px; cursor: pointer;
4355
4017
  font-size: 11px; background: #21262d; color: #8b949e;
@@ -4381,6 +4043,13 @@ var TEST_EDITOR_STYLES = `
4381
4043
  .code-preview::-webkit-scrollbar { height: 4px; }
4382
4044
  .code-preview::-webkit-scrollbar-thumb { background: #30363d; border-radius: 2px; }
4383
4045
  .code-preview-icp { margin-top: 8px; }
4046
+ .ticket-group { margin-bottom: 8px; }
4047
+ .ticket-group-header {
4048
+ padding: 4px 10px; margin-bottom: 4px;
4049
+ font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.7px;
4050
+ color: #d29922; border-left: 3px solid rgba(210,153,34,0.5);
4051
+ background: rgba(210,153,34,0.06); border-radius: 0 4px 4px 0;
4052
+ }
4384
4053
  .sh-kw { color: #ff7b72; }
4385
4054
  .sh-bi { color: #d2a8ff; }
4386
4055
  .sh-str { color: #a5d6ff; }
@@ -4487,8 +4156,48 @@ function highlightLine(line) {
4487
4156
  }
4488
4157
 
4489
4158
  // src/components/test-editor/test-editor.template.ts
4159
+ function renderTestRow(test, i, state, t) {
4160
+ const { selectMode, selectedIds, expandedIndex, interceptorsByTest, locale, issueTrackerConfig } = state;
4161
+ const expanded = expandedIndex === i;
4162
+ const date = new Date(test.createdAt).toLocaleString(locale, { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
4163
+ const icps = interceptorsByTest[test.id] ?? test.interceptors ?? [];
4164
+ const tagsHtml = (test.tags ?? []).length ? `<span class="test-tags">${(test.tags ?? []).map((tag) => `<span class="test-tag">${escHtml(tag)}</span>`).join("")}</span>` : "";
4165
+ const checkbox = selectMode ? `<input type="checkbox" ${selectedIds.has(test.id) ? "checked" : ""} data-select="${test.id}" />` : "";
4166
+ const hasIcps = (test.interceptors ?? []).length > 0;
4167
+ const itBlockCode = `it('${escapeSingleQuotes(test.name)}', () => {
4168
+ ${(test.commands ?? []).map((c) => normalizeBlock(c, " ")).join("\n")}
4169
+ });`;
4170
+ const icpBlockCode = icps.length ? `beforeEach(() => {
4171
+ // Auto-generated Cypress interceptors
4172
+ ${icps.map((c) => normalizeBlock(c, " ")).join("\n")}
4173
+ });` : "";
4174
+ const notesHtml = expanded && test.notes ? `<p class="test-notes">${escHtml(test.notes)}</p>` : "";
4175
+ const ticketId = test.ticketId;
4176
+ const ticketUrl = ticketId ? buildTicketUrl(ticketId, issueTrackerConfig) : null;
4177
+ const ticketHtml = ticketId ? ticketUrl ? `<a class="ticket-link" href="${escAttr(ticketUrl)}" target="_blank" rel="noopener noreferrer" title="${t("TEST_EDITOR.TICKET_LINK_TITLE")}">${escHtml(ticketId)}</a>` : `<span class="ticket-badge">${escHtml(ticketId)}</span>` : "";
4178
+ const body = expanded ? `
4179
+ <div class="row-body">
4180
+ ${notesHtml}
4181
+ <pre class="code-preview">${syntaxHighlight(itBlockCode)}</pre>
4182
+ ${icpBlockCode ? `<pre class="code-preview code-preview-icp">${syntaxHighlight(icpBlockCode)}</pre>` : ""}
4183
+ </div>` : "";
4184
+ return `
4185
+ <div class="row${selectedIds.has(test.id) ? " selected-row" : ""}">
4186
+ <div class="row-header" data-action="expand" data-idx="${i}">
4187
+ ${checkbox}
4188
+ <span class="test-name">${escHtml(test.name)}</span>
4189
+ ${tagsHtml}
4190
+ ${ticketHtml}
4191
+ <span class="test-date">${date}</span>
4192
+ <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>
4193
+ ${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>` : ""}
4194
+ <button class="btn-icon btn-del" data-action="delete" data-id="${test.id}" title="${t("TEST_EDITOR.DELETE_TITLE")}">\u{1F5D1}</button>
4195
+ </div>
4196
+ ${body}
4197
+ </div>`;
4198
+ }
4490
4199
  function renderTestEditor(state, t) {
4491
- const { tags, visible, selectedVisible, activeTag, selectMode, selectedIds, describeName, expandedIndex, interceptorsByTest, groupByTicket, ticketUrlByTest, locale } = state;
4200
+ const { tags, visible, selectedVisible, activeTag, selectMode, describeName, groupByTicket } = state;
4492
4201
  const tagFilterHtml = tags.length ? `<div class="tag-filter">
4493
4202
  ${tags.map((tag) => `<button class="tag-chip${activeTag === tag ? " active" : ""}" data-filter-tag="${escAttr(tag)}">${escHtml(tag)}</button>`).join("")}
4494
4203
  </div>` : `<div class="tag-filter" style="color:#484f58;font-size:11px">${t("TEST_EDITOR.NO_TAGS")}</div>`;
@@ -4497,80 +4206,43 @@ function renderTestEditor(state, t) {
4497
4206
  <input id="describe-name" type="text" placeholder="${t("TEST_EDITOR.DESCRIBE_PLACEHOLDER")}" value="${escAttr(describeName)}" />
4498
4207
  <button class="btn-gen-describe" id="btn-gen-describe">${t("TEST_EDITOR.COPY_DESCRIBE")}</button>
4499
4208
  </div>` : "";
4500
- const renderRow = (test, i) => {
4501
- const expanded = expandedIndex === i;
4502
- const date = new Date(test.createdAt).toLocaleString(locale, { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
4503
- const icps = interceptorsByTest[test.id] ?? test.interceptors ?? [];
4504
- const tagsHtml = (test.tags ?? []).length ? `<span class="test-tags">${(test.tags ?? []).map((tag) => `<span class="test-tag">${escHtml(tag)}</span>`).join("")}</span>` : "";
4505
- const ticketId = (test.ticketId ?? "").trim();
4506
- const ticketUrl = ticketUrlByTest[test.id] ?? null;
4507
- const ticketHtml = !ticketId ? "" : ticketUrl ? `<a class="test-ticket" data-ticket-link href="${escAttr(ticketUrl)}" target="_blank" rel="noopener noreferrer" style="color:#58a6ff;font-size:11px;text-decoration:none">\u{1F3AB} ${escHtml(ticketId)}</a>` : `<span class="test-ticket" style="color:#8b949e;font-size:11px">\u{1F3AB} ${escHtml(ticketId)}</span>`;
4508
- const checkbox = selectMode ? `<input type="checkbox" ${selectedIds.has(test.id) ? "checked" : ""} data-select="${test.id}" />` : "";
4509
- const hasIcps = (test.interceptors ?? []).length > 0;
4510
- const ticketComment = ticketId ? `${buildTicketComment(ticketId, ticketUrl)}
4511
- ` : "";
4512
- const itBlockCode = `${ticketComment}it('${escapeSingleQuotes(test.name)}', () => {
4513
- ${(test.commands ?? []).map((c) => normalizeBlock(c, " ")).join("\n")}
4514
- });`;
4515
- const icpBlockCode = icps.length ? `beforeEach(() => {
4516
- // Auto-generated Cypress interceptors
4517
- ${icps.map((c) => normalizeBlock(c, " ")).join("\n")}
4518
- });` : "";
4519
- const notesHtml = expanded && test.notes ? `<p class="test-notes">${escHtml(test.notes)}</p>` : "";
4520
- const body = expanded ? `
4521
- <div class="row-body">
4522
- ${notesHtml}
4523
- <pre class="code-preview">${syntaxHighlight(itBlockCode)}</pre>
4524
- ${icpBlockCode ? `<pre class="code-preview code-preview-icp">${syntaxHighlight(icpBlockCode)}</pre>` : ""}
4525
- </div>` : "";
4526
- return `
4527
- <div class="row${selectedIds.has(test.id) ? " selected-row" : ""}">
4528
- <div class="row-header" data-action="expand" data-idx="${i}">
4529
- ${checkbox}
4530
- <span class="test-name">${escHtml(test.name)}</span>
4531
- ${tagsHtml}
4532
- ${ticketHtml}
4533
- <span class="test-date">${date}</span>
4534
- <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>
4535
- ${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>` : ""}
4536
- <button class="btn-icon btn-del" data-action="delete" data-id="${test.id}" title="${t("TEST_EDITOR.DELETE_TITLE")}">\u{1F5D1}</button>
4537
- </div>
4538
- ${body}
4539
- </div>`;
4540
- };
4541
- const indexed = visible.map((test, i) => ({ test, i }));
4542
- const groupedRows = () => {
4209
+ let rows;
4210
+ if (groupByTicket) {
4543
4211
  const groups = /* @__PURE__ */ new Map();
4544
- for (const item of indexed) {
4545
- const key = (item.test.ticketId ?? "").trim();
4546
- const bucket = groups.get(key);
4547
- if (bucket) bucket.push(item);
4548
- else groups.set(key, [item]);
4212
+ for (const test of visible) {
4213
+ const key = test.ticketId?.trim() || "";
4214
+ const group = groups.get(key) ?? [];
4215
+ group.push(test);
4216
+ groups.set(key, group);
4549
4217
  }
4550
- const keys = [...groups.keys()].sort((a, b) => a === "" ? 1 : b === "" ? -1 : a.localeCompare(b));
4551
- return keys.map((key) => {
4552
- const items = groups.get(key) ?? [];
4553
- const label = key || t("TEST_EDITOR.NO_TICKET_GROUP");
4554
- const url = key ? ticketUrlByTest[items[0].test.id] ?? null : null;
4555
- const header = url ? `<a data-ticket-link href="${escAttr(url)}" target="_blank" rel="noopener noreferrer" style="color:#58a6ff;text-decoration:none">\u{1F3AB} ${escHtml(label)}</a>` : `\u{1F3AB} ${escHtml(label)}`;
4556
- return `<div class="ticket-group">
4557
- <div class="ticket-group-hd" style="padding:6px 10px;color:#8b949e;font-size:12px;font-weight:600;border-top:1px solid #21262d">${header}</div>
4558
- ${items.map(({ test, i }) => renderRow(test, i)).join("")}
4559
- </div>`;
4218
+ const sortedKeys = [...groups.keys()].sort(
4219
+ (a, b) => a === "" ? 1 : b === "" ? -1 : a.localeCompare(b)
4220
+ );
4221
+ rows = sortedKeys.map((key) => {
4222
+ const group = groups.get(key);
4223
+ const header = `<div class="ticket-group-header">${key ? escHtml(key) : t("TEST_EDITOR.NO_TICKET")}</div>`;
4224
+ const groupRows = group.map((test) => {
4225
+ const i = visible.indexOf(test);
4226
+ return renderTestRow(test, i, state, t);
4227
+ }).join("");
4228
+ return `<div class="ticket-group">${header}${groupRows}</div>`;
4560
4229
  }).join("");
4561
- };
4562
- const listBody = !visible.length ? `<div class="empty">${t("TEST_EDITOR.NO_TESTS")}</div>` : groupByTicket ? groupedRows() : indexed.map(({ test, i }) => renderRow(test, i)).join("");
4230
+ } else {
4231
+ rows = visible.map((test, i) => renderTestRow(test, i, state, t)).join("");
4232
+ }
4563
4233
  return `
4564
4234
  <div class="toolbar">
4565
4235
  ${tagFilterHtml}
4566
- <button class="btn-select${groupByTicket ? " active" : ""}" id="btn-group-ticket">${t("TEST_EDITOR.GROUP_BY_TICKET")}</button>
4236
+ <button class="btn-select${groupByTicket ? " active" : ""}" id="btn-group-ticket">
4237
+ ${groupByTicket ? t("TEST_EDITOR.UNGROUP_TICKET") : t("TEST_EDITOR.GROUP_BY_TICKET")}
4238
+ </button>
4567
4239
  <button class="btn-select${selectMode ? " active" : ""}" id="btn-select-mode">
4568
4240
  ${selectMode ? t("TEST_EDITOR.CANCEL_SELECT") : t("TEST_EDITOR.MULTI_SELECT")}
4569
4241
  </button>
4570
4242
  </div>
4571
4243
  ${describeBarHtml}
4572
4244
  <div class="list">
4573
- ${listBody}
4245
+ ${visible.length ? rows : `<div class="empty">${t("TEST_EDITOR.NO_TESTS")}</div>`}
4574
4246
  </div>`;
4575
4247
  }
4576
4248
 
@@ -4587,8 +4259,7 @@ var TestEditorElement = class extends HTMLElement {
4587
4259
  selectedIds = /* @__PURE__ */ new Set();
4588
4260
  describeName = "";
4589
4261
  groupByTicket = false;
4590
- issueTrackerProviderId = DEFAULT_ISSUE_TRACKER_PROVIDER_ID;
4591
- issueTrackerParams = {};
4262
+ issueTrackerConfig = { ...DEFAULT_ISSUE_TRACKER_CONFIG };
4592
4263
  constructor() {
4593
4264
  super();
4594
4265
  this.shadow = this.attachShadow({ mode: "open" });
@@ -4598,17 +4269,7 @@ var TestEditorElement = class extends HTMLElement {
4598
4269
  this.loadTests();
4599
4270
  }
4600
4271
  async loadTests() {
4601
- const [tests, config] = await Promise.all([
4602
- this.persistence.getAllTests(),
4603
- this.persistence.getGeneralConfig()
4604
- ]);
4605
- this.tests = tests;
4606
- this.issueTrackerProviderId = config?.["issueTrackerProviderId"] ?? DEFAULT_ISSUE_TRACKER_PROVIDER_ID;
4607
- this.issueTrackerParams = config?.["issueTrackerParams"] ?? {};
4608
- this.render();
4609
- }
4610
- toggleGroupByTicket() {
4611
- this.groupByTicket = !this.groupByTicket;
4272
+ this.tests = await this.persistence.getAllTests();
4612
4273
  this.render();
4613
4274
  }
4614
4275
  async deleteTest(id) {
@@ -4629,6 +4290,10 @@ var TestEditorElement = class extends HTMLElement {
4629
4290
  hasInterceptors(testId) {
4630
4291
  return Array.isArray(this.interceptorsByTest[testId]) && this.interceptorsByTest[testId].length > 0;
4631
4292
  }
4293
+ toggleGroupByTicket() {
4294
+ this.groupByTicket = !this.groupByTicket;
4295
+ this.render();
4296
+ }
4632
4297
  toggleSelectMode() {
4633
4298
  this.selectMode = !this.selectMode;
4634
4299
  if (!this.selectMode) this.selectedIds.clear();
@@ -4683,12 +4348,6 @@ ${beforeEach}${itBlocks}
4683
4348
  const tags = this.allTags;
4684
4349
  const visible = this.visibleTests;
4685
4350
  const selectedVisible = visible.filter((t) => this.selectedIds.has(t.id));
4686
- const ticketUrlByTest = {};
4687
- for (const test of visible) {
4688
- if (test.ticketId) {
4689
- ticketUrlByTest[test.id] = resolveTicketUrl(this.issueTrackerProviderId, this.issueTrackerParams, test.ticketId);
4690
- }
4691
- }
4692
4351
  this.shadow.innerHTML = `<style>${TEST_EDITOR_STYLES}</style>${renderTestEditor({
4693
4352
  tags,
4694
4353
  visible,
@@ -4699,15 +4358,12 @@ ${beforeEach}${itBlocks}
4699
4358
  describeName: this.describeName,
4700
4359
  expandedIndex: this.expandedIndex,
4701
4360
  interceptorsByTest: this.interceptorsByTest,
4361
+ locale: localeForLang(this.translation.getLang()),
4702
4362
  groupByTicket: this.groupByTicket,
4703
- ticketUrlByTest,
4704
- locale: localeForLang(this.translation.getLang())
4363
+ issueTrackerConfig: this.issueTrackerConfig
4705
4364
  }, this.t.bind(this))}`;
4706
- this.shadow.getElementById("btn-select-mode")?.addEventListener("click", () => this.toggleSelectMode());
4707
4365
  this.shadow.getElementById("btn-group-ticket")?.addEventListener("click", () => this.toggleGroupByTicket());
4708
- this.shadow.querySelectorAll("[data-ticket-link]").forEach(
4709
- (el) => el.addEventListener("click", (ev) => ev.stopPropagation())
4710
- );
4366
+ this.shadow.getElementById("btn-select-mode")?.addEventListener("click", () => this.toggleSelectMode());
4711
4367
  this.shadow.querySelectorAll("[data-filter-tag]").forEach((el) => {
4712
4368
  el.addEventListener("click", () => {
4713
4369
  const tag = el.dataset["filterTag"] ?? "";
@@ -4771,26 +4427,16 @@ if (!customElements.get("test-editor")) {
4771
4427
 
4772
4428
  // src/components/configuration/configuration.styles.ts
4773
4429
  var CONFIGURATION_STYLES = `
4774
- :host { display: block; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e6edf3; }
4430
+ :host { display: block; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: #e6edf3; max-height: 70vh; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #30363d transparent; }
4775
4431
  * { box-sizing: border-box; }
4776
4432
 
4777
4433
  /* \u2500\u2500 Grid container \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
4778
- /* The SweetAlert2 host clips overflow (overflow:hidden), so the grid owns its
4779
- own scroll \u2014 otherwise the last cards fall below the fold with no scrollbar. */
4780
4434
  .cfg-grid {
4781
4435
  display: grid;
4782
4436
  grid-template-columns: 1fr 1fr;
4783
4437
  gap: 10px;
4784
4438
  padding: 14px;
4785
- max-height: 72vh;
4786
- overflow-y: auto;
4787
- scrollbar-width: thin;
4788
- scrollbar-color: #30363d transparent;
4789
4439
  }
4790
- .cfg-grid::-webkit-scrollbar { width: 8px; }
4791
- .cfg-grid::-webkit-scrollbar-thumb { background: #30363d; border-radius: 4px; }
4792
- .cfg-grid::-webkit-scrollbar-thumb:hover { background: #484f58; }
4793
- .cfg-grid::-webkit-scrollbar-track { background: transparent; }
4794
4440
 
4795
4441
  /* \u2500\u2500 Cards \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
4796
4442
  .card {
@@ -5004,33 +4650,8 @@ function renderExportOverlay(state, t) {
5004
4650
  </div>
5005
4651
  </div>`;
5006
4652
  }
5007
- var TRACKER_INPUT_STYLE = "flex:1;min-width:180px;padding:5px 8px;background:#161b22;border:1px solid #30363d;border-radius:5px;color:#c9d1d9;font-size:12px;outline:none";
5008
- function renderIssueTrackerCard(state, t) {
5009
- const { issueTrackerProviderId, issueTrackerParams } = state;
5010
- const provider = findIssueTrackerProvider(issueTrackerProviderId) ?? ISSUE_TRACKER_PROVIDERS[0];
5011
- const values = issueTrackerParams[provider.id] ?? {};
5012
- const options = ISSUE_TRACKER_PROVIDERS.map(
5013
- (p) => `<option value="${escAttr(p.id)}" ${provider.id === p.id ? "selected" : ""}>${escHtml(t(p.labelKey))}</option>`
5014
- ).join("");
5015
- const fields = provider.fields.map((f) => `
5016
- <div class="field-row">
5017
- <span class="field-label">${escHtml(t(f.labelKey))}</span>
5018
- <input type="text" data-tracker-field="${escAttr(f.key)}" value="${escAttr(values[f.key] ?? "")}"
5019
- placeholder="${escAttr(t(f.placeholderKey))}" style="${TRACKER_INPUT_STYLE}" />
5020
- </div>`).join("");
5021
- return `
5022
- <div class="card card-wide">
5023
- <div class="card-hd">${t("CONFIG.ISSUE_TRACKER_SECTION")}</div>
5024
- <div class="field-row">
5025
- <span class="field-label">${t("CONFIG.ISSUE_TRACKER_LABEL")}</span>
5026
- <select id="issue-tracker-provider">${options}</select>
5027
- </div>
5028
- ${fields}
5029
- <div class="check-sub issue-tracker-disclaimer" style="margin-top:8px">${t("CONFIG.ISSUE_TRACKER_DISCLAIMER")}</div>
5030
- </div>`;
5031
- }
5032
4653
  function renderConfiguration(state, t) {
5033
- const { selectedLanguage, advancedHttpConfig, fixtureMode, selectorStrategy, filesystemGranted, cypressFolderName, smartSelectorEnabled, startHidden, resumeTtlMinutes } = state;
4654
+ const { selectedLanguage, advancedHttpConfig, fixtureMode, selectorStrategy, filesystemGranted, cypressFolderName, smartSelectorEnabled, startHidden, resumeTtlMinutes, issueTrackerConfig } = state;
5034
4655
  const langOptions = LANGS.map(
5035
4656
  (l) => `<option value="${l.value}" ${selectedLanguage === l.value ? "selected" : ""}>${l.label}</option>`
5036
4657
  ).join("");
@@ -5151,8 +4772,33 @@ function renderConfiguration(state, t) {
5151
4772
  </div>
5152
4773
  </div>
5153
4774
 
5154
- <!-- Issue tracker (spec 014) -->
5155
- ${renderIssueTrackerCard(state, t)}
4775
+ <!-- Issue Tracker -->
4776
+ <div class="card card-wide">
4777
+ <div class="card-hd">${t("CONFIG.ISSUE_TRACKER_SECTION")}</div>
4778
+ <label class="check-row">
4779
+ <input type="checkbox" id="issue-tracker-toggle" ${issueTrackerConfig.enabled ? "checked" : ""} />
4780
+ <div>
4781
+ <div class="check-title">${t("CONFIG.ISSUE_TRACKER_TITLE")}</div>
4782
+ <div class="check-sub">${t("CONFIG.ISSUE_TRACKER_SUB")}</div>
4783
+ </div>
4784
+ </label>
4785
+ ${issueTrackerConfig.enabled ? `
4786
+ <div class="field-row" style="margin-top:10px">
4787
+ <span class="field-label">${t("CONFIG.ISSUE_TRACKER_PROVIDER")}</span>
4788
+ <select id="issue-tracker-provider">
4789
+ ${Object.values(ISSUE_TRACKER_PRESETS).map(
4790
+ (p) => `<option value="${p.id}" ${issueTrackerConfig.provider === p.id ? "selected" : ""}>${escHtml(p.label)}</option>`
4791
+ ).join("")}
4792
+ </select>
4793
+ </div>
4794
+ <div class="field-row" style="margin-top:8px">
4795
+ <span class="field-label">${t("CONFIG.ISSUE_TRACKER_BASE_URL")}</span>
4796
+ <input id="issue-tracker-base-url" type="text"
4797
+ placeholder="${escAttr(t("CONFIG.ISSUE_TRACKER_BASE_URL_PH"))}"
4798
+ value="${escAttr(issueTrackerConfig.baseUrl)}"
4799
+ style="flex:1;padding:5px 8px;background:#161b22;border:1px solid #30363d;border-radius:5px;color:#c9d1d9;font-size:12px;outline:none" />
4800
+ </div>` : ""}
4801
+ </div>
5156
4802
 
5157
4803
  <!-- Data -->
5158
4804
  <div class="card card-wide">
@@ -5182,13 +4828,12 @@ var ConfigurationElement = class extends HTMLElement {
5182
4828
  smartSelectorEnabled = true;
5183
4829
  startHidden = false;
5184
4830
  resumeTtlMinutes = DEFAULT_RESUME_TTL_MINUTES;
5185
- issueTrackerProviderId = DEFAULT_ISSUE_TRACKER_PROVIDER_ID;
5186
- issueTrackerParams = {};
5187
4831
  isExporting = false;
5188
4832
  exportMode = "all";
5189
4833
  exportTests = [];
5190
4834
  exportSelectedIds = /* @__PURE__ */ new Set();
5191
4835
  exportSelectedTags = /* @__PURE__ */ new Set();
4836
+ issueTrackerConfig = { ...DEFAULT_ISSUE_TRACKER_CONFIG };
5192
4837
  filesystemGranted = false;
5193
4838
  cypressFolderName = null;
5194
4839
  constructor() {
@@ -5225,12 +4870,11 @@ var ConfigurationElement = class extends HTMLElement {
5225
4870
  this.filesystemGranted = config?.["allowReadWriteFiles"] === "true";
5226
4871
  const handle = config?.["cypressDirectoryHandle"];
5227
4872
  this.cypressFolderName = handle?.name ?? null;
5228
- if (config?.["issueTrackerProviderId"] !== void 0) {
5229
- this.issueTrackerProviderId = config["issueTrackerProviderId"];
5230
- }
5231
- if (config?.["issueTrackerParams"] !== void 0) {
5232
- this.issueTrackerParams = config["issueTrackerParams"];
5233
- }
4873
+ this.issueTrackerConfig = {
4874
+ enabled: config?.["issueTrackerEnabled"] === "true",
4875
+ provider: config?.["issueTrackerProvider"] ?? DEFAULT_ISSUE_TRACKER_CONFIG.provider,
4876
+ baseUrl: config?.["issueTrackerBaseUrl"] ?? ""
4877
+ };
5234
4878
  this.render();
5235
4879
  }
5236
4880
  async onLanguageChange(lang) {
@@ -5263,20 +4907,6 @@ var ConfigurationElement = class extends HTMLElement {
5263
4907
  await this.persistence.setConfig({ [RESUME_TTL_CONFIG_KEY]: safe });
5264
4908
  this.render();
5265
4909
  }
5266
- /** Selects the issue-tracker provider (link-only, no sync — spec 014). */
5267
- async onIssueTrackerProviderChange(id) {
5268
- this.issueTrackerProviderId = id;
5269
- await this.persistence.setConfig({ issueTrackerProviderId: id });
5270
- this.render();
5271
- }
5272
- /** Stores a provider field value, scoped to the active provider so switching keeps values. */
5273
- async onIssueTrackerParamChange(fieldKey, value) {
5274
- const providerId = this.issueTrackerProviderId;
5275
- const current = { ...this.issueTrackerParams[providerId] ?? {} };
5276
- current[fieldKey] = value;
5277
- this.issueTrackerParams = { ...this.issueTrackerParams, [providerId]: current };
5278
- await this.persistence.setConfig({ issueTrackerParams: this.issueTrackerParams });
5279
- }
5280
4910
  onResetWidgetPosition() {
5281
4911
  this.dispatchEvent(new CustomEvent("resetwidgetposition", { bubbles: true, composed: true }));
5282
4912
  showToast(this.t("CONFIG.WIDGET_POSITION_RESET_DONE"));
@@ -5293,6 +4923,23 @@ var ConfigurationElement = class extends HTMLElement {
5293
4923
  this.dispatchEvent(new CustomEvent("selectorstrategychange", { detail: strategy, bubbles: true, composed: true }));
5294
4924
  this.render();
5295
4925
  }
4926
+ async onIssueTrackerEnabledChange(enabled) {
4927
+ this.issueTrackerConfig = { ...this.issueTrackerConfig, enabled };
4928
+ await this.persistence.setConfig({ issueTrackerEnabled: enabled ? "true" : "false" });
4929
+ this.dispatchEvent(new CustomEvent("issuetrackerchange", { detail: { ...this.issueTrackerConfig }, bubbles: true, composed: true }));
4930
+ this.render();
4931
+ }
4932
+ async onIssueTrackerProviderChange(provider) {
4933
+ this.issueTrackerConfig = { ...this.issueTrackerConfig, provider };
4934
+ await this.persistence.setConfig({ issueTrackerProvider: provider });
4935
+ this.dispatchEvent(new CustomEvent("issuetrackerchange", { detail: { ...this.issueTrackerConfig }, bubbles: true, composed: true }));
4936
+ this.render();
4937
+ }
4938
+ async onIssueTrackerBaseUrlChange(baseUrl) {
4939
+ this.issueTrackerConfig = { ...this.issueTrackerConfig, baseUrl };
4940
+ await this.persistence.setConfig({ issueTrackerBaseUrl: baseUrl });
4941
+ this.dispatchEvent(new CustomEvent("issuetrackerchange", { detail: { ...this.issueTrackerConfig }, bubbles: true, composed: true }));
4942
+ }
5296
4943
  async changeFolder() {
5297
4944
  try {
5298
4945
  await this.persistence.requestDirectoryPermissions();
@@ -5390,13 +5037,12 @@ var ConfigurationElement = class extends HTMLElement {
5390
5037
  smartSelectorEnabled: this.smartSelectorEnabled,
5391
5038
  startHidden: this.startHidden,
5392
5039
  resumeTtlMinutes: this.resumeTtlMinutes,
5393
- issueTrackerProviderId: this.issueTrackerProviderId,
5394
- issueTrackerParams: this.issueTrackerParams,
5395
5040
  isExporting: this.isExporting,
5396
5041
  exportMode: this.exportMode,
5397
5042
  exportTests: this.exportTests,
5398
5043
  exportSelectedIds: this.exportSelectedIds,
5399
- exportSelectedTags: this.exportSelectedTags
5044
+ exportSelectedTags: this.exportSelectedTags,
5045
+ issueTrackerConfig: this.issueTrackerConfig
5400
5046
  }, this.t.bind(this))}`;
5401
5047
  ;
5402
5048
  this.shadow.getElementById("lang-select").addEventListener(
@@ -5428,15 +5074,18 @@ var ConfigurationElement = class extends HTMLElement {
5428
5074
  "change",
5429
5075
  (e) => this.onSelectorStrategyChange(e.target.value)
5430
5076
  );
5077
+ ;
5078
+ this.shadow.getElementById("issue-tracker-toggle")?.addEventListener(
5079
+ "change",
5080
+ (e) => this.onIssueTrackerEnabledChange(e.target.checked)
5081
+ );
5431
5082
  this.shadow.getElementById("issue-tracker-provider")?.addEventListener(
5432
5083
  "change",
5433
5084
  (e) => this.onIssueTrackerProviderChange(e.target.value)
5434
5085
  );
5435
- this.shadow.querySelectorAll("[data-tracker-field]").forEach(
5436
- (el) => el.addEventListener(
5437
- "change",
5438
- () => this.onIssueTrackerParamChange(el.dataset["trackerField"] ?? "", el.value)
5439
- )
5086
+ this.shadow.getElementById("issue-tracker-base-url")?.addEventListener(
5087
+ "change",
5088
+ (e) => this.onIssueTrackerBaseUrlChange(e.target.value)
5440
5089
  );
5441
5090
  this.shadow.getElementById("btn-change-folder")?.addEventListener("click", () => this.changeFolder());
5442
5091
  this.shadow.getElementById("btn-revoke")?.addEventListener("click", () => this.revokeAccess());
@@ -6040,9 +5689,6 @@ var FILE_PREVIEW_STYLES = `
6040
5689
  .file-name { flex: 1; font-size: 12px; color: #8b949e;
6041
5690
  font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace; }
6042
5691
  .body { display: flex; flex: 1; overflow: hidden; min-height: 0; }
6043
- /* Host for the CodeMirror editor (or the textarea fallback). */
6044
- .editor-host { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden; }
6045
- .editor-host .cm-editor { flex: 1; min-height: 0; }
6046
5692
  .editor {
6047
5693
  flex: 1; min-width: 0; padding: 14px;
6048
5694
  background: #0d1117; color: #c9d1d9; border: none; outline: none;
@@ -6232,7 +5878,7 @@ function renderFilePreview(state, t) {
6232
5878
  <button id="btn-copy" title="${t("FILE_PREVIEW.COPY_TITLE")}">\u{1F4CB}</button>
6233
5879
  </div>
6234
5880
  <div class="body">
6235
- ${showDiff ? `<div class="diff-panel" id="diff-panel">${buildDiffHtml(originalContent ?? "", currentContent, t)}</div>` : `<div class="editor-host" id="editor-host"><textarea class="editor" id="editor" spellcheck="false">${escHtml(fileContent ?? "")}</textarea></div>`}
5881
+ ${showDiff ? `<div class="diff-panel" id="diff-panel">${buildDiffHtml(originalContent ?? "", currentContent, t)}</div>` : `<textarea class="editor" id="editor" spellcheck="false">${escHtml(fileContent ?? "")}</textarea>`}
6236
5882
  ${blocksPanelHtml}
6237
5883
  </div>
6238
5884
  ${runResultHtml}
@@ -6250,7 +5896,6 @@ function renderFilePreview(state, t) {
6250
5896
  var FilePreviewElement = class extends HTMLElement {
6251
5897
  shadow;
6252
5898
  textarea = null;
6253
- editor = null;
6254
5899
  fileName = null;
6255
5900
  closeLabel = "";
6256
5901
  translation = translationService;
@@ -6276,29 +5921,20 @@ var FilePreviewElement = class extends HTMLElement {
6276
5921
  if (!this.closeLabel) this.closeLabel = this.translation.translate("FILE_PREVIEW.CLOSE_BTN");
6277
5922
  this.render();
6278
5923
  }
6279
- disconnectedCallback() {
6280
- this.editor?.destroy();
6281
- this.editor = null;
6282
- }
6283
5924
  t(key) {
6284
5925
  return this.translation.translate(key);
6285
5926
  }
6286
- /** Current editor content (CodeMirror if upgraded, else the textarea/fallback). */
6287
- currentValue() {
6288
- return this.editor?.getValue() ?? this.textarea?.value ?? this._fileContent ?? "";
6289
- }
6290
5927
  get fileContent() {
6291
5928
  return this._fileContent;
6292
5929
  }
6293
5930
  set fileContent(v) {
6294
5931
  if (this._originalContent === null) this._originalContent = v;
6295
5932
  this._fileContent = v;
6296
- if (this.editor) this.editor.setValue(v ?? "");
6297
- else if (this.textarea) this.textarea.value = v ?? "";
5933
+ if (this.textarea) this.textarea.value = v ?? "";
6298
5934
  else this.render();
6299
5935
  }
6300
5936
  saveFile() {
6301
- const content = this.currentValue();
5937
+ const content = this.textarea?.value ?? this._fileContent ?? "";
6302
5938
  this.dispatchEvent(new CustomEvent("save", { detail: content, bubbles: true, composed: true }));
6303
5939
  }
6304
5940
  onClose() {
@@ -6347,7 +5983,7 @@ var FilePreviewElement = class extends HTMLElement {
6347
5983
  * (and warns) when the file has no valid describe() block to insert into.
6348
5984
  */
6349
5985
  insertBlocks() {
6350
- const base = this.currentValue();
5986
+ const base = this.textarea?.value ?? this._fileContent ?? "";
6351
5987
  let content = base;
6352
5988
  if (this.interceptorsBlock) {
6353
5989
  const merged = advancedTestTransformationService.insertBeforeEach(content, this.interceptorsBlock);
@@ -6367,14 +6003,12 @@ var FilePreviewElement = class extends HTMLElement {
6367
6003
  showToast(this.t("FILE_PREVIEW.INSERT_DONE"));
6368
6004
  }
6369
6005
  render() {
6370
- this.editor?.destroy();
6371
- this.editor = null;
6372
6006
  this.shadow.innerHTML = `<style>${FILE_PREVIEW_STYLES}</style>${renderFilePreview({
6373
6007
  fileName: this.fileName,
6374
6008
  showDiff: this._showDiff,
6375
6009
  fileContent: this._fileContent,
6376
6010
  originalContent: this._originalContent,
6377
- currentContent: this._fileContent ?? "",
6011
+ currentContent: this.textarea?.value ?? this._fileContent ?? "",
6378
6012
  itBlock: this.itBlock,
6379
6013
  interceptorsBlock: this.interceptorsBlock,
6380
6014
  closeLabel: this.closeLabel,
@@ -6388,13 +6022,12 @@ var FilePreviewElement = class extends HTMLElement {
6388
6022
  this.textarea.addEventListener("input", () => {
6389
6023
  this._fileContent = ta.value;
6390
6024
  });
6391
- void this.upgradeEditor();
6392
6025
  }
6393
6026
  this.shadow.getElementById("btn-save")?.addEventListener("click", () => this.saveFile());
6394
6027
  this.shadow.getElementById("btn-close")?.addEventListener("click", () => this.onClose());
6395
6028
  this.shadow.getElementById("btn-launch")?.addEventListener("click", () => this.launchTest());
6396
6029
  this.shadow.getElementById("btn-copy")?.addEventListener("click", () => {
6397
- this.copyToClipboard(this.currentValue());
6030
+ this.copyToClipboard(this.textarea?.value ?? this._fileContent ?? "");
6398
6031
  });
6399
6032
  this.shadow.getElementById("btn-diff")?.addEventListener("click", () => this.toggleDiff());
6400
6033
  this.shadow.getElementById("btn-insert")?.addEventListener("click", () => this.insertBlocks());
@@ -6405,34 +6038,6 @@ var FilePreviewElement = class extends HTMLElement {
6405
6038
  this.copyToClipboard(this.interceptorsBlock);
6406
6039
  });
6407
6040
  }
6408
- /**
6409
- * Lazily upgrades the textarea to a CodeMirror editor (syntax highlight +
6410
- * Cypress autocomplete). On any failure the textarea is left in place, so the
6411
- * editor degrades gracefully when CodeMirror can't load.
6412
- */
6413
- async upgradeEditor() {
6414
- const host = this.shadow.getElementById("editor-host");
6415
- const ta = this.textarea;
6416
- if (!host || !ta) return;
6417
- try {
6418
- const handle = await createCodeEditor({
6419
- parent: host,
6420
- root: this.shadow,
6421
- doc: this._fileContent ?? "",
6422
- onChange: (value) => {
6423
- this._fileContent = value;
6424
- }
6425
- });
6426
- if (!host.isConnected) {
6427
- handle.destroy();
6428
- return;
6429
- }
6430
- ta.remove();
6431
- this.textarea = null;
6432
- this.editor = handle;
6433
- } catch {
6434
- }
6435
- }
6436
6041
  };
6437
6042
  if (!customElements.get("file-preview")) {
6438
6043
  customElements.define("file-preview", FilePreviewElement);
@@ -6789,6 +6394,26 @@ function renderRecorderWidget(rec, paused, t) {
6789
6394
  <div class="rec-badge">${paused ? t("RECORDER.BADGE_PAUSED") : t("RECORDER.BADGE_REC")}</div>`;
6790
6395
  }
6791
6396
 
6397
+ // src/utils/fixture-convert.utils.ts
6398
+ function toFixtureInterceptors(interceptors, fixtures) {
6399
+ const fixtureNames = new Set(fixtures.map((f) => f.name));
6400
+ return interceptors.map((icp) => {
6401
+ const match = icp.match(/\.as\('([^']+)'\)$/);
6402
+ if (!match) return icp;
6403
+ const fixtureFile = `${match[1]}.json`;
6404
+ if (!fixtureNames.has(fixtureFile)) return icp;
6405
+ return icp.replace(/\)\.as\(/, `, { fixture: '${fixtureFile}' }).as(`);
6406
+ });
6407
+ }
6408
+ function simplifyFixtureWaits(commands, fixtures) {
6409
+ const fixtureAliases = new Set(fixtures.map((f) => f.name.replace(/\.json$/, "")));
6410
+ return commands.map((cmd) => {
6411
+ const match = cmd.match(/^cy\.wait\('@([^']+)'\)/);
6412
+ if (!match || !fixtureAliases.has(match[1])) return cmd;
6413
+ return `cy.wait('@${match[1]}').then((interception) => { })`;
6414
+ });
6415
+ }
6416
+
6792
6417
  // src/components/lib-e2e-recorder/lib-e2e-recorder.ts
6793
6418
  var LibE2eRecorderElement = class extends HTMLElement {
6794
6419
  shadow;
@@ -6815,6 +6440,7 @@ var LibE2eRecorderElement = class extends HTMLElement {
6815
6440
  httpMonitor;
6816
6441
  smartSelectorEnabled = true;
6817
6442
  _needsRecordingRebuild = false;
6443
+ issueTrackerConfig = { ...DEFAULT_ISSUE_TRACKER_CONFIG };
6818
6444
  recording;
6819
6445
  persistence;
6820
6446
  translation;
@@ -6958,6 +6584,11 @@ var LibE2eRecorderElement = class extends HTMLElement {
6958
6584
  const strategy = config?.["selectorStrategy"];
6959
6585
  if (strategy) this.recording.selectorStrategy = strategy;
6960
6586
  this.smartSelectorEnabled = config?.["smartSelectorEnabled"] !== "false";
6587
+ this.issueTrackerConfig = {
6588
+ enabled: config?.["issueTrackerEnabled"] === "true",
6589
+ provider: config?.["issueTrackerProvider"] ?? DEFAULT_ISSUE_TRACKER_CONFIG.provider,
6590
+ baseUrl: config?.["issueTrackerBaseUrl"] ?? ""
6591
+ };
6961
6592
  }
6962
6593
  // ── cross-app session continuity (spec 006) ───────────────────────────────
6963
6594
  /**
@@ -7383,6 +7014,7 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
7383
7014
  const child = document.createElement("test-editor");
7384
7015
  child.persistence = this.persistence;
7385
7016
  child.translation = this.translation;
7017
+ child.issueTrackerConfig = this.issueTrackerConfig;
7386
7018
  container.appendChild(child);
7387
7019
  },
7388
7020
  willClose: () => {
@@ -7408,19 +7040,16 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
7408
7040
  if (!container) return;
7409
7041
  const child = document.createElement("save-test");
7410
7042
  child.translation = this.translation;
7411
- this.persistence.getGeneralConfig().then((config) => {
7412
- const providerId = config?.["issueTrackerProviderId"];
7413
- if (providerId) child.issueTrackerProviderId = providerId;
7414
- });
7043
+ child.issueTrackerConfig = this.issueTrackerConfig;
7415
7044
  container.appendChild(child);
7416
7045
  child.addEventListener("savetest", (e) => {
7417
7046
  const { description, notes, tags, ticketId } = e.detail ?? {};
7418
- this.onSaveTest(description ?? null, tags ?? [], notes ?? "", ticketId ?? null);
7047
+ this.onSaveTest(description ?? null, tags ?? [], notes ?? "", ticketId ?? "");
7419
7048
  import_sweetalert2.default.close();
7420
7049
  });
7421
7050
  child.addEventListener("saveandexport", (e) => {
7422
7051
  const { description, notes, tags, ticketId } = e.detail ?? {};
7423
- this.onSaveAndExportTest(description ?? null, tags ?? [], notes ?? "", ticketId ?? null);
7052
+ this.onSaveAndExportTest(description ?? null, tags ?? [], notes ?? "", ticketId ?? "");
7424
7053
  import_sweetalert2.default.close();
7425
7054
  });
7426
7055
  },
@@ -7456,6 +7085,9 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
7456
7085
  this.style.display = this.isVisible ? "" : "none";
7457
7086
  });
7458
7087
  child.addEventListener("resetwidgetposition", () => this.resetWidgetPosition());
7088
+ child.addEventListener("issuetrackerchange", (e) => {
7089
+ this.issueTrackerConfig = e.detail;
7090
+ });
7459
7091
  },
7460
7092
  willClose: () => {
7461
7093
  this.isSettingsDialogOpen = false;
@@ -7608,38 +7240,36 @@ cypress/ <span style="color:#484f58">${this.translation.translate("RECOR
7608
7240
  this.resizePopup();
7609
7241
  }
7610
7242
  // ── save handlers ─────────────────────────────────────────────────────────
7611
- async onSaveTest(description, tags = [], notes = "", ticketId = null) {
7612
- if (description) {
7613
- const fixtures = this.recording.getFixturesSnapshot();
7614
- await this.persistence.insertTest(description, this.cypressCommands, this.interceptors, tags, notes, ticketId ?? void 0);
7615
- await this.writeFixturesIfAny(fixtures);
7616
- this.recording.clearCommands();
7617
- this.clearRecordingHistory();
7618
- this.cypressCommands = [];
7619
- this.interceptors = [];
7620
- }
7621
- }
7622
- async onSaveAndExportTest(description, tags = [], notes = "", ticketId = null) {
7623
- if (description) {
7624
- const fixtures = this.recording.getFixturesSnapshot();
7625
- const id = await this.persistence.insertTest(description, this.cypressCommands, this.interceptors, tags, notes, ticketId ?? void 0);
7626
- await this.writeFixturesIfAny(fixtures);
7627
- this.recording.clearCommands();
7628
- this.clearRecordingHistory();
7629
- this.cypressCommands = [];
7630
- this.interceptors = [];
7631
- setTimeout(() => this.showAdvancedEditorDialog(id), 300);
7632
- }
7633
- }
7634
- /** Writes captured fixtures to cypress/fixtures and toasts the outcome (spec 012). */
7635
- async writeFixturesIfAny(fixtures) {
7636
- if (!fixtures.length) return;
7637
- try {
7638
- const n = await this.persistence.writeFixtures(fixtures);
7639
- showToast(`${this.translation.translate("RECORDER.FIXTURES_WRITTEN_TOAST")} (${n})`);
7640
- } catch {
7641
- showToast(this.translation.translate("RECORDER.FIXTURES_NO_FOLDER_TOAST"), false);
7243
+ async onSaveTest(description, tags = [], notes = "", ticketId = "") {
7244
+ if (!description) return;
7245
+ await this.persistence.insertTest(description, this.cypressCommands, this.interceptors, tags, notes, ticketId || void 0);
7246
+ this.recording.clearCommands();
7247
+ this.clearRecordingHistory();
7248
+ this.cypressCommands = [];
7249
+ this.interceptors = [];
7250
+ }
7251
+ async onSaveAndExportTest(description, tags = [], notes = "", ticketId = "") {
7252
+ if (!description) return;
7253
+ const fixtures = this.recording.getFixturesSnapshot();
7254
+ const isFixtureMode = this.httpMonitor?.isFixtureModeEnabled() ?? false;
7255
+ let finalInterceptors = this.interceptors;
7256
+ let finalCommands = this.cypressCommands;
7257
+ if (isFixtureMode && fixtures.length > 0) {
7258
+ try {
7259
+ const n = await this.persistence.writeFixtures(fixtures);
7260
+ finalInterceptors = toFixtureInterceptors(this.interceptors, fixtures);
7261
+ finalCommands = simplifyFixtureWaits(this.cypressCommands, fixtures);
7262
+ showToast(`${this.translation.translate("RECORDER.FIXTURES_WRITTEN_TOAST")} (${n})`);
7263
+ } catch {
7264
+ showToast(this.translation.translate("RECORDER.FIXTURES_NO_FOLDER_TOAST"), false);
7265
+ }
7642
7266
  }
7267
+ const id = await this.persistence.insertTest(description, finalCommands, finalInterceptors, tags, notes, ticketId || void 0);
7268
+ this.recording.clearCommands();
7269
+ this.clearRecordingHistory();
7270
+ this.cypressCommands = [];
7271
+ this.interceptors = [];
7272
+ setTimeout(() => this.showAdvancedEditorDialog(id), 300);
7643
7273
  }
7644
7274
  // ── helpers ───────────────────────────────────────────────────────────────
7645
7275
  toggleModal(flag, openFn) {
@@ -7685,26 +7315,21 @@ if (!customElements.get("lib-e2e-recorder")) {
7685
7315
  }
7686
7316
 
7687
7317
  // src/index.ts
7688
- var VERSION = "0.8.0";
7318
+ var VERSION = "0.9.0";
7689
7319
  // Annotate the CommonJS export names for ESM import in node:
7690
7320
  0 && (module.exports = {
7691
7321
  ACTIVE_SESSION_BREADCRUMB_KEY,
7692
7322
  AdvancedTestEditorElement,
7693
7323
  AdvancedTestTransformationService,
7694
- CHAIN_METHODS,
7695
- CYPRESS_COMPLETIONS,
7696
- CY_METHODS,
7697
7324
  ConfigurationElement,
7698
7325
  DB_SCHEMA,
7699
7326
  DB_STORE_NAMES,
7700
- DEFAULT_ISSUE_TRACKER_PROVIDER_ID,
7701
7327
  DEFAULT_RESUME_TTL_MINUTES,
7702
7328
  DRAG_THRESHOLD,
7703
7329
  FilePreviewElement,
7704
7330
  HelpPanelElement,
7705
7331
  HttpMonitor,
7706
7332
  INPUT_TYPES,
7707
- ISSUE_TRACKER_PROVIDERS,
7708
7333
  LIB_E2E_CYPRESS_FOR_DUMMYS_SWAL2_STYLES,
7709
7334
  LOCALE_BY_LANG,
7710
7335
  LibE2eRecorderElement,
@@ -7717,7 +7342,6 @@ var VERSION = "0.8.0";
7717
7342
  SelectorPickerElement,
7718
7343
  Subject,
7719
7344
  TOGGLE_MARGIN,
7720
- TOP_LEVEL,
7721
7345
  TestEditorElement,
7722
7346
  TestPrevisualizerElement,
7723
7347
  TransformationService,
@@ -7726,14 +7350,8 @@ var VERSION = "0.8.0";
7726
7350
  advancedTestTransformationService,
7727
7351
  boxOffsetForDirection,
7728
7352
  boxTopLeftFor,
7729
- buildTicketComment,
7730
- buildTicketUrl,
7731
7353
  clampTogglePosition,
7732
- createCodeEditor,
7733
7354
  defaultTogglePosition,
7734
- filterCompletions,
7735
- filterCypressCompletions,
7736
- findIssueTrackerProvider,
7737
7355
  generateAlias,
7738
7356
  inferAssertionCommand,
7739
7357
  injectStyles,
@@ -7745,7 +7363,6 @@ var VERSION = "0.8.0";
7745
7363
  makeSwalDraggableByContentId,
7746
7364
  persistenceService,
7747
7365
  resolveExpandDirection,
7748
- resolveTicketUrl,
7749
7366
  selectTestsForExport,
7750
7367
  setSwal2DataCyAttribute,
7751
7368
  showToast,