@oneuptime/common 14.0.10 → 14.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (837) hide show
  1. package/Models/DatabaseModels/AIConversation.ts +127 -1
  2. package/Models/DatabaseModels/CephCluster.ts +12 -0
  3. package/Models/DatabaseModels/CloudResource.ts +12 -0
  4. package/Models/DatabaseModels/DatabaseServer.ts +12 -0
  5. package/Models/DatabaseModels/DockerHost.ts +12 -0
  6. package/Models/DatabaseModels/DockerSwarmCluster.ts +12 -0
  7. package/Models/DatabaseModels/GlobalOidc.ts +0 -4
  8. package/Models/DatabaseModels/GlobalOidcProject.ts +0 -4
  9. package/Models/DatabaseModels/GlobalSso.ts +0 -4
  10. package/Models/DatabaseModels/GlobalSsoProject.ts +0 -4
  11. package/Models/DatabaseModels/Host.ts +12 -0
  12. package/Models/DatabaseModels/IncidentForm.ts +594 -0
  13. package/Models/DatabaseModels/IncidentFormSubmission.ts +283 -0
  14. package/Models/DatabaseModels/IncidentTemplate.ts +59 -0
  15. package/Models/DatabaseModels/Index.ts +14 -0
  16. package/Models/DatabaseModels/IoTFleet.ts +12 -0
  17. package/Models/DatabaseModels/KubernetesCluster.ts +12 -0
  18. package/Models/DatabaseModels/MessageQueue.ts +1102 -0
  19. package/Models/DatabaseModels/MessageQueueLabelRule.ts +553 -0
  20. package/Models/DatabaseModels/MessageQueueOwnerRule.ts +633 -0
  21. package/Models/DatabaseModels/MessageQueueOwnerTeam.ts +491 -0
  22. package/Models/DatabaseModels/MessageQueueOwnerUser.ts +490 -0
  23. package/Models/DatabaseModels/PodmanHost.ts +12 -0
  24. package/Models/DatabaseModels/Project.ts +59 -0
  25. package/Models/DatabaseModels/ProjectOidc.ts +0 -4
  26. package/Models/DatabaseModels/ProjectSso.ts +0 -4
  27. package/Models/DatabaseModels/ProxmoxCluster.ts +12 -0
  28. package/Models/DatabaseModels/RumApplication.ts +12 -0
  29. package/Models/DatabaseModels/ServerlessFunction.ts +12 -0
  30. package/Models/DatabaseModels/Service.ts +12 -0
  31. package/Models/DatabaseModels/StatusPageOidc.ts +0 -4
  32. package/Models/DatabaseModels/StatusPageSso.ts +0 -4
  33. package/Models/DatabaseModels/TeamComplianceSetting.ts +47 -2
  34. package/Models/DatabaseModels/VMwareVCenter.ts +12 -0
  35. package/Server/API/AIChatAPI.ts +13 -114
  36. package/Server/API/AIInvestigationAPI.ts +3 -3
  37. package/Server/API/AIInvestigationConversationAPI.ts +266 -0
  38. package/Server/API/IncidentFormAPI.ts +376 -0
  39. package/Server/API/ProjectOIDC.ts +16 -25
  40. package/Server/API/ProjectSSO.ts +16 -24
  41. package/Server/API/StatusPageAPI.ts +42 -77
  42. package/Server/Enterprise/EnterpriseEdition.ts +93 -28
  43. package/Server/Enterprise/EnterpriseFeature.ts +25 -3
  44. package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +1 -1
  45. package/Server/Enterprise/EnterpriseServerModule.ts +4 -1
  46. package/Server/EnvironmentConfig.ts +1 -1
  47. package/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.ts +129 -0
  48. package/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.ts +52 -0
  49. package/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.ts +75 -0
  50. package/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.ts +477 -0
  51. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  52. package/Server/Middleware/IncidentFormRateLimit.ts +847 -0
  53. package/Server/Middleware/UserAuthorization.ts +16 -38
  54. package/Server/Services/AIService.ts +10 -13
  55. package/Server/Services/IncidentCustomFieldService.ts +112 -2
  56. package/Server/Services/IncidentEpisodeMemberService.ts +24 -4
  57. package/Server/Services/IncidentEpisodeService.ts +10 -1
  58. package/Server/Services/IncidentFormService.ts +1566 -0
  59. package/Server/Services/IncidentFormSubmissionService.ts +97 -0
  60. package/Server/Services/IncidentGroupingEngineService.ts +45 -8
  61. package/Server/Services/IncidentService.ts +123 -5
  62. package/Server/Services/IncidentTemplateService.ts +22 -0
  63. package/Server/Services/Index.ts +14 -0
  64. package/Server/Services/MessageQueueLabelRuleEngineService.ts +321 -0
  65. package/Server/Services/MessageQueueLabelRuleService.ts +14 -0
  66. package/Server/Services/MessageQueueOwnerRuleEngineService.ts +412 -0
  67. package/Server/Services/MessageQueueOwnerRuleService.ts +14 -0
  68. package/Server/Services/MessageQueueOwnerTeamService.ts +62 -0
  69. package/Server/Services/MessageQueueOwnerUserService.ts +62 -0
  70. package/Server/Services/MessageQueueService.ts +1463 -0
  71. package/Server/Services/OpenTelemetryIngestService.ts +37 -4
  72. package/Server/Services/ProjectService.ts +0 -59
  73. package/Server/Services/StatusPagePrivateUserService.ts +1 -4
  74. package/Server/Services/StatusPageService.ts +1 -51
  75. package/Server/Services/TeamComplianceSettingService.ts +163 -42
  76. package/Server/Services/UserNotificationRuleService.ts +8 -1
  77. package/Server/Services/WorkspaceNotificationSummaryService.ts +12 -3
  78. package/Server/Types/Database/Permissions/CreatePermission.ts +17 -3
  79. package/Server/Types/Database/Permissions/EditionPermission.ts +140 -38
  80. package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +16 -0
  81. package/Server/Types/Database/Permissions/TablePermission.ts +3 -3
  82. package/Server/Types/Database/Permissions/UpdatePermission.ts +20 -6
  83. package/Server/Utils/AI/Chat/AgentContextCompactor.ts +200 -0
  84. package/Server/Utils/AI/Chat/ChatAgentRunner.ts +307 -48
  85. package/Server/Utils/AI/Chat/ChatRunCancellation.ts +156 -0
  86. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +78 -18
  87. package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +1 -1
  88. package/Server/Utils/AI/Chat/ToolOutputPager.ts +322 -0
  89. package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +88 -30
  90. package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +13 -1
  91. package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +0 -32
  92. package/Server/Utils/AI/CodeFix/FixRunBudget.ts +27 -25
  93. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +7 -1
  94. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +34 -14
  95. package/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.ts +69 -24
  96. package/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.ts +9 -0
  97. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +53 -10
  98. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +27 -10
  99. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +27 -10
  100. package/Server/Utils/AI/SRE/InvestigationQueue.ts +16 -19
  101. package/Server/Utils/AI/SRE/InvestigationThread.ts +646 -0
  102. package/Server/Utils/AI/SRE/InvestigationThreadService.ts +715 -0
  103. package/Server/Utils/AI/Toolbox/Serializer.ts +10 -3
  104. package/Server/Utils/EditionEnforcement.ts +19 -202
  105. package/Server/Utils/LLM/LLMService.ts +259 -63
  106. package/Server/Utils/RelaxedScimTeamLocksReport.ts +245 -0
  107. package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +25 -0
  108. package/Server/Utils/SameOriginRequest.ts +203 -0
  109. package/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.ts +2 -0
  110. package/Server/Utils/Telemetry/EntityRegistry.ts +4 -1
  111. package/Server/Utils/Telemetry/MessageQueueDiscovery.ts +1402 -0
  112. package/Server/Utils/Telemetry/ResourceEntityFilter.ts +4 -1
  113. package/Server/Utils/VendorAssets.ts +3 -3
  114. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +3 -1
  115. package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +3 -1
  116. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +10 -1
  117. package/Tests/App/Accounts/IncidentFormPage.test.tsx +2036 -0
  118. package/Tests/App/Accounts/IncidentFormServerMessages.test.ts +119 -0
  119. package/Tests/App/Accounts/IncidentFormUtils.test.ts +1157 -0
  120. package/Tests/App/Accounts/LoginPasskey.test.tsx +6 -4
  121. package/Tests/App/Accounts/LoginSso.test.tsx +724 -0
  122. package/Tests/App/AdminDashboard/AdminAuthenticationRequireSso.test.tsx +338 -0
  123. package/Tests/App/AdminDashboard/AdminAuthenticationSsoCardLocales.test.tsx +331 -0
  124. package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +47 -33
  125. package/Tests/App/AdminDashboard/GlobalSsoOidcPages.test.tsx +1021 -0
  126. package/Tests/App/Dashboard/AffectedResourcesDisplay.test.tsx +876 -78
  127. package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +20 -3
  128. package/Tests/App/Dashboard/CephProxmoxVMwareDocumentationCards.test.tsx +359 -0
  129. package/Tests/App/Dashboard/CephSetupGuide.test.ts +961 -0
  130. package/Tests/App/Dashboard/CloudDocumentationCard.test.tsx +381 -95
  131. package/Tests/App/Dashboard/CloudSetupGuide.test.ts +1463 -0
  132. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +174 -12
  133. package/Tests/App/Dashboard/ContainerSetupGuideCards.test.tsx +345 -0
  134. package/Tests/App/Dashboard/CoreListEmptyStates.test.tsx +501 -0
  135. package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +0 -1
  136. package/Tests/App/Dashboard/DatabaseDocumentationCard.test.tsx +400 -0
  137. package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +5 -0
  138. package/Tests/App/Dashboard/DatabaseSetupGuide.test.ts +1869 -0
  139. package/Tests/App/Dashboard/DatabaseTelemetryScope.test.ts +44 -0
  140. package/Tests/App/Dashboard/DatabaseTelemetryTabs.test.tsx +50 -9
  141. package/Tests/App/Dashboard/DockerSetupGuide.test.ts +899 -0
  142. package/Tests/App/Dashboard/DockerSwarmSetupGuide.test.ts +964 -0
  143. package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +141 -7
  144. package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +52 -39
  145. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +7 -1
  146. package/Tests/App/Dashboard/GettingStartedTaskRoutes.test.tsx +331 -0
  147. package/Tests/App/Dashboard/HomeOverviewStatsFirstRun.test.tsx +925 -0
  148. package/Tests/App/Dashboard/HostDocumentationCard.test.tsx +225 -0
  149. package/Tests/App/Dashboard/HostSetupGuide.test.ts +1183 -0
  150. package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +69 -45
  151. package/Tests/App/Dashboard/IncidentCreateTemplateCustomFieldSettings.test.tsx +745 -0
  152. package/Tests/App/Dashboard/IncidentCreateTemplateOwners.test.tsx +414 -0
  153. package/Tests/App/Dashboard/IncidentCustomFieldCreateSettingsForm.test.ts +1042 -0
  154. package/Tests/App/Dashboard/IncidentCustomFieldSettingsCard.test.tsx +1816 -0
  155. package/Tests/App/Dashboard/IncidentFormPlan.test.ts +89 -0
  156. package/Tests/App/Dashboard/IncidentFormShareLinkCard.test.tsx +846 -0
  157. package/Tests/App/Dashboard/IncidentFormsPages.test.tsx +1324 -0
  158. package/Tests/App/Dashboard/IncidentTemplateCustomFieldSettings.test.tsx +630 -0
  159. package/Tests/App/Dashboard/IncidentsSideMenu.test.tsx +13 -0
  160. package/Tests/App/Dashboard/IncomingEmailMonitorLinkVerifyHint.test.tsx +104 -0
  161. package/Tests/App/Dashboard/InvestigationConversation.test.tsx +1107 -0
  162. package/Tests/App/Dashboard/InvestigationConversationData.test.ts +546 -0
  163. package/Tests/App/Dashboard/InvestigationEvidenceList.test.tsx +170 -5
  164. package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +207 -2
  165. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +729 -34
  166. package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +9 -21
  167. package/Tests/App/Dashboard/InvestigationReportView.test.tsx +274 -37
  168. package/Tests/App/Dashboard/InvestigationStatusBadge.test.tsx +130 -0
  169. package/Tests/App/Dashboard/IoTDocumentationCard.test.tsx +218 -0
  170. package/Tests/App/Dashboard/IoTSetupGuide.test.ts +1124 -0
  171. package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +20 -6
  172. package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +107 -83
  173. package/Tests/App/Dashboard/KubernetesSetupGuide.test.ts +787 -0
  174. package/Tests/App/Dashboard/MessageQueueBreadcrumbs.test.tsx +228 -0
  175. package/Tests/App/Dashboard/MessageQueueBrokerHealth.test.tsx +1538 -0
  176. package/Tests/App/Dashboard/MessageQueueBrokerHealthMetricChart.test.tsx +969 -0
  177. package/Tests/App/Dashboard/MessageQueueBrokerHealthReads.test.ts +889 -0
  178. package/Tests/App/Dashboard/MessageQueueBrokerMetricsReach.test.ts +236 -0
  179. package/Tests/App/Dashboard/MessageQueueDocumentationCard.test.tsx +479 -0
  180. package/Tests/App/Dashboard/MessageQueueListPages.test.tsx +1232 -0
  181. package/Tests/App/Dashboard/MessageQueueMetricDescriptions.test.ts +244 -0
  182. package/Tests/App/Dashboard/MessageQueueMonitorLink.test.ts +952 -0
  183. package/Tests/App/Dashboard/MessageQueueMonitorLinkMonitorCreate.test.tsx +859 -0
  184. package/Tests/App/Dashboard/MessageQueueOverview.test.tsx +1773 -0
  185. package/Tests/App/Dashboard/MessageQueueOverviewPresentation.test.ts +196 -0
  186. package/Tests/App/Dashboard/MessageQueuePresentation.test.ts +799 -0
  187. package/Tests/App/Dashboard/MessageQueueProducersConsumers.test.tsx +680 -0
  188. package/Tests/App/Dashboard/MessageQueueRoutes.test.tsx +383 -0
  189. package/Tests/App/Dashboard/MessageQueueRulePages.test.tsx +278 -0
  190. package/Tests/App/Dashboard/MessageQueueSetupGuide.test.ts +2778 -0
  191. package/Tests/App/Dashboard/MessageQueueSideMenus.test.tsx +273 -0
  192. package/Tests/App/Dashboard/MessageQueueSpanPopulations.test.ts +930 -0
  193. package/Tests/App/Dashboard/MessageQueueSpanStore.ts +351 -0
  194. package/Tests/App/Dashboard/MessageQueueTelemetryScope.test.ts +375 -0
  195. package/Tests/App/Dashboard/MessageQueueTelemetryScopeTabs.test.tsx +668 -0
  196. package/Tests/App/Dashboard/MessageQueueViewPages.test.tsx +685 -0
  197. package/Tests/App/Dashboard/MonitorAndHomeNotOperationalMenus.test.tsx +342 -0
  198. package/Tests/App/Dashboard/MonitorCreateFirstRunDefaults.test.tsx +623 -0
  199. package/Tests/App/Dashboard/MonitorCreateFromMonitorBackedDevice.test.tsx +442 -0
  200. package/Tests/App/Dashboard/MonitorLogsPageViewSummary.test.tsx +814 -0
  201. package/Tests/App/Dashboard/MonitorSetupCardBodies.test.tsx +246 -0
  202. package/Tests/App/Dashboard/NavigationSearchKeywords.test.tsx +8 -0
  203. package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +27 -12
  204. package/Tests/App/Dashboard/PlanGatedPage.test.tsx +292 -0
  205. package/Tests/App/Dashboard/PodmanSetupGuide.test.ts +948 -0
  206. package/Tests/App/Dashboard/ProductOrientation.test.tsx +5 -4
  207. package/Tests/App/Dashboard/ProductSettingsSectionCollapsed.test.tsx +266 -0
  208. package/Tests/App/Dashboard/ProxmoxSetupGuide.test.ts +1318 -0
  209. package/Tests/App/Dashboard/ResourceAiAgentAttentionServerParity.test.ts +555 -0
  210. package/Tests/App/Dashboard/ResourceAiAgentPage.test.tsx +298 -9
  211. package/Tests/App/Dashboard/ResourceAiAgentStatus.test.ts +453 -0
  212. package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +16 -0
  213. package/Tests/App/Dashboard/ResourceConnectionGuideCard.test.tsx +444 -0
  214. package/Tests/App/Dashboard/ResourceConnectionGuideOverviewsInfra.test.tsx +798 -0
  215. package/Tests/App/Dashboard/ResourceConnectionGuideOverviewsPlatforms.test.tsx +717 -0
  216. package/Tests/App/Dashboard/ResourceConnectionGuides.test.ts +981 -0
  217. package/Tests/App/Dashboard/ResourceConnectionState.test.ts +132 -0
  218. package/Tests/App/Dashboard/RumDocumentationCard.test.tsx +293 -0
  219. package/Tests/App/Dashboard/RumSessionReplayRetentionSettings.test.tsx +467 -0
  220. package/Tests/App/Dashboard/RumSetupGuide.test.ts +758 -0
  221. package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +25 -0
  222. package/Tests/App/Dashboard/ServerlessDocumentationCard.test.tsx +254 -0
  223. package/Tests/App/Dashboard/ServerlessSetupGuide.test.ts +1074 -0
  224. package/Tests/App/Dashboard/SetupGuide.test.ts +246 -0
  225. package/Tests/App/Dashboard/SetupGuideCard.test.tsx +756 -0
  226. package/Tests/App/Dashboard/SloListSideMenu.test.tsx +120 -0
  227. package/Tests/App/Dashboard/SsoPages.test.tsx +833 -0
  228. package/Tests/App/Dashboard/TelemetryDocumentation.test.tsx +253 -0
  229. package/Tests/App/Dashboard/TelemetryRetentionShells.test.tsx +730 -0
  230. package/Tests/App/Dashboard/VMwareSetupGuide.test.ts +1033 -0
  231. package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +87 -7
  232. package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +28 -9
  233. package/Tests/Models/DatabaseModels/IncidentFormModel.test.ts +1250 -0
  234. package/Tests/Models/DatabaseModels/MessageQueueModels.test.ts +1983 -0
  235. package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
  236. package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +190 -14
  237. package/Tests/Models/IncidentTemplateCustomFieldSettingsColumn.test.ts +291 -0
  238. package/Tests/Server/API/AIInvestigationConversationAPI.test.ts +412 -0
  239. package/Tests/Server/API/GlobalConfigLicense.test.ts +38 -9
  240. package/Tests/Server/API/IncidentFormAPI.test.ts +942 -0
  241. package/Tests/Server/API/IncidentFormPublicRoutes.test.ts +1653 -0
  242. package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -1
  243. package/Tests/Server/API/{SsoProviderListingsEdition.test.ts → SsoProviderListings.test.ts} +164 -110
  244. package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +76 -54
  245. package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +125 -42
  246. package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +36 -10
  247. package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +51 -9
  248. package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +13 -8
  249. package/Tests/Server/Enterprise/SsoInCommunityEdition.test.ts +540 -0
  250. package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
  251. package/Tests/Server/Infrastructure/Postgres/AddIncidentFormsMigration.test.ts +438 -0
  252. package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleNotificationChannelsMigration.test.ts +255 -0
  253. package/Tests/Server/Infrastructure/Postgres/MessageQueueTablesMigration.test.ts +637 -0
  254. package/Tests/Server/Middleware/IncidentFormRateLimit.test.ts +1630 -0
  255. package/Tests/Server/Middleware/UserAuthorization.test.ts +16 -17
  256. package/Tests/Server/Middleware/UserAuthorizationSsoEnforcement.test.ts +544 -0
  257. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +37 -8
  258. package/Tests/Server/Services/IncidentCreatedFeedTitle.test.ts +212 -0
  259. package/Tests/Server/Services/IncidentCustomFieldDeleteFormQuestions.test.ts +297 -0
  260. package/Tests/Server/Services/IncidentFormOwnersCreatedNotification.test.ts +497 -0
  261. package/Tests/Server/Services/IncidentFormPostgres.test.ts +958 -0
  262. package/Tests/Server/Services/IncidentFormPublicForm.test.ts +1260 -0
  263. package/Tests/Server/Services/IncidentFormPublicSubmission.test.ts +2520 -0
  264. package/Tests/Server/Services/IncidentFormRegistration.test.ts +159 -0
  265. package/Tests/Server/Services/IncidentFormService.test.ts +663 -0
  266. package/Tests/Server/Services/IncidentFormSubmissionService.test.ts +430 -0
  267. package/Tests/Server/Services/IncidentFormSubmissionTemplateBranch.test.ts +473 -0
  268. package/Tests/Server/Services/IncidentGroupingTemplateSubstitution.test.ts +81 -0
  269. package/Tests/Server/Services/IncidentTemplateCustomFieldSettings.test.ts +262 -0
  270. package/Tests/Server/Services/IncidentTemplateOwnersOnDeclare.test.ts +566 -0
  271. package/Tests/Server/Services/IncidentTitleMarkdownSinks.test.ts +640 -0
  272. package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +3 -3
  273. package/Tests/Server/Services/MessageQueueOwnerServices.test.ts +440 -0
  274. package/Tests/Server/Services/MessageQueueRegistries.test.ts +577 -0
  275. package/Tests/Server/Services/MessageQueueRuleEngines.test.ts +900 -0
  276. package/Tests/Server/Services/MessageQueueService.test.ts +2210 -0
  277. package/Tests/Server/Services/MessageQueueSqlPostgres.test.ts +1271 -0
  278. package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +28 -1
  279. package/Tests/Server/Services/OpenTelemetryRetentionOverrideEdition.test.ts +474 -0
  280. package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +21 -0
  281. package/Tests/Server/Services/OwnerRowUniqueness.test.ts +3 -0
  282. package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +0 -1
  283. package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +56 -6
  284. package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -1
  285. package/Tests/Server/Services/SsoRequirementReads.test.ts +454 -0
  286. package/Tests/Server/Services/SsoRequirementWrites.test.ts +529 -0
  287. package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +3 -3
  288. package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +107 -89
  289. package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +1068 -176
  290. package/Tests/Server/Services/TeamComplianceSettingService.test.ts +1962 -200
  291. package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +654 -297
  292. package/Tests/Server/Types/Database/Permissions/RetentionOverrideColumns.test.ts +784 -0
  293. package/Tests/Server/Types/Database/Permissions/SsoScalePlanGate.test.ts +418 -0
  294. package/Tests/Server/Utils/AI/AIAlertGating.test.ts +0 -1
  295. package/Tests/Server/Utils/AI/AIInvestigationEngineRunLimits.test.ts +136 -0
  296. package/Tests/Server/Utils/AI/AIInvestigationQueue.test.ts +15 -14
  297. package/Tests/Server/Utils/AI/AgentContextCompactor.test.ts +351 -0
  298. package/Tests/Server/Utils/AI/ChatAgentRunnerSharedThread.test.ts +842 -0
  299. package/Tests/Server/Utils/AI/ChatRunCancellation.test.ts +195 -0
  300. package/Tests/Server/Utils/AI/CodeFixReadiness.test.ts +10 -57
  301. package/Tests/Server/Utils/AI/FixRunBudget.test.ts +20 -24
  302. package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +7 -30
  303. package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +225 -29
  304. package/Tests/Server/Utils/AI/InvestigationInfrastructureAccessWiring.test.ts +266 -25
  305. package/Tests/Server/Utils/AI/InvestigationThread.test.ts +580 -0
  306. package/Tests/Server/Utils/AI/InvestigationThreadService.test.ts +801 -0
  307. package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +525 -43
  308. package/Tests/Server/Utils/AI/KubectlJobRunner.test.ts +4 -2
  309. package/Tests/Server/Utils/AI/LLMServiceUnreachableProvider.test.ts +435 -0
  310. package/Tests/Server/Utils/AI/ObservabilityAssistantLimits.test.ts +462 -0
  311. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +14 -25
  312. package/Tests/Server/Utils/AI/RemediationExecutionRunnerResourceMode.test.ts +2 -0
  313. package/Tests/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.test.ts +297 -15
  314. package/Tests/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.test.ts +10 -6
  315. package/Tests/Server/Utils/AI/ToolOutputPager.test.ts +382 -0
  316. package/Tests/Server/Utils/AI/ToolResultSerializer.test.ts +23 -4
  317. package/Tests/Server/Utils/AI/Toolbox/LogTools.test.ts +25 -6
  318. package/Tests/Server/Utils/AI/Toolbox/Serializer.test.ts +14 -3
  319. package/Tests/Server/Utils/EditionEnforcement.test.ts +107 -147
  320. package/Tests/Server/Utils/{CommunityEditionSsoReport.test.ts → RelaxedScimTeamLocksReport.test.ts} +212 -208
  321. package/Tests/Server/Utils/SameOriginRequest.test.ts +372 -0
  322. package/Tests/Server/Utils/Telemetry/EntityRegistry.test.ts +20 -0
  323. package/Tests/Server/Utils/Telemetry/MessageQueueDiscovery.test.ts +2590 -0
  324. package/Tests/Server/Utils/Telemetry/MessageQueueSpanQueryTwin.ts +246 -0
  325. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +23 -0
  326. package/Tests/Types/AI/AIAgentRunLimits.test.ts +255 -0
  327. package/Tests/Types/Code/JsonSyntax.test.ts +1300 -0
  328. package/Tests/Types/CustomField/CustomFieldCreateSettings.test.ts +1113 -0
  329. package/Tests/Types/CustomField/CustomFieldMappingCatalog.test.ts +91 -0
  330. package/Tests/Types/CustomField/CustomFieldValueValidator.test.ts +30 -0
  331. package/Tests/Types/Incident/IncidentFormIpAllowlist.test.ts +227 -0
  332. package/Tests/Types/Incident/IncidentFormPublic.test.ts +1916 -0
  333. package/Tests/Types/MessageQueue/MessageQueueEntityKey.test.ts +916 -0
  334. package/Tests/Types/MessageQueue/MessageQueueIdentity.test.ts +567 -0
  335. package/Tests/Types/MessageQueue/MessageQueueManualIdentity.test.ts +686 -0
  336. package/Tests/Types/MessageQueue/MessageQueueMetricCatalog.test.ts +1726 -0
  337. package/Tests/Types/MessageQueue/MessageQueueMetricResolution.test.ts +1758 -0
  338. package/Tests/Types/MessageQueue/MessagingInstrumentationFixtures.test.ts +4686 -0
  339. package/Tests/Types/MessageQueue/MessagingSystem.test.ts +840 -0
  340. package/Tests/Types/MessageQueue/MessagingTelemetryFixtures.ts +3287 -0
  341. package/Tests/Types/MessageQueue/MessagingTelemetryResolver.test.ts +2966 -0
  342. package/Tests/Types/Monitor/MessageQueueAlertTemplates.test.ts +5401 -0
  343. package/Tests/Types/Monitor/MetricEngineFold.ts +154 -0
  344. package/Tests/Types/ResourceAiAgent/ResourceAiAccess.test.ts +5 -3
  345. package/Tests/Types/Team/ComplianceRule.test.ts +588 -37
  346. package/Tests/Types/Team/TeamComplianceStatus.test.ts +64 -11
  347. package/Tests/Types/XML.test.ts +51 -0
  348. package/Tests/UI/Components/CodeEditor/CodeEditor.test.tsx +2362 -0
  349. package/Tests/UI/Components/CodeEditor/CodeEditorCommands.test.ts +810 -0
  350. package/Tests/UI/Components/CodeEditor/CodeEditorHighlight.test.ts +338 -0
  351. package/Tests/UI/Components/CodeEditor/CodeEditorLanguages.test.ts +222 -0
  352. package/Tests/UI/Components/CodeEditor/CodeEditorStyles.test.ts +807 -0
  353. package/Tests/UI/Components/CodeEditor/NoMonaco.test.ts +245 -0
  354. package/Tests/UI/Components/CodeEditor/YamlEditor.test.tsx +729 -0
  355. package/Tests/UI/Components/CopyTextButton.test.tsx +76 -0
  356. package/Tests/UI/Components/EditionLabelLapseCopy.ts +51 -14
  357. package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +131 -23
  358. package/Tests/UI/Components/ErrorMessageAction.test.tsx +142 -0
  359. package/Tests/UI/Components/FacetSidebarInScope.test.tsx +459 -0
  360. package/Tests/UI/Components/FacetValuesInScope.test.ts +270 -0
  361. package/Tests/UI/Components/Forms/FormFieldCodeEditors.test.tsx +294 -0
  362. package/Tests/UI/Components/Forms/FormFieldMarkdown.test.tsx +126 -0
  363. package/Tests/UI/Components/Forms/FormFieldYaml.test.tsx +22 -75
  364. package/Tests/UI/Components/Forms/ModelFormOnBeforeCreateMiscData.test.tsx +254 -0
  365. package/Tests/UI/Components/Icon.test.tsx +32 -0
  366. package/Tests/UI/Components/Input.test.tsx +31 -0
  367. package/Tests/UI/Components/LockedFilterChipScope.test.tsx +501 -0
  368. package/Tests/UI/Components/MarkdownConverters.test.ts +253 -1
  369. package/Tests/UI/Components/MarkdownEditor.test.tsx +2785 -2
  370. package/Tests/UI/Components/MarkdownEditorHistory.test.ts +243 -0
  371. package/Tests/UI/Components/MarkdownListEditing.test.ts +1118 -0
  372. package/Tests/UI/Components/MarkdownPaste.test.ts +1180 -0
  373. package/Tests/UI/Components/MarkdownViewerCopy.test.tsx +466 -0
  374. package/Tests/UI/Components/MarkdownVisualEditing.test.ts +1089 -0
  375. package/Tests/UI/Components/ModelTable/BaseModelTableEmptyStateAction.test.tsx +623 -0
  376. package/Tests/UI/Components/ResetObjectID.test.tsx +282 -0
  377. package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
  378. package/Tests/UI/Components/Table/EmptyTableMessage.test.ts +439 -0
  379. package/Tests/UI/Components/Table/TableEmptyState.test.tsx +352 -0
  380. package/Tests/UI/Components/TableRowMobileTitles.test.tsx +78 -0
  381. package/Tests/UI/Components/TextArea.test.tsx +19 -0
  382. package/Tests/UI/Components/Workflow/ModelColumnEditor.render.test.tsx +12 -29
  383. package/Tests/UI/Components/fixtures/MarkdownPasteFixtures.ts +460 -0
  384. package/Tests/UI/EsbuildConfig.test.ts +115 -39
  385. package/Tests/UI/EsbuildEnterpriseAlias.test.ts +4 -4
  386. package/Tests/UI/Monitor/MonitorLogSummaryRender.test.tsx +377 -0
  387. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +81 -1
  388. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +17 -0
  389. package/Tests/Utils/LabelRuleImportExport.test.ts +5 -0
  390. package/Tests/Utils/Markdown/DashboardMarkdownRenderer.ts +81 -0
  391. package/Tests/Utils/Markdown/UntrustedMarkdown.test.ts +1339 -0
  392. package/Tests/Utils/Monitor/MonitorLogSummaryUtil.test.ts +862 -0
  393. package/Types/AI/AIAgentRunLimits.ts +146 -0
  394. package/Types/Code/CodeType.ts +2 -0
  395. package/Types/Code/JsonSyntax.ts +923 -0
  396. package/Types/CustomField/CustomFieldCreateSettings.ts +549 -0
  397. package/Types/CustomField/CustomFieldMappingCatalog.ts +77 -0
  398. package/Types/CustomField/CustomFieldValueValidator.ts +10 -7
  399. package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +1 -1
  400. package/Types/Icon/IconProp.ts +2 -0
  401. package/Types/Incident/IncidentFormIpAllowlist.ts +206 -0
  402. package/Types/Incident/IncidentFormPublic.ts +1145 -0
  403. package/Types/Kubernetes/KubernetesClusterAiAccess.ts +10 -3
  404. package/Types/MessageQueue/MessageQueueIdentity.ts +256 -0
  405. package/Types/MessageQueue/MessageQueueManualIdentity.ts +325 -0
  406. package/Types/MessageQueue/MessageQueueMetricCatalog.ts +1357 -0
  407. package/Types/MessageQueue/MessagingSystem.ts +518 -0
  408. package/Types/MessageQueue/MessagingTelemetryResolver.ts +2311 -0
  409. package/Types/Monitor/MessageQueueAlertTemplates.ts +2128 -0
  410. package/Types/Permission.ts +310 -0
  411. package/Types/ResourceAiAgent/ResourceAiAccess.ts +10 -4
  412. package/Types/Rules/RuleCriteriaFieldRegistry.ts +12 -0
  413. package/Types/Rules/RuleRun.ts +4 -0
  414. package/Types/Team/ComplianceNotificationChannel.ts +4 -3
  415. package/Types/Team/ComplianceRule.ts +71 -18
  416. package/Types/Team/ComplianceRuleType.ts +2 -2
  417. package/Types/Team/TeamComplianceStatus.ts +6 -2
  418. package/Types/Telemetry/EntityType.ts +16 -0
  419. package/Types/Telemetry/LockedFilterDetail.ts +22 -0
  420. package/Types/XML.ts +26 -0
  421. package/UI/Components/CodeEditor/CodeEditor.css +379 -0
  422. package/UI/Components/CodeEditor/CodeEditor.tsx +1128 -294
  423. package/UI/Components/CodeEditor/CodeEditorCommands.ts +774 -0
  424. package/UI/Components/CodeEditor/CodeEditorHighlight.ts +106 -0
  425. package/UI/Components/CodeEditor/CodeEditorLanguages.ts +164 -0
  426. package/UI/Components/CodeEditor/YamlEditor.tsx +28 -326
  427. package/UI/Components/CopyTextButton/CopyTextButton.tsx +5 -1
  428. package/UI/Components/CustomFields/CustomFieldModelFormFields.tsx +6 -25
  429. package/UI/Components/EditionLabel/EditionLabel.tsx +14 -13
  430. package/UI/Components/ErrorMessage/ErrorMessage.tsx +11 -1
  431. package/UI/Components/Forms/Fields/FormField.tsx +8 -0
  432. package/UI/Components/Forms/Types/Field.ts +8 -0
  433. package/UI/Components/Icon/Icon.tsx +18 -0
  434. package/UI/Components/Input/Input.tsx +11 -4
  435. package/UI/Components/LogsViewer/LogsViewer.tsx +4 -0
  436. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +21 -3
  437. package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
  438. package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +877 -79
  439. package/UI/Components/Markdown.tsx/MarkdownEditorHistory.ts +284 -0
  440. package/UI/Components/Markdown.tsx/MarkdownListEditing.ts +1362 -0
  441. package/UI/Components/Markdown.tsx/MarkdownPaste.ts +1326 -0
  442. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +106 -10
  443. package/UI/Components/Markdown.tsx/MarkdownVisualEditing.ts +1032 -0
  444. package/UI/Components/ModelTable/BaseModelTable.tsx +85 -7
  445. package/UI/Components/ResetObjectID/ResetObjectID.tsx +35 -9
  446. package/UI/Components/Table/EmptyTableMessage.ts +81 -0
  447. package/UI/Components/Table/Table.tsx +50 -21
  448. package/UI/Components/Table/TableRow.tsx +9 -1
  449. package/UI/Components/TelemetryViewer/FacetVisibility.ts +97 -2
  450. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +13 -0
  451. package/UI/Components/TelemetryViewer/components/LockedFilterChip.tsx +214 -7
  452. package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +21 -3
  453. package/UI/Components/TextArea/TextArea.tsx +9 -2
  454. package/UI/Components/Workflow/ModelFieldPicker.tsx +10 -12
  455. package/UI/Styles/Theme.css +8 -0
  456. package/UI/esbuild-config.js +2 -32
  457. package/Utils/LabelRuleImportExport.ts +2 -0
  458. package/Utils/Markdown/UntrustedMarkdown.ts +1513 -0
  459. package/Utils/Monitor/IncomingEmailMonitorRequestUtil.ts +41 -0
  460. package/Utils/Monitor/MonitorLogSummaryUtil.ts +212 -0
  461. package/Utils/Telemetry/EntityKey.ts +48 -0
  462. package/build/dist/Models/DatabaseModels/AIConversation.js +125 -1
  463. package/build/dist/Models/DatabaseModels/AIConversation.js.map +1 -1
  464. package/build/dist/Models/DatabaseModels/CephCluster.js +12 -0
  465. package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
  466. package/build/dist/Models/DatabaseModels/CloudResource.js +12 -0
  467. package/build/dist/Models/DatabaseModels/CloudResource.js.map +1 -1
  468. package/build/dist/Models/DatabaseModels/DatabaseServer.js +12 -0
  469. package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -1
  470. package/build/dist/Models/DatabaseModels/DockerHost.js +12 -0
  471. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  472. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js +12 -0
  473. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js.map +1 -1
  474. package/build/dist/Models/DatabaseModels/GlobalOidc.js +0 -4
  475. package/build/dist/Models/DatabaseModels/GlobalOidc.js.map +1 -1
  476. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +0 -4
  477. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
  478. package/build/dist/Models/DatabaseModels/GlobalSso.js +0 -4
  479. package/build/dist/Models/DatabaseModels/GlobalSso.js.map +1 -1
  480. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +0 -4
  481. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -1
  482. package/build/dist/Models/DatabaseModels/Host.js +12 -0
  483. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  484. package/build/dist/Models/DatabaseModels/IncidentForm.js +605 -0
  485. package/build/dist/Models/DatabaseModels/IncidentForm.js.map +1 -0
  486. package/build/dist/Models/DatabaseModels/IncidentFormSubmission.js +299 -0
  487. package/build/dist/Models/DatabaseModels/IncidentFormSubmission.js.map +1 -0
  488. package/build/dist/Models/DatabaseModels/IncidentTemplate.js +60 -0
  489. package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
  490. package/build/dist/Models/DatabaseModels/Index.js +14 -0
  491. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  492. package/build/dist/Models/DatabaseModels/IoTFleet.js +12 -0
  493. package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
  494. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +12 -0
  495. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  496. package/build/dist/Models/DatabaseModels/MessageQueue.js +1106 -0
  497. package/build/dist/Models/DatabaseModels/MessageQueue.js.map +1 -0
  498. package/build/dist/Models/DatabaseModels/MessageQueueLabelRule.js +562 -0
  499. package/build/dist/Models/DatabaseModels/MessageQueueLabelRule.js.map +1 -0
  500. package/build/dist/Models/DatabaseModels/MessageQueueOwnerRule.js +643 -0
  501. package/build/dist/Models/DatabaseModels/MessageQueueOwnerRule.js.map +1 -0
  502. package/build/dist/Models/DatabaseModels/MessageQueueOwnerTeam.js +506 -0
  503. package/build/dist/Models/DatabaseModels/MessageQueueOwnerTeam.js.map +1 -0
  504. package/build/dist/Models/DatabaseModels/MessageQueueOwnerUser.js +505 -0
  505. package/build/dist/Models/DatabaseModels/MessageQueueOwnerUser.js.map +1 -0
  506. package/build/dist/Models/DatabaseModels/PodmanHost.js +12 -0
  507. package/build/dist/Models/DatabaseModels/PodmanHost.js.map +1 -1
  508. package/build/dist/Models/DatabaseModels/Project.js +61 -0
  509. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  510. package/build/dist/Models/DatabaseModels/ProjectOidc.js +0 -4
  511. package/build/dist/Models/DatabaseModels/ProjectOidc.js.map +1 -1
  512. package/build/dist/Models/DatabaseModels/ProjectSso.js +0 -4
  513. package/build/dist/Models/DatabaseModels/ProjectSso.js.map +1 -1
  514. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +12 -0
  515. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
  516. package/build/dist/Models/DatabaseModels/RumApplication.js +12 -0
  517. package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
  518. package/build/dist/Models/DatabaseModels/ServerlessFunction.js +12 -0
  519. package/build/dist/Models/DatabaseModels/ServerlessFunction.js.map +1 -1
  520. package/build/dist/Models/DatabaseModels/Service.js +12 -0
  521. package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
  522. package/build/dist/Models/DatabaseModels/StatusPageOidc.js +0 -4
  523. package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
  524. package/build/dist/Models/DatabaseModels/StatusPageSso.js +0 -4
  525. package/build/dist/Models/DatabaseModels/StatusPageSso.js.map +1 -1
  526. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +49 -3
  527. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
  528. package/build/dist/Models/DatabaseModels/VMwareVCenter.js +12 -0
  529. package/build/dist/Models/DatabaseModels/VMwareVCenter.js.map +1 -1
  530. package/build/dist/Server/API/AIChatAPI.js +9 -103
  531. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  532. package/build/dist/Server/API/AIInvestigationAPI.js +3 -3
  533. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  534. package/build/dist/Server/API/AIInvestigationConversationAPI.js +162 -0
  535. package/build/dist/Server/API/AIInvestigationConversationAPI.js.map +1 -0
  536. package/build/dist/Server/API/IncidentFormAPI.js +245 -0
  537. package/build/dist/Server/API/IncidentFormAPI.js.map +1 -0
  538. package/build/dist/Server/API/ProjectOIDC.js +16 -24
  539. package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
  540. package/build/dist/Server/API/ProjectSSO.js +16 -24
  541. package/build/dist/Server/API/ProjectSSO.js.map +1 -1
  542. package/build/dist/Server/API/StatusPageAPI.js +42 -75
  543. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  544. package/build/dist/Server/Enterprise/EnterpriseEdition.js +79 -25
  545. package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -1
  546. package/build/dist/Server/Enterprise/EnterpriseFeature.js +24 -3
  547. package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -1
  548. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +1 -1
  549. package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -1
  550. package/build/dist/Server/EnvironmentConfig.js +1 -1
  551. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.js +63 -0
  552. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.js.map +1 -0
  553. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.js +41 -0
  554. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.js.map +1 -0
  555. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.js +40 -0
  556. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.js.map +1 -0
  557. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.js +183 -0
  558. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.js.map +1 -0
  559. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  560. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  561. package/build/dist/Server/Middleware/IncidentFormRateLimit.js +559 -0
  562. package/build/dist/Server/Middleware/IncidentFormRateLimit.js.map +1 -0
  563. package/build/dist/Server/Middleware/UserAuthorization.js +11 -30
  564. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  565. package/build/dist/Server/Services/AIService.js +10 -13
  566. package/build/dist/Server/Services/AIService.js.map +1 -1
  567. package/build/dist/Server/Services/IncidentCustomFieldService.js +83 -0
  568. package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
  569. package/build/dist/Server/Services/IncidentEpisodeMemberService.js +8 -4
  570. package/build/dist/Server/Services/IncidentEpisodeMemberService.js.map +1 -1
  571. package/build/dist/Server/Services/IncidentEpisodeService.js +10 -1
  572. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  573. package/build/dist/Server/Services/IncidentFormService.js +1202 -0
  574. package/build/dist/Server/Services/IncidentFormService.js.map +1 -0
  575. package/build/dist/Server/Services/IncidentFormSubmissionService.js +91 -0
  576. package/build/dist/Server/Services/IncidentFormSubmissionService.js.map +1 -0
  577. package/build/dist/Server/Services/IncidentGroupingEngineService.js +20 -8
  578. package/build/dist/Server/Services/IncidentGroupingEngineService.js.map +1 -1
  579. package/build/dist/Server/Services/IncidentService.js +106 -4
  580. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  581. package/build/dist/Server/Services/IncidentTemplateService.js +18 -0
  582. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  583. package/build/dist/Server/Services/Index.js +14 -0
  584. package/build/dist/Server/Services/Index.js.map +1 -1
  585. package/build/dist/Server/Services/MessageQueueLabelRuleEngineService.js +262 -0
  586. package/build/dist/Server/Services/MessageQueueLabelRuleEngineService.js.map +1 -0
  587. package/build/dist/Server/Services/MessageQueueLabelRuleService.js +13 -0
  588. package/build/dist/Server/Services/MessageQueueLabelRuleService.js.map +1 -0
  589. package/build/dist/Server/Services/MessageQueueOwnerRuleEngineService.js +334 -0
  590. package/build/dist/Server/Services/MessageQueueOwnerRuleEngineService.js.map +1 -0
  591. package/build/dist/Server/Services/MessageQueueOwnerRuleService.js +13 -0
  592. package/build/dist/Server/Services/MessageQueueOwnerRuleService.js.map +1 -0
  593. package/build/dist/Server/Services/MessageQueueOwnerTeamService.js +62 -0
  594. package/build/dist/Server/Services/MessageQueueOwnerTeamService.js.map +1 -0
  595. package/build/dist/Server/Services/MessageQueueOwnerUserService.js +62 -0
  596. package/build/dist/Server/Services/MessageQueueOwnerUserService.js.map +1 -0
  597. package/build/dist/Server/Services/MessageQueueService.js +1140 -0
  598. package/build/dist/Server/Services/MessageQueueService.js.map +1 -0
  599. package/build/dist/Server/Services/OpenTelemetryIngestService.js +25 -4
  600. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  601. package/build/dist/Server/Services/ProjectService.js +0 -54
  602. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  603. package/build/dist/Server/Services/StatusPagePrivateUserService.js +1 -4
  604. package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
  605. package/build/dist/Server/Services/StatusPageService.js +0 -46
  606. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  607. package/build/dist/Server/Services/TeamComplianceSettingService.js +120 -31
  608. package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
  609. package/build/dist/Server/Services/UserNotificationRuleService.js +8 -1
  610. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  611. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +12 -3
  612. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -1
  613. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +6 -3
  614. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
  615. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +109 -37
  616. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
  617. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +16 -0
  618. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
  619. package/build/dist/Server/Types/Database/Permissions/TablePermission.js +3 -3
  620. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +9 -6
  621. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
  622. package/build/dist/Server/Utils/AI/Chat/AgentContextCompactor.js +128 -0
  623. package/build/dist/Server/Utils/AI/Chat/AgentContextCompactor.js.map +1 -0
  624. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +192 -50
  625. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
  626. package/build/dist/Server/Utils/AI/Chat/ChatRunCancellation.js +133 -0
  627. package/build/dist/Server/Utils/AI/Chat/ChatRunCancellation.js.map +1 -0
  628. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +53 -13
  629. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
  630. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +1 -1
  631. package/build/dist/Server/Utils/AI/Chat/ToolOutputPager.js +206 -0
  632. package/build/dist/Server/Utils/AI/Chat/ToolOutputPager.js.map +1 -0
  633. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +53 -26
  634. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
  635. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +4 -2
  636. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
  637. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +1 -29
  638. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -1
  639. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +24 -24
  640. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
  641. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +7 -2
  642. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  643. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +29 -14
  644. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  645. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js +38 -14
  646. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js.map +1 -1
  647. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js +2 -0
  648. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js.map +1 -1
  649. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +40 -10
  650. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  651. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +19 -7
  652. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  653. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +19 -7
  654. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  655. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js +15 -18
  656. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -1
  657. package/build/dist/Server/Utils/AI/SRE/InvestigationThread.js +412 -0
  658. package/build/dist/Server/Utils/AI/SRE/InvestigationThread.js.map +1 -0
  659. package/build/dist/Server/Utils/AI/SRE/InvestigationThreadService.js +495 -0
  660. package/build/dist/Server/Utils/AI/SRE/InvestigationThreadService.js.map +1 -0
  661. package/build/dist/Server/Utils/AI/Toolbox/Serializer.js +10 -3
  662. package/build/dist/Server/Utils/AI/Toolbox/Serializer.js.map +1 -1
  663. package/build/dist/Server/Utils/EditionEnforcement.js +20 -158
  664. package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -1
  665. package/build/dist/Server/Utils/LLM/LLMService.js +193 -54
  666. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  667. package/build/dist/Server/Utils/RelaxedScimTeamLocksReport.js +160 -0
  668. package/build/dist/Server/Utils/RelaxedScimTeamLocksReport.js.map +1 -0
  669. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +25 -0
  670. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
  671. package/build/dist/Server/Utils/SameOriginRequest.js +151 -0
  672. package/build/dist/Server/Utils/SameOriginRequest.js.map +1 -0
  673. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js +2 -0
  674. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js.map +1 -1
  675. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +4 -1
  676. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  677. package/build/dist/Server/Utils/Telemetry/MessageQueueDiscovery.js +1015 -0
  678. package/build/dist/Server/Utils/Telemetry/MessageQueueDiscovery.js.map +1 -0
  679. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +4 -1
  680. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -1
  681. package/build/dist/Server/Utils/VendorAssets.js +3 -3
  682. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +3 -1
  683. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  684. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +3 -1
  685. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
  686. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +10 -1
  687. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  688. package/build/dist/Types/AI/AIAgentRunLimits.js +91 -0
  689. package/build/dist/Types/AI/AIAgentRunLimits.js.map +1 -0
  690. package/build/dist/Types/Code/CodeType.js +2 -0
  691. package/build/dist/Types/Code/CodeType.js.map +1 -1
  692. package/build/dist/Types/Code/JsonSyntax.js +604 -0
  693. package/build/dist/Types/Code/JsonSyntax.js.map +1 -0
  694. package/build/dist/Types/CustomField/CustomFieldCreateSettings.js +310 -0
  695. package/build/dist/Types/CustomField/CustomFieldCreateSettings.js.map +1 -0
  696. package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js +38 -0
  697. package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js.map +1 -1
  698. package/build/dist/Types/CustomField/CustomFieldValueValidator.js +10 -7
  699. package/build/dist/Types/CustomField/CustomFieldValueValidator.js.map +1 -1
  700. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +1 -1
  701. package/build/dist/Types/Icon/IconProp.js +2 -0
  702. package/build/dist/Types/Icon/IconProp.js.map +1 -1
  703. package/build/dist/Types/Incident/IncidentFormIpAllowlist.js +119 -0
  704. package/build/dist/Types/Incident/IncidentFormIpAllowlist.js.map +1 -0
  705. package/build/dist/Types/Incident/IncidentFormPublic.js +678 -0
  706. package/build/dist/Types/Incident/IncidentFormPublic.js.map +1 -0
  707. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +10 -3
  708. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
  709. package/build/dist/Types/MessageQueue/MessageQueueIdentity.js +184 -0
  710. package/build/dist/Types/MessageQueue/MessageQueueIdentity.js.map +1 -0
  711. package/build/dist/Types/MessageQueue/MessageQueueManualIdentity.js +190 -0
  712. package/build/dist/Types/MessageQueue/MessageQueueManualIdentity.js.map +1 -0
  713. package/build/dist/Types/MessageQueue/MessageQueueMetricCatalog.js +1057 -0
  714. package/build/dist/Types/MessageQueue/MessageQueueMetricCatalog.js.map +1 -0
  715. package/build/dist/Types/MessageQueue/MessagingSystem.js +394 -0
  716. package/build/dist/Types/MessageQueue/MessagingSystem.js.map +1 -0
  717. package/build/dist/Types/MessageQueue/MessagingTelemetryResolver.js +1768 -0
  718. package/build/dist/Types/MessageQueue/MessagingTelemetryResolver.js.map +1 -0
  719. package/build/dist/Types/Monitor/MessageQueueAlertTemplates.js +1207 -0
  720. package/build/dist/Types/Monitor/MessageQueueAlertTemplates.js.map +1 -0
  721. package/build/dist/Types/Permission.js +274 -0
  722. package/build/dist/Types/Permission.js.map +1 -1
  723. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js +10 -4
  724. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js.map +1 -1
  725. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +12 -0
  726. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
  727. package/build/dist/Types/Rules/RuleRun.js +4 -0
  728. package/build/dist/Types/Rules/RuleRun.js.map +1 -1
  729. package/build/dist/Types/Team/ComplianceNotificationChannel.js +4 -3
  730. package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -1
  731. package/build/dist/Types/Team/ComplianceRule.js +48 -14
  732. package/build/dist/Types/Team/ComplianceRule.js.map +1 -1
  733. package/build/dist/Types/Team/ComplianceRuleType.js +2 -2
  734. package/build/dist/Types/Telemetry/EntityType.js +16 -0
  735. package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
  736. package/build/dist/Types/XML.js +23 -0
  737. package/build/dist/Types/XML.js.map +1 -1
  738. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +641 -266
  739. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  740. package/build/dist/UI/Components/CodeEditor/CodeEditorCommands.js +464 -0
  741. package/build/dist/UI/Components/CodeEditor/CodeEditorCommands.js.map +1 -0
  742. package/build/dist/UI/Components/CodeEditor/CodeEditorHighlight.js +69 -0
  743. package/build/dist/UI/Components/CodeEditor/CodeEditorHighlight.js.map +1 -0
  744. package/build/dist/UI/Components/CodeEditor/CodeEditorLanguages.js +108 -0
  745. package/build/dist/UI/Components/CodeEditor/CodeEditorLanguages.js.map +1 -0
  746. package/build/dist/UI/Components/CodeEditor/YamlEditor.js +14 -186
  747. package/build/dist/UI/Components/CodeEditor/YamlEditor.js.map +1 -1
  748. package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js +5 -1
  749. package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js.map +1 -1
  750. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js +5 -14
  751. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js.map +1 -1
  752. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +14 -13
  753. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  754. package/build/dist/UI/Components/ErrorMessage/ErrorMessage.js +1 -1
  755. package/build/dist/UI/Components/ErrorMessage/ErrorMessage.js.map +1 -1
  756. package/build/dist/UI/Components/Forms/Fields/FormField.js +8 -3
  757. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  758. package/build/dist/UI/Components/Icon/Icon.js +8 -0
  759. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  760. package/build/dist/UI/Components/Input/Input.js +11 -3
  761. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  762. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +2 -1
  763. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  764. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +14 -4
  765. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  766. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
  767. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
  768. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +710 -65
  769. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
  770. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditorHistory.js +238 -0
  771. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditorHistory.js.map +1 -0
  772. package/build/dist/UI/Components/Markdown.tsx/MarkdownListEditing.js +1028 -0
  773. package/build/dist/UI/Components/Markdown.tsx/MarkdownListEditing.js.map +1 -0
  774. package/build/dist/UI/Components/Markdown.tsx/MarkdownPaste.js +1088 -0
  775. package/build/dist/UI/Components/Markdown.tsx/MarkdownPaste.js.map +1 -0
  776. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +70 -10
  777. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  778. package/build/dist/UI/Components/Markdown.tsx/MarkdownVisualEditing.js +887 -0
  779. package/build/dist/UI/Components/Markdown.tsx/MarkdownVisualEditing.js.map +1 -0
  780. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +57 -6
  781. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  782. package/build/dist/UI/Components/ResetObjectID/ResetObjectID.js +7 -5
  783. package/build/dist/UI/Components/ResetObjectID/ResetObjectID.js.map +1 -1
  784. package/build/dist/UI/Components/Table/EmptyTableMessage.js +51 -0
  785. package/build/dist/UI/Components/Table/EmptyTableMessage.js.map +1 -0
  786. package/build/dist/UI/Components/Table/Table.js +30 -11
  787. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  788. package/build/dist/UI/Components/Table/TableRow.js +9 -2
  789. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  790. package/build/dist/UI/Components/TelemetryViewer/FacetVisibility.js +63 -1
  791. package/build/dist/UI/Components/TelemetryViewer/FacetVisibility.js.map +1 -1
  792. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +4 -2
  793. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  794. package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js +91 -10
  795. package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js.map +1 -1
  796. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +14 -4
  797. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
  798. package/build/dist/UI/Components/TextArea/TextArea.js +9 -3
  799. package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
  800. package/build/dist/UI/Components/Workflow/ModelFieldPicker.js +1 -2
  801. package/build/dist/UI/Components/Workflow/ModelFieldPicker.js.map +1 -1
  802. package/build/dist/Utils/LabelRuleImportExport.js +2 -0
  803. package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
  804. package/build/dist/Utils/Markdown/UntrustedMarkdown.js +988 -0
  805. package/build/dist/Utils/Markdown/UntrustedMarkdown.js.map +1 -0
  806. package/build/dist/Utils/Monitor/IncomingEmailMonitorRequestUtil.js +34 -0
  807. package/build/dist/Utils/Monitor/IncomingEmailMonitorRequestUtil.js.map +1 -0
  808. package/build/dist/Utils/Monitor/MonitorLogSummaryUtil.js +148 -0
  809. package/build/dist/Utils/Monitor/MonitorLogSummaryUtil.js.map +1 -0
  810. package/build/dist/Utils/Telemetry/EntityKey.js +36 -0
  811. package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
  812. package/package.json +1 -3
  813. package/Server/Utils/CommunityEditionSsoReport.ts +0 -361
  814. package/Tests/App/Accounts/LoginSsoEdition.test.tsx +0 -616
  815. package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +0 -217
  816. package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +0 -271
  817. package/Tests/App/Dashboard/CloudDocumentationMarkdown.test.ts +0 -215
  818. package/Tests/App/Dashboard/TelemetryResourceRetentionSettings.test.tsx +0 -784
  819. package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +0 -518
  820. package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +0 -419
  821. package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +0 -769
  822. package/Tests/UI/Components/CodeEditor.test.tsx +0 -638
  823. package/Tests/UI/Components/MonacoLoader.test.ts +0 -67
  824. package/Tests/UI/Components/MonacoRuntime.test.ts +0 -121
  825. package/Tests/UI/Components/Telemetry/TelemetryRetentionConfigForm.test.tsx +0 -329
  826. package/Tests/UI/Components/YamlEditor.test.tsx +0 -847
  827. package/UI/Components/CodeEditor/MonacoLoader.ts +0 -36
  828. package/UI/Components/Telemetry/TelemetryRetentionConfigForm.tsx +0 -319
  829. package/UI/Components/Telemetry/TelemetryRetentionConfigSummary.tsx +0 -223
  830. package/build/dist/Server/Utils/CommunityEditionSsoReport.js +0 -223
  831. package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +0 -1
  832. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js +0 -34
  833. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js.map +0 -1
  834. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js +0 -171
  835. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js.map +0 -1
  836. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigSummary.js +0 -119
  837. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigSummary.js.map +0 -1
@@ -0,0 +1,2362 @@
1
+ import "@testing-library/jest-dom";
2
+ import React, { ReactElement, useLayoutEffect, useState } from "react";
3
+ import {
4
+ act,
5
+ cleanup,
6
+ fireEvent,
7
+ render,
8
+ screen,
9
+ } from "@testing-library/react";
10
+ import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
11
+ import CodeEditor, {
12
+ CODE_EDITOR_DEFAULT_MAX_HEIGHT,
13
+ CODE_EDITOR_DEFAULT_MIN_LINES,
14
+ CODE_EDITOR_KEYS_HINT,
15
+ CODE_EDITOR_LINE_HEIGHT_PX,
16
+ CODE_EDITOR_PADDING_Y_PX,
17
+ CODE_EDITOR_VALIDATION_DEBOUNCE_MS,
18
+ ComponentProps,
19
+ applyEditToTextarea,
20
+ toEditorText,
21
+ } from "../../../../UI/Components/CodeEditor/CodeEditor";
22
+ import { TextEdit } from "../../../../UI/Components/CodeEditor/CodeEditorCommands";
23
+ import CodeType from "../../../../Types/Code/CodeType";
24
+
25
+ /*
26
+ * The real editor, in jsdom: there is no Monaco to stand in for any more. The
27
+ * one thing jsdom lacks is document.execCommand, so every edit the editor
28
+ * makes itself (indent, pairing, Format...) goes through its fallback - the
29
+ * value written through the prototype setter plus an input event - which is
30
+ * exactly the path that has to keep React's onChange in step.
31
+ */
32
+
33
+ type GetElementFunction = () => HTMLElement;
34
+ type GetInputFunction = () => HTMLTextAreaElement;
35
+ type GetTextFunction = () => string;
36
+
37
+ const getInput: GetInputFunction = (): HTMLTextAreaElement => {
38
+ return screen.getByTestId("code-editor-input") as HTMLTextAreaElement;
39
+ };
40
+
41
+ const getLayer: GetElementFunction = (): HTMLElement => {
42
+ return screen.getByTestId("code-editor-highlight");
43
+ };
44
+
45
+ const layerText: GetTextFunction = (): string => {
46
+ return getLayer().textContent || "";
47
+ };
48
+
49
+ const gutterText: GetTextFunction = (): string => {
50
+ return screen.getByTestId("code-editor-gutter").textContent || "";
51
+ };
52
+
53
+ const statusText: GetTextFunction = (): string => {
54
+ return screen.getByTestId("code-editor-status").textContent || "";
55
+ };
56
+
57
+ const cursorText: GetTextFunction = (): string => {
58
+ return screen.getByTestId("code-editor-cursor").textContent || "";
59
+ };
60
+
61
+ type FocusFunction = () => void;
62
+
63
+ const focusInput: FocusFunction = (): void => {
64
+ act(() => {
65
+ getInput().focus();
66
+ });
67
+ };
68
+
69
+ type SetSelectionFunction = (start: number, end?: number) => void;
70
+
71
+ /*
72
+ * Moves the selection the way an arrow key would: the editor re-reads it on
73
+ * keyup. (React's onSelect is not driven by the native `select` event, so
74
+ * firing that would prove nothing.)
75
+ */
76
+ const setSelection: SetSelectionFunction = (
77
+ start: number,
78
+ end?: number,
79
+ ): void => {
80
+ act(() => {
81
+ getInput().setSelectionRange(start, end ?? start);
82
+ });
83
+ fireEvent.keyUp(getInput(), { key: "ArrowRight" });
84
+ };
85
+
86
+ type PressFunction = (key: string, init?: Record<string, unknown>) => boolean;
87
+
88
+ /** Fires keydown and returns false when the editor called preventDefault. */
89
+ const press: PressFunction = (
90
+ key: string,
91
+ init?: Record<string, unknown>,
92
+ ): boolean => {
93
+ return fireEvent.keyDown(getInput(), { key, ...(init || {}) });
94
+ };
95
+
96
+ type SelectionOfFunction = () => [number, number];
97
+
98
+ const selectionOf: SelectionOfFunction = (): [number, number] => {
99
+ return [getInput().selectionStart, getInput().selectionEnd];
100
+ };
101
+
102
+ type AdvanceFunction = (ms: number) => void;
103
+
104
+ const advance: AdvanceFunction = (ms: number): void => {
105
+ act(() => {
106
+ jest.advanceTimersByTime(ms);
107
+ });
108
+ };
109
+
110
+ interface HarnessProps extends Omit<ComponentProps, "value" | "onChange"> {
111
+ initial?: string | undefined;
112
+ onValue?: ((value: string) => void) | undefined;
113
+ }
114
+
115
+ // A parent that feeds every change back, the way a form field does.
116
+ const Harness: (props: HarnessProps) => ReactElement = (
117
+ props: HarnessProps,
118
+ ): ReactElement => {
119
+ const { initial, onValue, ...rest } = props;
120
+ const [value, setValue] = useState<string>(initial || "");
121
+
122
+ return (
123
+ <CodeEditor
124
+ {...rest}
125
+ value={value}
126
+ onChange={(next: string) => {
127
+ setValue(next);
128
+
129
+ if (onValue) {
130
+ onValue(next);
131
+ }
132
+ }}
133
+ />
134
+ );
135
+ };
136
+
137
+ afterEach(() => {
138
+ cleanup();
139
+ jest.useRealTimers();
140
+ });
141
+
142
+ describe("CodeEditor — the value it shows", () => {
143
+ test("renders the value prop when no initialValue is given", () => {
144
+ /*
145
+ * Regression: the Runbook step editors (Bash script, JavaScript script,
146
+ * HTTP headers and body) pass only `value`. An initialValue effect used to
147
+ * run after the value effect on mount and blank the editor, so saved
148
+ * scripts came back empty on reload.
149
+ */
150
+ const script: string = 'echo "Runbook step running"';
151
+
152
+ render(<CodeEditor type={CodeType.Bash} value={script} />);
153
+
154
+ expect(getInput().value).toBe(script);
155
+ expect(layerText()).toBe(script + "\n");
156
+ });
157
+
158
+ test("has the value on its very first commit, not after an effect repairs it", () => {
159
+ const script: string = "df -h | head -5";
160
+ const seen: Array<string> = [];
161
+
162
+ /*
163
+ * A layout effect in a later sibling runs after the editor's first DOM
164
+ * commit and before any passive effect - so what it reads is what the
165
+ * first render produced, before the prop-sync effect could fix anything.
166
+ */
167
+ const Probe: () => null = (): null => {
168
+ useLayoutEffect(() => {
169
+ seen.push(getInput().value, layerText());
170
+ }, []);
171
+
172
+ return null;
173
+ };
174
+
175
+ render(
176
+ <>
177
+ <CodeEditor type={CodeType.Text} value={script} />
178
+ <Probe />
179
+ </>,
180
+ );
181
+
182
+ expect(seen).toEqual([script, script + "\n"]);
183
+ });
184
+
185
+ test("given both props, the very first commit already shows value", () => {
186
+ /*
187
+ * The prop-sync effect would repair a wrong seed a commit later, which is
188
+ * why only a first-commit read can see it - and that commit is painted,
189
+ * so a wrong seed shows `initialValue` for a frame.
190
+ */
191
+ const seen: Array<string> = [];
192
+
193
+ const Probe: () => null = (): null => {
194
+ useLayoutEffect(() => {
195
+ seen.push(getInput().value, layerText());
196
+ }, []);
197
+
198
+ return null;
199
+ };
200
+
201
+ render(
202
+ <>
203
+ <CodeEditor
204
+ type={CodeType.JSON}
205
+ initialValue={'{ "stale": true }'}
206
+ value={'{ "current": true }'}
207
+ />
208
+ <Probe />
209
+ </>,
210
+ );
211
+
212
+ expect(seen).toEqual(['{ "current": true }', '{ "current": true }\n']);
213
+ });
214
+
215
+ test("renders the initialValue prop when no value is given", () => {
216
+ const json: string = '{ "Authorization": "Bearer token" }';
217
+
218
+ render(<CodeEditor type={CodeType.JSON} initialValue={json} />);
219
+
220
+ expect(getInput().value).toBe(json);
221
+ });
222
+
223
+ test("prefers value over initialValue when both are given", () => {
224
+ render(
225
+ <CodeEditor
226
+ type={CodeType.JSON}
227
+ initialValue={'{ "stale": true }'}
228
+ value={'{ "current": true }'}
229
+ />,
230
+ );
231
+
232
+ expect(getInput().value).toBe('{ "current": true }');
233
+ });
234
+
235
+ test("follows the value prop when it changes after mount", () => {
236
+ const { rerender } = render(
237
+ <CodeEditor type={CodeType.JavaScript} value="return 1;" />,
238
+ );
239
+
240
+ rerender(<CodeEditor type={CodeType.JavaScript} value="return 2;" />);
241
+
242
+ expect(getInput().value).toBe("return 2;");
243
+ expect(layerText()).toBe("return 2;\n");
244
+ });
245
+
246
+ test("picks up a value that only arrives after the first render", () => {
247
+ // Pages that load the model asynchronously render once with nothing.
248
+ const { rerender } = render(<CodeEditor type={CodeType.Text} />);
249
+
250
+ expect(getInput().value).toBe("");
251
+
252
+ rerender(<CodeEditor type={CodeType.Text} value="uptime" />);
253
+
254
+ expect(getInput().value).toBe("uptime");
255
+ });
256
+
257
+ test("follows the initialValue prop when it changes after mount", () => {
258
+ // Form fields recompute initialValue from currentValues on every keystroke.
259
+ const { rerender } = render(
260
+ <CodeEditor type={CodeType.JavaScript} initialValue="return 1;" />,
261
+ );
262
+
263
+ rerender(
264
+ <CodeEditor type={CodeType.JavaScript} initialValue="return 2;" />,
265
+ );
266
+
267
+ expect(getInput().value).toBe("return 2;");
268
+ });
269
+
270
+ test("reports edits to onChange", () => {
271
+ const onChange: jest.Mock = jest.fn();
272
+
273
+ render(
274
+ <CodeEditor type={CodeType.Text} value="before" onChange={onChange} />,
275
+ );
276
+
277
+ fireEvent.change(getInput(), { target: { value: "after" } });
278
+
279
+ expect(onChange).toHaveBeenCalledWith("after");
280
+ expect(layerText()).toBe("after\n");
281
+ });
282
+
283
+ test("typing is reflected even when the parent does not feed the value back", () => {
284
+ render(<CodeEditor type={CodeType.Text} />);
285
+
286
+ fireEvent.change(getInput(), { target: { value: "typed" } });
287
+
288
+ expect(getInput().value).toBe("typed");
289
+ });
290
+
291
+ test("stringifies a non-string value", () => {
292
+ render(
293
+ <CodeEditor
294
+ type={CodeType.JSON}
295
+ value={{ hello: "world" } as unknown as string}
296
+ />,
297
+ );
298
+
299
+ expect(getInput().value).toBe(JSON.stringify({ hello: "world" }, null, 4));
300
+ });
301
+
302
+ test("normalises CRLF, because a textarea always reads back with LF", () => {
303
+ render(<CodeEditor type={CodeType.Text} value={"a\r\nb\rc"} />);
304
+
305
+ expect(getInput().value).toBe("a\nb\nc");
306
+ // One character out of step per line would shift every glyph after it.
307
+ expect(layerText()).toBe("a\nb\nc\n");
308
+ });
309
+ });
310
+
311
+ describe("CodeEditor — toEditorText", () => {
312
+ test.each([
313
+ [undefined, ""],
314
+ [null, ""],
315
+ ["plain", "plain"],
316
+ ["a\r\nb", "a\nb"],
317
+ ["a\rb", "a\nb"],
318
+ ])("%p becomes %p", (input: unknown, expected: string) => {
319
+ expect(toEditorText(input as string | undefined)).toBe(expected);
320
+ });
321
+
322
+ test("an object is pretty-printed with four spaces", () => {
323
+ expect(toEditorText({ a: [1] } as unknown as string)).toBe(
324
+ '{\n "a": [\n 1\n ]\n}',
325
+ );
326
+ });
327
+ });
328
+
329
+ describe("CodeEditor — the highlighted layer under the textarea", () => {
330
+ test("holds exactly the document's characters, plus the newline that shows an empty last line", () => {
331
+ const text: string = '{\n "name": "api",\n "retries": 3\n}\n';
332
+
333
+ render(<CodeEditor type={CodeType.JSON} value={text} />);
334
+
335
+ expect(layerText()).toBe(text + "\n");
336
+ });
337
+
338
+ test("colours tokens with highlight.js spans", () => {
339
+ render(<CodeEditor type={CodeType.JSON} value={'{"retries": 3}'} />);
340
+
341
+ expect(getLayer().querySelector(".hljs-attr")?.textContent).toBe(
342
+ '"retries"',
343
+ );
344
+ expect(getLayer().querySelector(".hljs-number")?.textContent).toBe("3");
345
+ });
346
+
347
+ test("plain text gets no token spans", () => {
348
+ render(<CodeEditor type={CodeType.Text} value={'{"retries": 3}'} />);
349
+
350
+ expect(getLayer().querySelectorAll("span")).toHaveLength(0);
351
+ });
352
+
353
+ test.each([CodeType.Text, CodeType.HTML, CodeType.Markdown, CodeType.JSON])(
354
+ "markup in a %s document is shown, never injected",
355
+ (type: CodeType) => {
356
+ const hostile: string =
357
+ '<img src=x onerror="alert(1)"><script>x()</script>';
358
+
359
+ render(<CodeEditor type={type} value={hostile} />);
360
+
361
+ expect(getLayer().querySelector("img")).toBeNull();
362
+ expect(getLayer().querySelector("script")).toBeNull();
363
+ expect(layerText()).toBe(hostile + "\n");
364
+ },
365
+ );
366
+
367
+ test("the layer is hidden from assistive technology", () => {
368
+ render(<CodeEditor type={CodeType.JSON} value="{}" />);
369
+
370
+ expect(getLayer()).toHaveAttribute("aria-hidden", "true");
371
+ expect(screen.getByTestId("code-editor-gutter")).toHaveAttribute(
372
+ "aria-hidden",
373
+ "true",
374
+ );
375
+ });
376
+
377
+ test("a type change after mount re-highlights with the new grammar", () => {
378
+ const { rerender } = render(
379
+ <CodeEditor type={CodeType.Text} value={'{"a": 1}'} />,
380
+ );
381
+
382
+ expect(getLayer().querySelectorAll("span")).toHaveLength(0);
383
+ expect(screen.getByTestId("code-editor-language")).toHaveTextContent(
384
+ "Text",
385
+ );
386
+
387
+ rerender(<CodeEditor type={CodeType.JSON} value={'{"a": 1}'} />);
388
+
389
+ expect(getLayer().querySelector(".hljs-attr")).not.toBeNull();
390
+ expect(screen.getByTestId("code-editor-language")).toHaveTextContent(
391
+ "JSON",
392
+ );
393
+ expect(
394
+ document.querySelector('[data-code-type="json"]'),
395
+ ).toBeInTheDocument();
396
+ });
397
+
398
+ test.each([
399
+ [CodeType.JSON, "JSON"],
400
+ [CodeType.YAML, "YAML"],
401
+ [CodeType.JavaScript, "JavaScript"],
402
+ [CodeType.CSS, "CSS"],
403
+ [CodeType.HTML, "HTML"],
404
+ [CodeType.SQL, "SQL"],
405
+ [CodeType.Bash, "Bash"],
406
+ [CodeType.Markdown, "Markdown"],
407
+ [CodeType.Text, "Text"],
408
+ ])("%s is labelled %s in the toolbar", (type: CodeType, label: string) => {
409
+ render(<CodeEditor type={type} value="" />);
410
+
411
+ expect(screen.getByTestId("code-editor-language")).toHaveTextContent(label);
412
+ });
413
+ });
414
+
415
+ describe("CodeEditor — the line number gutter", () => {
416
+ test("numbers every line, including an empty last one", () => {
417
+ render(<CodeEditor type={CodeType.JSON} value={"{\n}\n"} />);
418
+
419
+ expect(gutterText()).toBe("1\n2\n3");
420
+ });
421
+
422
+ test("an empty document still has line 1", () => {
423
+ render(<CodeEditor type={CodeType.JSON} value="" />);
424
+
425
+ expect(gutterText()).toBe("1");
426
+ });
427
+
428
+ test("grows as lines are added", () => {
429
+ render(<CodeEditor type={CodeType.Text} value="a" />);
430
+
431
+ fireEvent.change(getInput(), { target: { value: "a\nb\nc\nd" } });
432
+
433
+ expect(gutterText()).toBe("1\n2\n3\n4");
434
+ });
435
+
436
+ test("marks the caret's line while the editor has focus, and only then", () => {
437
+ render(<CodeEditor type={CodeType.Text} value={"one\ntwo\nthree"} />);
438
+
439
+ expect(document.querySelector(".ou-code-editor__gutter-active")).toBeNull();
440
+
441
+ focusInput();
442
+ setSelection(5);
443
+
444
+ expect(
445
+ document.querySelector(".ou-code-editor__gutter-active")?.textContent,
446
+ ).toBe("2");
447
+ expect(gutterText()).toBe("1\n2\n3");
448
+
449
+ fireEvent.blur(getInput());
450
+
451
+ expect(document.querySelector(".ou-code-editor__gutter-active")).toBeNull();
452
+ });
453
+
454
+ test("the current-line band sits on the caret's line", () => {
455
+ render(<CodeEditor type={CodeType.Text} value={"one\ntwo\nthree"} />);
456
+
457
+ focusInput();
458
+ setSelection(9);
459
+
460
+ expect(screen.getByTestId("code-editor-active-line")).toHaveStyle({
461
+ top: `${CODE_EDITOR_PADDING_Y_PX + 2 * CODE_EDITOR_LINE_HEIGHT_PX}px`,
462
+ });
463
+ });
464
+
465
+ test("no current-line band over a selection", () => {
466
+ render(<CodeEditor type={CodeType.Text} value={"one\ntwo"} />);
467
+
468
+ focusInput();
469
+ setSelection(0, 5);
470
+
471
+ expect(screen.queryByTestId("code-editor-active-line")).toBeNull();
472
+ });
473
+
474
+ test("marks the line of a JSON syntax error", () => {
475
+ render(
476
+ <CodeEditor
477
+ type={CodeType.JSON}
478
+ value={'{\n "name": "api",\n "retries": 3,\n}'}
479
+ />,
480
+ );
481
+
482
+ expect(
483
+ document.querySelector(".ou-code-editor__gutter-error")?.textContent,
484
+ ).toBe("3");
485
+ });
486
+
487
+ test("marks the line of a YAML syntax error", () => {
488
+ render(
489
+ <CodeEditor
490
+ type={CodeType.YAML}
491
+ value={"detection:\n\tselection: 1\n"}
492
+ />,
493
+ );
494
+
495
+ expect(
496
+ document.querySelector(".ou-code-editor__gutter-error")?.textContent,
497
+ ).toBe("2");
498
+ });
499
+
500
+ test("the error mark only moves once the debounced check has run", () => {
501
+ jest.useFakeTimers();
502
+
503
+ render(<Harness type={CodeType.JSON} initial={'{"a": 1}'} />);
504
+
505
+ expect(document.querySelector(".ou-code-editor__gutter-error")).toBeNull();
506
+
507
+ fireEvent.change(getInput(), { target: { value: '{"a": 1,\n}' } });
508
+
509
+ advance(CODE_EDITOR_VALIDATION_DEBOUNCE_MS - 1);
510
+ expect(document.querySelector(".ou-code-editor__gutter-error")).toBeNull();
511
+
512
+ advance(1);
513
+ expect(
514
+ document.querySelector(".ou-code-editor__gutter-error")?.textContent,
515
+ ).toBe("1");
516
+ });
517
+
518
+ test("clicking a line number selects that line", () => {
519
+ render(<CodeEditor type={CodeType.Text} value={"a\nbb\nccc"} />);
520
+
521
+ // jsdom lays the gutter out at 0,0: line 2 starts one row down.
522
+ const prevented: boolean = !fireEvent.mouseDown(
523
+ screen.getByTestId("code-editor-gutter"),
524
+ {
525
+ button: 0,
526
+ clientY: CODE_EDITOR_PADDING_Y_PX + CODE_EDITOR_LINE_HEIGHT_PX + 5,
527
+ },
528
+ );
529
+
530
+ expect(prevented).toBe(true);
531
+ expect(document.activeElement).toBe(getInput());
532
+ expect(selectionOf()).toEqual([2, 5]);
533
+ });
534
+
535
+ test("clicking below the last line selects the last line", () => {
536
+ render(<CodeEditor type={CodeType.Text} value={"a\nbb"} />);
537
+
538
+ fireEvent.mouseDown(screen.getByTestId("code-editor-gutter"), {
539
+ button: 0,
540
+ clientY: 500,
541
+ });
542
+
543
+ expect(selectionOf()).toEqual([2, 4]);
544
+ });
545
+
546
+ test("a right click on the gutter is left alone", () => {
547
+ render(<CodeEditor type={CodeType.Text} value={"a\nbb"} />);
548
+
549
+ expect(
550
+ fireEvent.mouseDown(screen.getByTestId("code-editor-gutter"), {
551
+ button: 2,
552
+ clientY: 5,
553
+ }),
554
+ ).toBe(true);
555
+ });
556
+
557
+ test("showLineNumbers={false} hides the gutter", () => {
558
+ render(
559
+ <CodeEditor type={CodeType.JSON} value="{}" showLineNumbers={false} />,
560
+ );
561
+
562
+ expect(screen.queryByTestId("code-editor-gutter")).toBeNull();
563
+ });
564
+
565
+ test("wrapped prose has no gutter: a wrapped line has no single number row", () => {
566
+ render(<CodeEditor type={CodeType.Markdown} value="# Notes" />);
567
+
568
+ expect(screen.queryByTestId("code-editor-gutter")).toBeNull();
569
+ });
570
+ });
571
+
572
+ describe("CodeEditor — the status bar", () => {
573
+ test("an empty JSON document says so instead of claiming an error", () => {
574
+ render(<CodeEditor type={CodeType.JSON} value="" />);
575
+
576
+ expect(statusText()).toBe("Nothing entered yet.");
577
+ });
578
+
579
+ test("valid JSON is confirmed, with a line count", () => {
580
+ render(<CodeEditor type={CodeType.JSON} value={'{\n "a": 1\n}'} />);
581
+
582
+ expect(statusText()).toBe("Valid JSON · 3 lines");
583
+ });
584
+
585
+ test("a one-line document says line, not lines", () => {
586
+ render(<CodeEditor type={CodeType.JSON} value="[]" />);
587
+
588
+ expect(statusText()).toBe("Valid JSON · 1 line");
589
+ });
590
+
591
+ test("broken JSON is reported with what to do and where", () => {
592
+ render(
593
+ <CodeEditor
594
+ type={CodeType.JSON}
595
+ value={'{\n "name": "api",\n "retries": 3,\n}'}
596
+ />,
597
+ );
598
+
599
+ expect(statusText()).toBe(
600
+ "Trailing comma: remove the ',' before '}' (line 3, column 15)",
601
+ );
602
+ });
603
+
604
+ test("valid YAML is confirmed", () => {
605
+ render(<CodeEditor type={CodeType.YAML} value={"a: 1\nb: 2"} />);
606
+
607
+ expect(statusText()).toBe("Valid YAML · 2 lines");
608
+ });
609
+
610
+ test("a template placeholder standing in for a value still parses", () => {
611
+ render(
612
+ <CodeEditor
613
+ type={CodeType.JSON}
614
+ value={'{"retries": {{local.variables.count}}}'}
615
+ />,
616
+ );
617
+
618
+ expect(statusText()).toBe("Valid JSON · 1 line");
619
+ });
620
+
621
+ test("a template loop is reported as unchecked, not as broken and not as valid", () => {
622
+ render(
623
+ <CodeEditor
624
+ type={CodeType.JSON}
625
+ value={"[{{#each items}}{{this}},{{/each}}]"}
626
+ />,
627
+ );
628
+
629
+ expect(statusText()).toBe(
630
+ "Contains template expressions — syntax not checked.",
631
+ );
632
+ expect(document.querySelector(".ou-code-editor__gutter-error")).toBeNull();
633
+ });
634
+
635
+ test("allowJSON5 judges the document by JSON5's rules", () => {
636
+ const { rerender } = render(
637
+ <CodeEditor type={CodeType.JSON} value="{a: 1,}" />,
638
+ );
639
+
640
+ expect(statusText()).toBe(
641
+ "Property names must be wrapped in double quotes (line 1, column 2)",
642
+ );
643
+
644
+ rerender(
645
+ <CodeEditor type={CodeType.JSON} value="{a: 1,}" allowJSON5={true} />,
646
+ );
647
+
648
+ expect(statusText()).toBe("Valid JSON · 1 line");
649
+ });
650
+
651
+ test("JSON5 errors are located too", () => {
652
+ render(
653
+ <CodeEditor
654
+ type={CodeType.JSON}
655
+ value={"{\n a: ]\n}"}
656
+ allowJSON5={true}
657
+ />,
658
+ );
659
+
660
+ expect(statusText()).toMatch(/\(line 2, column 6\)$/);
661
+ expect(
662
+ document.querySelector(".ou-code-editor__gutter-error")?.textContent,
663
+ ).toBe("2");
664
+ });
665
+
666
+ test.each([
667
+ CodeType.JavaScript,
668
+ CodeType.CSS,
669
+ CodeType.HTML,
670
+ CodeType.SQL,
671
+ CodeType.Bash,
672
+ CodeType.Markdown,
673
+ CodeType.Text,
674
+ ])(
675
+ "%s has nothing to validate, so the status says nothing",
676
+ (type: CodeType) => {
677
+ render(<CodeEditor type={type} value="anything at all {" />);
678
+
679
+ expect(statusText()).toBe("");
680
+ },
681
+ );
682
+
683
+ test("a form error is shown for a language with no validator", () => {
684
+ render(
685
+ <CodeEditor
686
+ type={CodeType.JavaScript}
687
+ value="return 1;"
688
+ error="Script is required."
689
+ />,
690
+ );
691
+
692
+ expect(statusText()).toBe("Script is required.");
693
+ });
694
+
695
+ test("the form's error wins over the parser once the check has caught up", () => {
696
+ render(
697
+ <CodeEditor
698
+ type={CodeType.JSON}
699
+ value='{"a": 1,}'
700
+ error="Allowed Origins is not valid JSON."
701
+ />,
702
+ );
703
+
704
+ expect(statusText()).toBe("Allowed Origins is not valid JSON.");
705
+ });
706
+
707
+ test("the error text appears exactly once in the whole component", () => {
708
+ const message: string = "Allowed Origins is required.";
709
+
710
+ render(<CodeEditor type={CodeType.JSON} value="" error={message} />);
711
+
712
+ expect(screen.getAllByText(message)).toHaveLength(1);
713
+ });
714
+
715
+ test("it is announced politely rather than interrupting", () => {
716
+ render(<CodeEditor type={CodeType.JSON} value="{}" />);
717
+
718
+ expect(screen.getByTestId("code-editor-status")).toHaveAttribute(
719
+ "role",
720
+ "status",
721
+ );
722
+ expect(screen.getByTestId("code-editor-status")).toHaveAttribute(
723
+ "aria-live",
724
+ "polite",
725
+ );
726
+ });
727
+
728
+ describe("the check is debounced", () => {
729
+ beforeEach(() => {
730
+ jest.useFakeTimers();
731
+ });
732
+
733
+ test("a keystroke does not immediately re-judge the document", () => {
734
+ render(<Harness type={CodeType.JSON} initial={'{"a": 1}'} />);
735
+
736
+ expect(statusText()).toBe("Valid JSON · 1 line");
737
+
738
+ fireEvent.change(getInput(), { target: { value: '{"a": ' } });
739
+
740
+ advance(CODE_EDITOR_VALIDATION_DEBOUNCE_MS - 1);
741
+ expect(statusText()).toBe("Valid JSON · 1 line");
742
+
743
+ advance(1);
744
+ expect(statusText()).toBe(
745
+ "Unexpected end of JSON: expected a value (line 1, column 7)",
746
+ );
747
+ });
748
+
749
+ test("only the settled text is judged, not every intermediate one", () => {
750
+ render(<Harness type={CodeType.JSON} initial="[]" />);
751
+
752
+ fireEvent.change(getInput(), { target: { value: "[1" } });
753
+ advance(50);
754
+ fireEvent.change(getInput(), { target: { value: "[1," } });
755
+ advance(50);
756
+ fireEvent.change(getInput(), { target: { value: "[1, 2]" } });
757
+
758
+ expect(statusText()).toBe("Valid JSON · 1 line");
759
+
760
+ advance(CODE_EDITOR_VALIDATION_DEBOUNCE_MS);
761
+ expect(statusText()).toBe("Valid JSON · 1 line");
762
+ });
763
+
764
+ test("an undebounced form error does not overtake the debounced check", () => {
765
+ /*
766
+ * A form re-validates on every keystroke, so props.error arrives
767
+ * undebounced. Letting it win at once would put a red message under a
768
+ * half-typed line and defeat the debounce.
769
+ */
770
+ const { rerender } = render(
771
+ <CodeEditor type={CodeType.JSON} value="[]" />,
772
+ );
773
+
774
+ rerender(
775
+ <CodeEditor
776
+ type={CodeType.JSON}
777
+ value="["
778
+ error="Config is not valid."
779
+ />,
780
+ );
781
+
782
+ // Replaced from outside, so judged at once: settled, and the error shows.
783
+ expect(statusText()).toBe("Config is not valid.");
784
+
785
+ fireEvent.change(getInput(), { target: { value: "[1" } });
786
+
787
+ expect(statusText()).not.toBe("Config is not valid.");
788
+
789
+ advance(CODE_EDITOR_VALIDATION_DEBOUNCE_MS);
790
+ expect(statusText()).toBe("Config is not valid.");
791
+ });
792
+
793
+ test("a document replaced from outside is judged at once", () => {
794
+ const { rerender } = render(
795
+ <CodeEditor type={CodeType.JSON} value="[]" />,
796
+ );
797
+
798
+ rerender(<CodeEditor type={CodeType.JSON} value="[1,]" />);
799
+
800
+ expect(statusText()).toBe(
801
+ "Trailing comma: remove the ',' before ']' (line 1, column 3)",
802
+ );
803
+ });
804
+
805
+ test("clearing the document says so once the check catches up", () => {
806
+ render(<Harness type={CodeType.JSON} initial="[]" />);
807
+
808
+ fireEvent.change(getInput(), { target: { value: "" } });
809
+ advance(CODE_EDITOR_VALIDATION_DEBOUNCE_MS);
810
+
811
+ expect(statusText()).toBe("Nothing entered yet.");
812
+ });
813
+ });
814
+ });
815
+
816
+ describe("CodeEditor — the cursor position", () => {
817
+ test("starts at Ln 1, Col 1", () => {
818
+ render(<CodeEditor type={CodeType.Text} value={"abc\ndef"} />);
819
+
820
+ expect(cursorText()).toBe("Ln 1, Col 1");
821
+ });
822
+
823
+ test("follows the caret", () => {
824
+ render(<CodeEditor type={CodeType.Text} value={"abc\ndef"} />);
825
+
826
+ focusInput();
827
+ setSelection(6);
828
+
829
+ expect(cursorText()).toBe("Ln 2, Col 3");
830
+ });
831
+
832
+ test("counts a selection", () => {
833
+ render(<CodeEditor type={CodeType.Text} value={"abc\ndef"} />);
834
+
835
+ focusInput();
836
+ setSelection(1, 6);
837
+
838
+ expect(cursorText()).toBe("Ln 2, Col 3 (5 selected)");
839
+ });
840
+
841
+ test("reports the end the caret is on for a backwards selection", () => {
842
+ render(<CodeEditor type={CodeType.Text} value={"abc\ndef"} />);
843
+
844
+ focusInput();
845
+ act(() => {
846
+ getInput().setSelectionRange(1, 6, "backward");
847
+ });
848
+ fireEvent.keyUp(getInput(), { key: "ArrowLeft", shiftKey: true });
849
+
850
+ expect(cursorText()).toBe("Ln 1, Col 2 (5 selected)");
851
+ });
852
+ });
853
+
854
+ describe("CodeEditor — placeholder and example", () => {
855
+ test("the placeholder is drawn in the empty editor and is never the value", () => {
856
+ const onChange: jest.Mock = jest.fn();
857
+
858
+ render(
859
+ <CodeEditor
860
+ type={CodeType.JSON}
861
+ value=""
862
+ placeholder={'{ "Authorization": "Bearer ..." }'}
863
+ onChange={onChange}
864
+ />,
865
+ );
866
+
867
+ expect(getInput().value).toBe("");
868
+ expect(
869
+ document.querySelector(".ou-code-editor__placeholder")?.textContent,
870
+ ).toBe('{ "Authorization": "Bearer ..." }');
871
+ // Kept on the textarea for assistive technology; the layer draws it.
872
+ expect(getInput()).toHaveAttribute(
873
+ "placeholder",
874
+ '{ "Authorization": "Bearer ..." }',
875
+ );
876
+ expect(onChange).not.toHaveBeenCalled();
877
+ });
878
+
879
+ test("typing replaces the placeholder with the document", () => {
880
+ render(<Harness type={CodeType.JSON} placeholder="Paste JSON here." />);
881
+
882
+ fireEvent.change(getInput(), { target: { value: "{}" } });
883
+
884
+ expect(document.querySelector(".ou-code-editor__placeholder")).toBeNull();
885
+ expect(layerText()).toBe("{}\n");
886
+ });
887
+
888
+ test("a placeholder's markup is escaped", () => {
889
+ render(
890
+ <CodeEditor type={CodeType.Text} value="" placeholder="<b>bold</b>" />,
891
+ );
892
+
893
+ expect(getLayer().querySelector("b")).toBeNull();
894
+ expect(layerText()).toBe("<b>bold</b>\n");
895
+ });
896
+
897
+ const EXAMPLE: string = "\n// Say hello\nreturn { data: 'Hello' };\n ";
898
+ const TRIMMED_EXAMPLE: string = "// Say hello\nreturn { data: 'Hello' };";
899
+
900
+ test("an example is drawn dimmed and highlighted, without its blank edges", () => {
901
+ render(
902
+ <CodeEditor type={CodeType.JavaScript} value="" example={EXAMPLE} />,
903
+ );
904
+
905
+ const example: Element | null = document.querySelector(
906
+ ".ou-code-editor__example",
907
+ );
908
+
909
+ expect(example?.textContent).toBe(TRIMMED_EXAMPLE);
910
+ expect(example?.querySelector(".hljs-comment")).not.toBeNull();
911
+ expect(getInput().value).toBe("");
912
+ });
913
+
914
+ test("Insert example makes it the document and reports it", () => {
915
+ const onValue: jest.Mock = jest.fn();
916
+
917
+ render(
918
+ <Harness
919
+ type={CodeType.JavaScript}
920
+ example={EXAMPLE}
921
+ onValue={onValue}
922
+ />,
923
+ );
924
+
925
+ fireEvent.click(screen.getByTestId("code-editor-example-button"));
926
+
927
+ expect(getInput().value).toBe(TRIMMED_EXAMPLE);
928
+ expect(onValue).toHaveBeenLastCalledWith(TRIMMED_EXAMPLE);
929
+ expect(document.activeElement).toBe(getInput());
930
+ expect(selectionOf()).toEqual([0, 0]);
931
+ expect(screen.queryByTestId("code-editor-example-button")).toBeNull();
932
+ });
933
+
934
+ test("a placeholder wins the empty editor, but the example is still offered", () => {
935
+ render(
936
+ <CodeEditor
937
+ type={CodeType.JavaScript}
938
+ value=""
939
+ placeholder="Write a script."
940
+ example={EXAMPLE}
941
+ />,
942
+ );
943
+
944
+ expect(
945
+ document.querySelector(".ou-code-editor__placeholder")?.textContent,
946
+ ).toBe("Write a script.");
947
+ expect(document.querySelector(".ou-code-editor__example")).toBeNull();
948
+ expect(
949
+ screen.getByTestId("code-editor-example-button"),
950
+ ).toBeInTheDocument();
951
+ });
952
+
953
+ test("the example is not offered once there is a document", () => {
954
+ render(
955
+ <CodeEditor
956
+ type={CodeType.JavaScript}
957
+ value="return 1;"
958
+ example={EXAMPLE}
959
+ />,
960
+ );
961
+
962
+ expect(screen.queryByTestId("code-editor-example-button")).toBeNull();
963
+ expect(document.querySelector(".ou-code-editor__example")).toBeNull();
964
+ });
965
+
966
+ test("the example is not offered in a read-only editor", () => {
967
+ render(
968
+ <CodeEditor
969
+ type={CodeType.JavaScript}
970
+ value=""
971
+ example={EXAMPLE}
972
+ readOnly={true}
973
+ />,
974
+ );
975
+
976
+ expect(screen.queryByTestId("code-editor-example-button")).toBeNull();
977
+ });
978
+ });
979
+
980
+ describe("CodeEditor — Format", () => {
981
+ test("is offered for JSON only", () => {
982
+ for (const type of [
983
+ CodeType.YAML,
984
+ CodeType.JavaScript,
985
+ CodeType.CSS,
986
+ CodeType.HTML,
987
+ CodeType.SQL,
988
+ CodeType.Bash,
989
+ CodeType.Markdown,
990
+ CodeType.Text,
991
+ ]) {
992
+ render(<CodeEditor type={type} value="{}" />);
993
+
994
+ expect([type, screen.queryByTestId("code-editor-format-button")]).toEqual(
995
+ [type, null],
996
+ );
997
+
998
+ cleanup();
999
+ }
1000
+ });
1001
+
1002
+ test("is not offered in a read-only editor", () => {
1003
+ render(<CodeEditor type={CodeType.JSON} value="{}" readOnly={true} />);
1004
+
1005
+ expect(screen.queryByTestId("code-editor-format-button")).toBeNull();
1006
+ });
1007
+
1008
+ test("is enabled only for strict, valid JSON", () => {
1009
+ const { rerender } = render(
1010
+ <CodeEditor type={CodeType.JSON} value='{"a":1}' />,
1011
+ );
1012
+
1013
+ expect(screen.getByTestId("code-editor-format-button")).toBeEnabled();
1014
+
1015
+ const values: Array<string> = ["", "{a: 1}", '{"a": 1,}', '{"n": {{x}}}'];
1016
+
1017
+ const states: Array<[string, boolean]> = values.map(
1018
+ (value: string): [string, boolean] => {
1019
+ rerender(<CodeEditor type={CodeType.JSON} value={value} />);
1020
+
1021
+ return [
1022
+ value,
1023
+ (screen.getByTestId("code-editor-format-button") as HTMLButtonElement)
1024
+ .disabled,
1025
+ ];
1026
+ },
1027
+ );
1028
+
1029
+ expect(states).toEqual(
1030
+ values.map((value: string): [string, boolean] => {
1031
+ return [value, true];
1032
+ }),
1033
+ );
1034
+
1035
+ /*
1036
+ * JSON5 is valid for such a field, but reformatting through JSON would
1037
+ * drop its comments and unquoted keys: Format stays off.
1038
+ */
1039
+ rerender(
1040
+ <CodeEditor type={CodeType.JSON} value="{a: 1}" allowJSON5={true} />,
1041
+ );
1042
+
1043
+ expect(screen.getByTestId("code-editor-format-button")).toBeDisabled();
1044
+ });
1045
+
1046
+ test("pretty-prints with two spaces and reports the result", () => {
1047
+ const onValue: jest.Mock = jest.fn();
1048
+
1049
+ render(
1050
+ <Harness
1051
+ type={CodeType.JSON}
1052
+ initial={'{"name":"api","tags":["a","b"],"empty":{}}'}
1053
+ onValue={onValue}
1054
+ />,
1055
+ );
1056
+
1057
+ fireEvent.click(screen.getByTestId("code-editor-format-button"));
1058
+
1059
+ const expected: string =
1060
+ '{\n "name": "api",\n "tags": [\n "a",\n "b"\n ],\n "empty": {}\n}';
1061
+
1062
+ expect(getInput().value).toBe(expected);
1063
+ expect(onValue).toHaveBeenLastCalledWith(expected);
1064
+ });
1065
+
1066
+ test("keeps the document's own four-space indentation", () => {
1067
+ render(<Harness type={CodeType.JSON} initial={'{\n "a": [1,2]\n}'} />);
1068
+
1069
+ fireEvent.click(screen.getByTestId("code-editor-format-button"));
1070
+
1071
+ expect(getInput().value).toBe(
1072
+ '{\n "a": [\n 1,\n 2\n ]\n}',
1073
+ );
1074
+ });
1075
+
1076
+ test("never changes what the document says", () => {
1077
+ render(
1078
+ <Harness
1079
+ type={CodeType.JSON}
1080
+ initial={'{"id":12345678901234567890,"ratio":1.0,"name":"caf\\u00e9"}'}
1081
+ />,
1082
+ );
1083
+
1084
+ fireEvent.click(screen.getByTestId("code-editor-format-button"));
1085
+
1086
+ expect(getInput().value).toBe(
1087
+ '{\n "id": 12345678901234567890,\n "ratio": 1.0,\n "name": "caf\\u00e9"\n}',
1088
+ );
1089
+ });
1090
+
1091
+ test("Shift+Alt+F formats too, whatever key the layout reports", () => {
1092
+ render(<Harness type={CodeType.JSON} initial="[1,2]" />);
1093
+
1094
+ const prevented: boolean = !press("Ï", {
1095
+ code: "KeyF",
1096
+ shiftKey: true,
1097
+ altKey: true,
1098
+ });
1099
+
1100
+ expect(prevented).toBe(true);
1101
+ expect(getInput().value).toBe("[\n 1,\n 2\n]");
1102
+ });
1103
+
1104
+ test("Shift+Alt+F on invalid JSON is left alone", () => {
1105
+ render(<Harness type={CodeType.JSON} initial="[1,2" />);
1106
+
1107
+ expect(press("F", { code: "KeyF", shiftKey: true, altKey: true })).toBe(
1108
+ true,
1109
+ );
1110
+ expect(getInput().value).toBe("[1,2");
1111
+ });
1112
+ });
1113
+
1114
+ describe("CodeEditor — Copy", () => {
1115
+ let writeText: jest.Mock;
1116
+
1117
+ beforeEach(() => {
1118
+ writeText = jest.fn(() => {
1119
+ return Promise.resolve();
1120
+ });
1121
+
1122
+ Object.defineProperty(navigator, "clipboard", {
1123
+ value: { writeText },
1124
+ configurable: true,
1125
+ });
1126
+ });
1127
+
1128
+ afterEach(() => {
1129
+ Object.defineProperty(navigator, "clipboard", {
1130
+ value: undefined,
1131
+ configurable: true,
1132
+ });
1133
+ delete (document as unknown as { execCommand?: unknown }).execCommand;
1134
+ });
1135
+
1136
+ test("copies exactly what is in the editor", () => {
1137
+ render(<CodeEditor type={CodeType.JSON} value={'{"a": 1}'} />);
1138
+
1139
+ fireEvent.click(screen.getByTestId("code-editor-copy-button"));
1140
+
1141
+ expect(writeText).toHaveBeenCalledWith('{"a": 1}');
1142
+ });
1143
+
1144
+ test("copies the edited text, not the text it mounted with", () => {
1145
+ render(<CodeEditor type={CodeType.JSON} initialValue="[]" />);
1146
+
1147
+ fireEvent.change(getInput(), { target: { value: "[1]" } });
1148
+ fireEvent.click(screen.getByTestId("code-editor-copy-button"));
1149
+
1150
+ expect(writeText).toHaveBeenCalledWith("[1]");
1151
+ });
1152
+
1153
+ test("confirms the copy in the button and to assistive technology, then resets", () => {
1154
+ jest.useFakeTimers();
1155
+
1156
+ render(<CodeEditor type={CodeType.YAML} value="a: 1" />);
1157
+
1158
+ const button: HTMLElement = screen.getByTestId("code-editor-copy-button");
1159
+
1160
+ expect(button).toHaveAttribute("aria-label", "Copy YAML to clipboard");
1161
+
1162
+ fireEvent.click(button);
1163
+
1164
+ expect(button).toHaveTextContent("Copied");
1165
+ // WCAG 2.5.3: the accessible name contains the visible label.
1166
+ expect(button).toHaveAttribute("aria-label", "YAML copied to clipboard");
1167
+ expect(
1168
+ screen.getAllByRole("status").some((region: HTMLElement): boolean => {
1169
+ return (region.textContent || "").includes("YAML copied to clipboard");
1170
+ }),
1171
+ ).toBe(true);
1172
+
1173
+ advance(1999);
1174
+ expect(button).toHaveTextContent("Copied");
1175
+
1176
+ advance(1);
1177
+ expect(button).toHaveTextContent("Copy");
1178
+ expect(button).not.toHaveTextContent("Copied");
1179
+ });
1180
+
1181
+ test("falls back to execCommand('copy') where there is no async clipboard", () => {
1182
+ Object.defineProperty(navigator, "clipboard", {
1183
+ value: undefined,
1184
+ configurable: true,
1185
+ });
1186
+
1187
+ const execCommand: jest.Mock = jest.fn(() => {
1188
+ return true;
1189
+ });
1190
+
1191
+ Object.defineProperty(document, "execCommand", {
1192
+ value: execCommand,
1193
+ configurable: true,
1194
+ writable: true,
1195
+ });
1196
+
1197
+ render(<CodeEditor type={CodeType.JSON} value="[1, 2]" />);
1198
+
1199
+ focusInput();
1200
+ setSelection(1, 2);
1201
+
1202
+ fireEvent.click(screen.getByTestId("code-editor-copy-button"));
1203
+
1204
+ expect(execCommand).toHaveBeenCalledWith("copy");
1205
+ expect(screen.getByTestId("code-editor-copy-button")).toHaveTextContent(
1206
+ "Copied",
1207
+ );
1208
+ // The caller's selection is put back after the select-all the copy needed.
1209
+ expect(selectionOf()).toEqual([1, 2]);
1210
+ });
1211
+
1212
+ test("with no way to copy at all, it neither throws nor claims success", () => {
1213
+ Object.defineProperty(navigator, "clipboard", {
1214
+ value: undefined,
1215
+ configurable: true,
1216
+ });
1217
+
1218
+ render(<CodeEditor type={CodeType.JSON} value="[]" />);
1219
+
1220
+ expect(() => {
1221
+ fireEvent.click(screen.getByTestId("code-editor-copy-button"));
1222
+ }).not.toThrow();
1223
+ expect(screen.getByTestId("code-editor-copy-button")).toHaveTextContent(
1224
+ "Copy",
1225
+ );
1226
+ expect(screen.getByTestId("code-editor-copy-button")).not.toHaveTextContent(
1227
+ "Copied",
1228
+ );
1229
+ });
1230
+
1231
+ test("a denied clipboard permission does not throw", () => {
1232
+ Object.defineProperty(navigator, "clipboard", {
1233
+ value: {
1234
+ writeText: jest.fn(() => {
1235
+ return Promise.reject(new Error("denied"));
1236
+ }),
1237
+ },
1238
+ configurable: true,
1239
+ });
1240
+
1241
+ render(<CodeEditor type={CodeType.JSON} value="[]" />);
1242
+
1243
+ expect(() => {
1244
+ fireEvent.click(screen.getByTestId("code-editor-copy-button"));
1245
+ }).not.toThrow();
1246
+ });
1247
+
1248
+ test("there is nothing to copy from an empty editor", () => {
1249
+ render(<CodeEditor type={CodeType.JSON} value="" />);
1250
+
1251
+ expect(screen.getByTestId("code-editor-copy-button")).toBeDisabled();
1252
+ });
1253
+ });
1254
+
1255
+ describe("CodeEditor — Tab, and the way out", () => {
1256
+ test("Tab indents with two spaces and keeps focus", () => {
1257
+ const onValue: jest.Mock = jest.fn();
1258
+
1259
+ render(<Harness type={CodeType.JSON} initial="[]" onValue={onValue} />);
1260
+
1261
+ focusInput();
1262
+ setSelection(0);
1263
+
1264
+ expect(press("Tab")).toBe(false);
1265
+ expect(getInput().value).toBe(" []");
1266
+ expect(selectionOf()).toEqual([2, 2]);
1267
+ expect(onValue).toHaveBeenLastCalledWith(" []");
1268
+ });
1269
+
1270
+ test("Tab goes to the next tab stop, not two spaces further", () => {
1271
+ render(<Harness type={CodeType.Text} initial="abc" />);
1272
+
1273
+ focusInput();
1274
+ setSelection(3);
1275
+ press("Tab");
1276
+
1277
+ expect(getInput().value).toBe("abc ");
1278
+ expect(selectionOf()).toEqual([4, 4]);
1279
+ });
1280
+
1281
+ test("Tab over a selection inside one line replaces it with indentation", () => {
1282
+ render(<Harness type={CodeType.Text} initial="a bc d" />);
1283
+
1284
+ focusInput();
1285
+ setSelection(2, 4);
1286
+ press("Tab");
1287
+
1288
+ // "bc" becomes the two columns to the next tab stop.
1289
+ expect(getInput().value).toBe("a d");
1290
+ expect(selectionOf()).toEqual([4, 4]);
1291
+ });
1292
+
1293
+ test("Tab over several lines indents each of them", () => {
1294
+ render(<Harness type={CodeType.Text} initial={"a\nb\nc"} />);
1295
+
1296
+ focusInput();
1297
+ setSelection(0, 5);
1298
+ press("Tab");
1299
+
1300
+ expect(getInput().value).toBe(" a\n b\n c");
1301
+ expect(selectionOf()).toEqual([0, 11]);
1302
+ });
1303
+
1304
+ test("Shift+Tab takes a level off every selected line", () => {
1305
+ render(<Harness type={CodeType.Text} initial={" a\n b"} />);
1306
+
1307
+ focusInput();
1308
+ setSelection(0, 9);
1309
+
1310
+ expect(press("Tab", { shiftKey: true })).toBe(false);
1311
+ expect(getInput().value).toBe("a\n b");
1312
+ });
1313
+
1314
+ test("the level is the document's own: four spaces where it uses four", () => {
1315
+ render(<Harness type={CodeType.Text} initial={"a\n b"} />);
1316
+
1317
+ focusInput();
1318
+ setSelection(2);
1319
+ press("Tab");
1320
+
1321
+ expect(getInput().value).toBe("a\n b");
1322
+ });
1323
+
1324
+ test("Shift+Tab with nothing to take off still keeps focus", () => {
1325
+ render(<Harness type={CodeType.Text} initial="a" />);
1326
+
1327
+ focusInput();
1328
+
1329
+ expect(press("Tab", { shiftKey: true })).toBe(false);
1330
+ expect(getInput().value).toBe("a");
1331
+ });
1332
+
1333
+ test("indents with a tab when the document is indented with tabs", () => {
1334
+ render(
1335
+ <Harness type={CodeType.JavaScript} initial={"if (a) {\n\tb();\n}"} />,
1336
+ );
1337
+
1338
+ focusInput();
1339
+ setSelection(9);
1340
+ press("Tab");
1341
+
1342
+ expect(getInput().value).toBe("if (a) {\n\t\tb();\n}");
1343
+ });
1344
+
1345
+ test("Escape releases Tab to the browser, and says so", () => {
1346
+ render(<Harness type={CodeType.JSON} initial="[]" />);
1347
+
1348
+ focusInput();
1349
+
1350
+ expect(screen.queryByTestId("code-editor-tab-released")).toBeNull();
1351
+ expect(press("Escape")).toBe(false);
1352
+ expect(screen.getByTestId("code-editor-tab-released")).toHaveTextContent(
1353
+ "Tab moves focus",
1354
+ );
1355
+
1356
+ // Not prevented: the browser moves focus, as it would for any control.
1357
+ expect(press("Tab")).toBe(true);
1358
+ expect(getInput().value).toBe("[]");
1359
+ });
1360
+
1361
+ test("a second Escape is not swallowed, so a dialog around it can close", () => {
1362
+ render(<Harness type={CodeType.JSON} initial="[]" />);
1363
+
1364
+ focusInput();
1365
+
1366
+ expect(press("Escape")).toBe(false);
1367
+ expect(press("Escape")).toBe(true);
1368
+ });
1369
+
1370
+ test("Shift+Tab after Escape moves focus backwards too", () => {
1371
+ render(<Harness type={CodeType.JSON} initial="[]" />);
1372
+
1373
+ focusInput();
1374
+ press("Escape");
1375
+
1376
+ expect(press("Tab", { shiftKey: true })).toBe(true);
1377
+ });
1378
+
1379
+ test("any other key takes Tab back", () => {
1380
+ render(<Harness type={CodeType.JSON} initial="[]" />);
1381
+
1382
+ focusInput();
1383
+ press("Escape");
1384
+ press("a");
1385
+
1386
+ expect(screen.queryByTestId("code-editor-tab-released")).toBeNull();
1387
+ expect(press("Tab")).toBe(false);
1388
+ });
1389
+
1390
+ test("a modifier on its own does not take Tab back (Shift+Tab needs Shift first)", () => {
1391
+ render(<Harness type={CodeType.JSON} initial="[]" />);
1392
+
1393
+ focusInput();
1394
+ press("Escape");
1395
+ press("Shift", { shiftKey: true });
1396
+
1397
+ expect(press("Tab", { shiftKey: true })).toBe(true);
1398
+ });
1399
+
1400
+ test("leaving and coming back re-arms Tab", () => {
1401
+ render(<Harness type={CodeType.JSON} initial="[]" />);
1402
+
1403
+ focusInput();
1404
+ press("Escape");
1405
+ fireEvent.blur(getInput());
1406
+ focusInput();
1407
+
1408
+ expect(press("Tab")).toBe(false);
1409
+ });
1410
+
1411
+ test("Ctrl+Tab and Alt+Tab belong to the browser and the OS", () => {
1412
+ render(<Harness type={CodeType.JSON} initial="[]" />);
1413
+
1414
+ focusInput();
1415
+
1416
+ expect(press("Tab", { ctrlKey: true })).toBe(true);
1417
+ expect(press("Tab", { altKey: true })).toBe(true);
1418
+ expect(getInput().value).toBe("[]");
1419
+ });
1420
+
1421
+ test("the way out is described to the textarea on the way in", () => {
1422
+ render(<CodeEditor type={CodeType.JSON} value="[]" />);
1423
+
1424
+ const describedBy: Array<string> = (
1425
+ getInput().getAttribute("aria-describedby") || ""
1426
+ ).split(" ");
1427
+
1428
+ expect(
1429
+ describedBy.some((id: string): boolean => {
1430
+ return (
1431
+ document.getElementById(id)?.textContent === CODE_EDITOR_KEYS_HINT
1432
+ );
1433
+ }),
1434
+ ).toBe(true);
1435
+ expect(CODE_EDITOR_KEYS_HINT).toMatch(/Escape, then Tab/);
1436
+ });
1437
+ });
1438
+
1439
+ describe("CodeEditor — typing", () => {
1440
+ test("Enter keeps the line's indentation", () => {
1441
+ render(<Harness type={CodeType.Text} initial=" a" />);
1442
+
1443
+ focusInput();
1444
+ setSelection(5);
1445
+
1446
+ expect(press("Enter")).toBe(false);
1447
+ expect(getInput().value).toBe(" a\n ");
1448
+ expect(selectionOf()).toEqual([10, 10]);
1449
+ });
1450
+
1451
+ test("Enter between braces puts the closer on its own line", () => {
1452
+ render(<Harness type={CodeType.JSON} initial="{}" />);
1453
+
1454
+ focusInput();
1455
+ setSelection(1);
1456
+ press("Enter");
1457
+
1458
+ expect(getInput().value).toBe("{\n \n}");
1459
+ expect(selectionOf()).toEqual([4, 4]);
1460
+ });
1461
+
1462
+ test("Enter after a YAML key goes one level deeper", () => {
1463
+ render(<Harness type={CodeType.YAML} initial="detection:" />);
1464
+
1465
+ focusInput();
1466
+ setSelection(10);
1467
+ press("Enter");
1468
+
1469
+ expect(getInput().value).toBe("detection:\n ");
1470
+ });
1471
+
1472
+ test("Ctrl+Enter is left to the page (forms submit on it)", () => {
1473
+ render(<Harness type={CodeType.Text} initial="a" />);
1474
+
1475
+ focusInput();
1476
+
1477
+ expect(press("Enter", { ctrlKey: true })).toBe(true);
1478
+ expect(getInput().value).toBe("a");
1479
+ });
1480
+
1481
+ test("an opening brace gets its closer, with the caret between them", () => {
1482
+ const onValue: jest.Mock = jest.fn();
1483
+
1484
+ render(<Harness type={CodeType.JSON} initial="" onValue={onValue} />);
1485
+
1486
+ focusInput();
1487
+
1488
+ expect(press("{")).toBe(false);
1489
+ expect(getInput().value).toBe("{}");
1490
+ expect(selectionOf()).toEqual([1, 1]);
1491
+ expect(onValue).toHaveBeenLastCalledWith("{}");
1492
+ });
1493
+
1494
+ test("a quote is paired in JSON", () => {
1495
+ render(<Harness type={CodeType.JSON} initial="{}" />);
1496
+
1497
+ focusInput();
1498
+ setSelection(1);
1499
+ press('"');
1500
+
1501
+ expect(getInput().value).toBe('{""}');
1502
+ expect(selectionOf()).toEqual([2, 2]);
1503
+ });
1504
+
1505
+ test("typing the closer that is already there steps over it", () => {
1506
+ const onValue: jest.Mock = jest.fn();
1507
+
1508
+ render(<Harness type={CodeType.JSON} initial="[]" onValue={onValue} />);
1509
+
1510
+ focusInput();
1511
+ setSelection(1);
1512
+
1513
+ expect(press("]")).toBe(false);
1514
+ expect(getInput().value).toBe("[]");
1515
+ expect(selectionOf()).toEqual([2, 2]);
1516
+ expect(onValue).not.toHaveBeenCalled();
1517
+ });
1518
+
1519
+ test("a bracket over a selection wraps it", () => {
1520
+ render(<Harness type={CodeType.JavaScript} initial="a + b" />);
1521
+
1522
+ focusInput();
1523
+ setSelection(0, 5);
1524
+ press("(");
1525
+
1526
+ expect(getInput().value).toBe("(a + b)");
1527
+ expect(selectionOf()).toEqual([1, 6]);
1528
+ });
1529
+
1530
+ test("an apostrophe inside a word is typed normally", () => {
1531
+ render(<Harness type={CodeType.JavaScript} initial="don" />);
1532
+
1533
+ focusInput();
1534
+ setSelection(3);
1535
+
1536
+ // Not handled: the browser types the single character itself.
1537
+ expect(press("'")).toBe(true);
1538
+ expect(getInput().value).toBe("don");
1539
+ });
1540
+
1541
+ test("an AltGr bracket (Ctrl+Alt on Windows) is still paired", () => {
1542
+ render(<Harness type={CodeType.JSON} initial="" />);
1543
+
1544
+ focusInput();
1545
+
1546
+ expect(press("{", { ctrlKey: true, altKey: true })).toBe(false);
1547
+ expect(getInput().value).toBe("{}");
1548
+ });
1549
+
1550
+ test("Ctrl or Cmd with a character is a shortcut, not typing", () => {
1551
+ render(<Harness type={CodeType.JSON} initial="" />);
1552
+
1553
+ focusInput();
1554
+
1555
+ expect(press("[", { metaKey: true, shiftKey: true })).toBe(true);
1556
+ expect(press("{", { ctrlKey: true })).toBe(true);
1557
+ expect(getInput().value).toBe("");
1558
+ });
1559
+
1560
+ test("Backspace between an empty pair deletes both halves", () => {
1561
+ render(<Harness type={CodeType.JSON} initial="[{}]" />);
1562
+
1563
+ focusInput();
1564
+ setSelection(2);
1565
+
1566
+ expect(press("Backspace")).toBe(false);
1567
+ expect(getInput().value).toBe("[]");
1568
+ expect(selectionOf()).toEqual([1, 1]);
1569
+ });
1570
+
1571
+ test("Backspace in the indentation goes back a whole level", () => {
1572
+ render(<Harness type={CodeType.YAML} initial={"a:\n b:\n c: 1"} />);
1573
+
1574
+ focusInput();
1575
+ // After the four spaces that start line 3.
1576
+ setSelection(12);
1577
+
1578
+ expect(press("Backspace")).toBe(false);
1579
+ expect(getInput().value).toBe("a:\n b:\n c: 1");
1580
+ });
1581
+
1582
+ test("an ordinary Backspace is left to the browser", () => {
1583
+ render(<Harness type={CodeType.Text} initial="abc" />);
1584
+
1585
+ focusInput();
1586
+ setSelection(3);
1587
+
1588
+ expect(press("Backspace")).toBe(true);
1589
+ expect(getInput().value).toBe("abc");
1590
+ });
1591
+
1592
+ test("composition (IME) input is left entirely to the browser", () => {
1593
+ render(<Harness type={CodeType.JSON} initial="" />);
1594
+
1595
+ focusInput();
1596
+
1597
+ expect(press("{", { isComposing: true })).toBe(true);
1598
+ expect(press("Enter", { keyCode: 229 })).toBe(true);
1599
+ expect(getInput().value).toBe("");
1600
+ });
1601
+
1602
+ test("an edit the editor makes reaches the textarea's own input event", () => {
1603
+ // What keeps React, and a form bound to onChange, in step with the edit.
1604
+ const inputs: Array<string> = [];
1605
+
1606
+ render(<Harness type={CodeType.JSON} initial="" />);
1607
+
1608
+ getInput().addEventListener("input", (event: Event) => {
1609
+ inputs.push((event.target as HTMLTextAreaElement).value);
1610
+ });
1611
+
1612
+ focusInput();
1613
+ press("[");
1614
+
1615
+ expect(inputs).toEqual(["[]"]);
1616
+ });
1617
+ });
1618
+
1619
+ describe("CodeEditor — commands", () => {
1620
+ test.each([
1621
+ [CodeType.JavaScript, "const a = 1;", "// const a = 1;"],
1622
+ [CodeType.YAML, "a: 1", "# a: 1"],
1623
+ [CodeType.SQL, "SELECT 1;", "-- SELECT 1;"],
1624
+ [CodeType.Bash, "echo hi", "# echo hi"],
1625
+ ])(
1626
+ "Ctrl+/ comments out a %s line, and again uncomments it",
1627
+ (type: CodeType, before: string, after: string) => {
1628
+ render(<Harness type={type} initial={before} />);
1629
+
1630
+ focusInput();
1631
+
1632
+ expect(press("/", { ctrlKey: true })).toBe(false);
1633
+ expect(getInput().value).toBe(after);
1634
+
1635
+ press("/", { ctrlKey: true });
1636
+ expect(getInput().value).toBe(before);
1637
+ },
1638
+ );
1639
+
1640
+ test("Cmd+/ does the same on a Mac", () => {
1641
+ render(<Harness type={CodeType.JavaScript} initial="a();" />);
1642
+
1643
+ focusInput();
1644
+ press("/", { metaKey: true });
1645
+
1646
+ expect(getInput().value).toBe("// a();");
1647
+ });
1648
+
1649
+ test("a layout where / is shifted still toggles, by key code", () => {
1650
+ render(<Harness type={CodeType.JavaScript} initial="a();" />);
1651
+
1652
+ focusInput();
1653
+ press("7", { ctrlKey: true, shiftKey: true, code: "Slash" });
1654
+
1655
+ expect(getInput().value).toBe("// a();");
1656
+ });
1657
+
1658
+ test("JSON has no comments, so Ctrl+/ is left alone", () => {
1659
+ render(<Harness type={CodeType.JSON} initial="[]" />);
1660
+
1661
+ focusInput();
1662
+
1663
+ expect(press("/", { ctrlKey: true })).toBe(true);
1664
+ expect(getInput().value).toBe("[]");
1665
+ });
1666
+
1667
+ test("Ctrl+] indents and Ctrl+[ outdents the caret's line", () => {
1668
+ render(<Harness type={CodeType.Text} initial={"a\nb"} />);
1669
+
1670
+ focusInput();
1671
+ setSelection(2);
1672
+
1673
+ expect(press("]", { ctrlKey: true })).toBe(false);
1674
+ expect(getInput().value).toBe("a\n b");
1675
+
1676
+ expect(press("[", { ctrlKey: true })).toBe(false);
1677
+ expect(getInput().value).toBe("a\nb");
1678
+ });
1679
+ });
1680
+
1681
+ describe("CodeEditor — read-only", () => {
1682
+ test("shows that it is read-only", () => {
1683
+ render(<CodeEditor type={CodeType.JSON} value="{}" readOnly={true} />);
1684
+
1685
+ expect(screen.getByTestId("code-editor-read-only")).toHaveTextContent(
1686
+ "Read-only",
1687
+ );
1688
+ expect(getInput()).toHaveAttribute("readonly");
1689
+ });
1690
+
1691
+ test("ignores every editing key and traps neither Tab nor Escape", () => {
1692
+ const onChange: jest.Mock = jest.fn();
1693
+
1694
+ render(
1695
+ <CodeEditor
1696
+ type={CodeType.JSON}
1697
+ value="{}"
1698
+ readOnly={true}
1699
+ onChange={onChange}
1700
+ />,
1701
+ );
1702
+
1703
+ focusInput();
1704
+ setSelection(1);
1705
+
1706
+ for (const [key, init] of [
1707
+ ["Tab", {}],
1708
+ ["Tab", { shiftKey: true }],
1709
+ ["Escape", {}],
1710
+ ["Enter", {}],
1711
+ ["Backspace", {}],
1712
+ ["{", {}],
1713
+ ["/", { ctrlKey: true }],
1714
+ ["]", { ctrlKey: true }],
1715
+ ["F", { code: "KeyF", shiftKey: true, altKey: true }],
1716
+ ] as Array<[string, Record<string, unknown>]>) {
1717
+ expect([key, press(key, init)]).toEqual([key, true]);
1718
+ }
1719
+
1720
+ expect(getInput().value).toBe("{}");
1721
+ expect(onChange).not.toHaveBeenCalled();
1722
+ expect(screen.queryByTestId("code-editor-tab-released")).toBeNull();
1723
+ });
1724
+
1725
+ test("has no keyboard hint, because nothing is trapped", () => {
1726
+ render(<CodeEditor type={CodeType.JSON} value="{}" readOnly={true} />);
1727
+
1728
+ expect(screen.queryByText(CODE_EDITOR_KEYS_HINT)).toBeNull();
1729
+ });
1730
+
1731
+ test("can still be copied", () => {
1732
+ render(<CodeEditor type={CodeType.JSON} value="{}" readOnly={true} />);
1733
+
1734
+ expect(screen.getByTestId("code-editor-copy-button")).toBeEnabled();
1735
+ });
1736
+ });
1737
+
1738
+ describe("CodeEditor — accessibility", () => {
1739
+ test("the field's label names the textarea, with no competing aria-label", () => {
1740
+ render(
1741
+ <>
1742
+ <span id="the-field-label">Request body</span>
1743
+ <CodeEditor
1744
+ type={CodeType.JSON}
1745
+ value="{}"
1746
+ ariaLabelledby="the-field-label"
1747
+ />
1748
+ </>,
1749
+ );
1750
+
1751
+ expect(getInput()).toHaveAttribute("aria-labelledby", "the-field-label");
1752
+ expect(getInput()).not.toHaveAttribute("aria-label");
1753
+ expect(screen.getByRole("textbox", { name: "Request body" })).toBe(
1754
+ getInput(),
1755
+ );
1756
+ });
1757
+
1758
+ test("with no label at all, it is named after its language", () => {
1759
+ render(<CodeEditor type={CodeType.JSON} value="{}" />);
1760
+
1761
+ expect(getInput()).toHaveAttribute("aria-label", "JSON editor");
1762
+ });
1763
+
1764
+ test("an id is applied, and a <label for> names it", () => {
1765
+ render(
1766
+ <>
1767
+ <label htmlFor="step-script">Script</label>
1768
+ <CodeEditor type={CodeType.JavaScript} value="" id="step-script" />
1769
+ </>,
1770
+ );
1771
+
1772
+ expect(getInput()).toHaveAttribute("id", "step-script");
1773
+ // aria-label would outrank the <label for>, so none is set.
1774
+ expect(getInput()).not.toHaveAttribute("aria-label");
1775
+ expect(screen.getByLabelText("Script")).toBe(getInput());
1776
+ });
1777
+
1778
+ test("describedby holds the caller's ids, the hint, the keys and the status", () => {
1779
+ render(
1780
+ <>
1781
+ <span id="caller-description">From the caller.</span>
1782
+ <CodeEditor
1783
+ type={CodeType.JSON}
1784
+ value="{}"
1785
+ hint="Paste the export."
1786
+ ariaDescribedby="caller-description"
1787
+ />
1788
+ </>,
1789
+ );
1790
+
1791
+ const ids: Array<string> = (
1792
+ getInput().getAttribute("aria-describedby") || ""
1793
+ ).split(" ");
1794
+
1795
+ expect(ids[0]).toBe("caller-description");
1796
+ expect(
1797
+ ids.map((id: string): string => {
1798
+ return document.getElementById(id)?.textContent || "";
1799
+ }),
1800
+ ).toEqual([
1801
+ "From the caller.",
1802
+ "Paste the export.",
1803
+ CODE_EDITOR_KEYS_HINT,
1804
+ "Valid JSON · 1 line",
1805
+ ]);
1806
+ });
1807
+
1808
+ test("with no status to give, the status is not in the description", () => {
1809
+ render(<CodeEditor type={CodeType.JavaScript} value="a();" />);
1810
+
1811
+ const ids: Array<string> = (
1812
+ getInput().getAttribute("aria-describedby") || ""
1813
+ ).split(" ");
1814
+
1815
+ expect(ids).toHaveLength(1);
1816
+ expect(document.getElementById(ids[0]!)?.textContent).toBe(
1817
+ CODE_EDITOR_KEYS_HINT,
1818
+ );
1819
+ });
1820
+
1821
+ test("an error marks the textarea invalid", () => {
1822
+ render(<CodeEditor type={CodeType.JSON} value="{" error="Broken." />);
1823
+
1824
+ expect(getInput()).toHaveAttribute("aria-invalid", "true");
1825
+ });
1826
+
1827
+ test("ariaInvalid marks it invalid without an error message", () => {
1828
+ render(<CodeEditor type={CodeType.JSON} value="{" ariaInvalid={true} />);
1829
+
1830
+ expect(getInput()).toHaveAttribute("aria-invalid", "true");
1831
+ });
1832
+
1833
+ test("a document with only a syntax problem is not announced as invalid", () => {
1834
+ // The status bar reports it; invalid-ness is the form's call.
1835
+ render(<CodeEditor type={CodeType.JSON} value="{" />);
1836
+
1837
+ expect(getInput()).not.toHaveAttribute("aria-invalid");
1838
+ });
1839
+
1840
+ test("the attributes follow their props after mount", () => {
1841
+ const { rerender } = render(<CodeEditor type={CodeType.JSON} value="{}" />);
1842
+
1843
+ rerender(<CodeEditor type={CodeType.JSON} value="{" error="Broken." />);
1844
+
1845
+ expect(getInput()).toHaveAttribute("aria-invalid", "true");
1846
+
1847
+ rerender(<CodeEditor type={CodeType.JSON} value="{}" />);
1848
+
1849
+ expect(getInput()).not.toHaveAttribute("aria-invalid");
1850
+ });
1851
+
1852
+ test("the textarea takes the caller's tabIndex", () => {
1853
+ render(<CodeEditor type={CodeType.JSON} value="{}" tabIndex={3} />);
1854
+
1855
+ expect(getInput()).toHaveAttribute("tabindex", "3");
1856
+ });
1857
+ });
1858
+
1859
+ describe("CodeEditor — spell check, wrapping and autocorrect", () => {
1860
+ test.each([
1861
+ CodeType.JSON,
1862
+ CodeType.YAML,
1863
+ CodeType.JavaScript,
1864
+ CodeType.CSS,
1865
+ CodeType.HTML,
1866
+ CodeType.SQL,
1867
+ CodeType.Bash,
1868
+ CodeType.Text,
1869
+ ])(
1870
+ "%s is never spell checked, whatever the caller asks",
1871
+ (type: CodeType) => {
1872
+ render(<CodeEditor type={type} value="x" disableSpellCheck={false} />);
1873
+
1874
+ expect(getInput()).toHaveAttribute("spellcheck", "false");
1875
+ },
1876
+ );
1877
+
1878
+ test("Markdown is prose, and follows the caller", () => {
1879
+ const { rerender } = render(
1880
+ <CodeEditor type={CodeType.Markdown} value="# hi" />,
1881
+ );
1882
+
1883
+ expect(getInput()).toHaveAttribute("spellcheck", "true");
1884
+
1885
+ rerender(
1886
+ <CodeEditor
1887
+ type={CodeType.Markdown}
1888
+ value="# hi"
1889
+ disableSpellCheck={true}
1890
+ />,
1891
+ );
1892
+
1893
+ expect(getInput()).toHaveAttribute("spellcheck", "false");
1894
+ });
1895
+
1896
+ test("code does not wrap; prose does", () => {
1897
+ const { rerender } = render(<CodeEditor type={CodeType.JSON} value="{}" />);
1898
+
1899
+ expect(getInput()).toHaveAttribute("wrap", "off");
1900
+ expect(document.querySelector(".ou-code-editor--wrap")).toBeNull();
1901
+
1902
+ rerender(<CodeEditor type={CodeType.Markdown} value="# hi" />);
1903
+
1904
+ expect(getInput()).toHaveAttribute("wrap", "soft");
1905
+ expect(document.querySelector(".ou-code-editor--wrap")).not.toBeNull();
1906
+ });
1907
+
1908
+ test("autocomplete, autocorrect, autocapitalize and Grammarly are off", () => {
1909
+ render(<CodeEditor type={CodeType.JSON} value="{}" />);
1910
+
1911
+ expect(getInput()).toHaveAttribute("autocomplete", "off");
1912
+ expect(getInput()).toHaveAttribute("autocorrect", "off");
1913
+ expect(getInput()).toHaveAttribute("autocapitalize", "off");
1914
+ expect(getInput()).toHaveAttribute("data-gramm", "false");
1915
+ });
1916
+ });
1917
+
1918
+ describe("CodeEditor — size", () => {
1919
+ test("grows with its content up to a default maximum", () => {
1920
+ render(<CodeEditor type={CodeType.JSON} value="{}" />);
1921
+
1922
+ const scroller: HTMLElement = screen.getByTestId("code-editor-scroller");
1923
+
1924
+ expect(scroller.getAttribute("style") || "").toContain(
1925
+ `max-height: ${CODE_EDITOR_DEFAULT_MAX_HEIGHT}`,
1926
+ );
1927
+ expect(scroller.style.height).toBe("");
1928
+ });
1929
+
1930
+ test("a caller can choose the maximum", () => {
1931
+ render(<CodeEditor type={CodeType.JSON} value="{}" maxHeight="20rem" />);
1932
+
1933
+ expect(screen.getByTestId("code-editor-scroller")).toHaveStyle({
1934
+ maxHeight: "20rem",
1935
+ });
1936
+ });
1937
+
1938
+ test("a fixed height wins over growing", () => {
1939
+ render(<CodeEditor type={CodeType.SQL} value="" height="12rem" />);
1940
+
1941
+ const scroller: HTMLElement = screen.getByTestId("code-editor-scroller");
1942
+
1943
+ expect(scroller).toHaveStyle({ height: "12rem" });
1944
+ expect(scroller.style.maxHeight).toBe("");
1945
+ });
1946
+
1947
+ test("starts at five lines tall, or at minLines", () => {
1948
+ const { rerender } = render(<CodeEditor type={CodeType.JSON} value="" />);
1949
+
1950
+ const code: () => HTMLElement = (): HTMLElement => {
1951
+ return document.querySelector(".ou-code-editor__code") as HTMLElement;
1952
+ };
1953
+
1954
+ expect(code()).toHaveStyle({
1955
+ minHeight: `${
1956
+ CODE_EDITOR_DEFAULT_MIN_LINES * CODE_EDITOR_LINE_HEIGHT_PX +
1957
+ 2 * CODE_EDITOR_PADDING_Y_PX
1958
+ }px`,
1959
+ });
1960
+
1961
+ rerender(<CodeEditor type={CodeType.JSON} value="" minLines={12} />);
1962
+
1963
+ expect(code()).toHaveStyle({
1964
+ minHeight: `${12 * CODE_EDITOR_LINE_HEIGHT_PX + 2 * CODE_EDITOR_PADDING_Y_PX}px`,
1965
+ });
1966
+ });
1967
+ });
1968
+
1969
+ describe("CodeEditor — keeps the caret in view", () => {
1970
+ /*
1971
+ * jsdom does no layout, so the geometry the arithmetic reads is set by
1972
+ * hand: a 100 x 200 viewport, a 40px gutter, 8px characters.
1973
+ */
1974
+ interface Geometry {
1975
+ scroller: HTMLElement;
1976
+ scrollTop: () => number;
1977
+ scrollLeft: () => number;
1978
+ }
1979
+
1980
+ type SetUpFunction = (value: string) => Geometry;
1981
+
1982
+ const setUp: SetUpFunction = (value: string): Geometry => {
1983
+ render(<Harness type={CodeType.Text} initial={value} />);
1984
+
1985
+ const scroller: HTMLElement = screen.getByTestId("code-editor-scroller");
1986
+ let top: number = 0;
1987
+ let left: number = 0;
1988
+
1989
+ Object.defineProperty(scroller, "clientHeight", { value: 100 });
1990
+ Object.defineProperty(scroller, "clientWidth", { value: 200 });
1991
+ Object.defineProperty(scroller, "scrollTop", {
1992
+ get: (): number => {
1993
+ return top;
1994
+ },
1995
+ set: (next: number): void => {
1996
+ top = next;
1997
+ },
1998
+ });
1999
+ Object.defineProperty(scroller, "scrollLeft", {
2000
+ get: (): number => {
2001
+ return left;
2002
+ },
2003
+ set: (next: number): void => {
2004
+ left = next;
2005
+ },
2006
+ });
2007
+ Object.defineProperty(
2008
+ screen.getByTestId("code-editor-gutter"),
2009
+ "offsetWidth",
2010
+ {
2011
+ value: 40,
2012
+ },
2013
+ );
2014
+
2015
+ const measure: HTMLElement = document.querySelector(
2016
+ ".ou-code-editor__measure",
2017
+ ) as HTMLElement;
2018
+
2019
+ measure.getBoundingClientRect = (): DOMRect => {
2020
+ return { width: 32 * 8 } as DOMRect;
2021
+ };
2022
+
2023
+ return {
2024
+ scroller,
2025
+ scrollTop: (): number => {
2026
+ return top;
2027
+ },
2028
+ scrollLeft: (): number => {
2029
+ return left;
2030
+ },
2031
+ };
2032
+ };
2033
+
2034
+ const LINES: string = Array.from(
2035
+ { length: 30 },
2036
+ (_: unknown, index: number) => {
2037
+ return `line ${index + 1}`;
2038
+ },
2039
+ ).join("\n");
2040
+
2041
+ test("scrolls down to a caret below the viewport, with the padding showing", () => {
2042
+ const geometry: Geometry = setUp(LINES);
2043
+
2044
+ focusInput();
2045
+ // The start of line 20.
2046
+ setSelection(LINES.indexOf("line 20"));
2047
+
2048
+ const bottom: number =
2049
+ CODE_EDITOR_PADDING_Y_PX + 20 * CODE_EDITOR_LINE_HEIGHT_PX;
2050
+
2051
+ expect(geometry.scrollTop()).toBe(bottom + CODE_EDITOR_PADDING_Y_PX - 100);
2052
+ });
2053
+
2054
+ test("scrolls back up to a caret above the viewport", () => {
2055
+ const geometry: Geometry = setUp(LINES);
2056
+
2057
+ focusInput();
2058
+ setSelection(LINES.indexOf("line 20"));
2059
+ setSelection(LINES.indexOf("line 3"));
2060
+
2061
+ expect(geometry.scrollTop()).toBe(2 * CODE_EDITOR_LINE_HEIGHT_PX);
2062
+ });
2063
+
2064
+ test("line 1 scrolls all the way to the top", () => {
2065
+ const geometry: Geometry = setUp(LINES);
2066
+
2067
+ focusInput();
2068
+ setSelection(LINES.length);
2069
+ setSelection(0);
2070
+
2071
+ expect(geometry.scrollTop()).toBe(0);
2072
+ });
2073
+
2074
+ test("scrolls right to the end of a long line, and back past the gutter for column 1", () => {
2075
+ const long: string = "x".repeat(50);
2076
+ const geometry: Geometry = setUp(long);
2077
+
2078
+ focusInput();
2079
+ setSelection(50);
2080
+
2081
+ // 40 gutter + 12 padding + 50 columns * 8, plus a two-character margin.
2082
+ expect(geometry.scrollLeft()).toBe(40 + 12 + 400 + 16 - 200);
2083
+
2084
+ setSelection(0);
2085
+
2086
+ expect(geometry.scrollLeft()).toBe(0);
2087
+ });
2088
+
2089
+ test("counts a tab as reaching the next tab stop", () => {
2090
+ const geometry: Geometry = setUp("\t\t" + "y".repeat(20));
2091
+
2092
+ focusInput();
2093
+ // After two tabs and 16 characters: column 8 + 16 = 24.
2094
+ setSelection(18);
2095
+
2096
+ expect(geometry.scrollLeft()).toBe(40 + 12 + 24 * 8 + 16 - 200);
2097
+ });
2098
+
2099
+ test("does nothing while the editor does not have focus", () => {
2100
+ const geometry: Geometry = setUp(LINES);
2101
+
2102
+ act(() => {
2103
+ getInput().setSelectionRange(LINES.length, LINES.length);
2104
+ });
2105
+ fireEvent.keyUp(getInput(), { key: "End" });
2106
+
2107
+ expect(geometry.scrollTop()).toBe(0);
2108
+ });
2109
+ });
2110
+
2111
+ describe("CodeEditor — events and pass-through", () => {
2112
+ test("onFocus fires on focus, and onBlur only on a real blur", () => {
2113
+ const onFocus: jest.Mock = jest.fn();
2114
+ const onBlur: jest.Mock = jest.fn();
2115
+
2116
+ render(<Harness type={CodeType.JSON} onFocus={onFocus} onBlur={onBlur} />);
2117
+
2118
+ focusInput();
2119
+ expect(onFocus).toHaveBeenCalledTimes(1);
2120
+
2121
+ /*
2122
+ * Monaco's wrapper called onBlur on every change; a form field then
2123
+ * counted each keystroke as leaving the field.
2124
+ */
2125
+ fireEvent.change(getInput(), { target: { value: "[1]" } });
2126
+ press("[");
2127
+ expect(onBlur).not.toHaveBeenCalled();
2128
+
2129
+ fireEvent.blur(getInput());
2130
+ expect(onBlur).toHaveBeenCalledTimes(1);
2131
+ });
2132
+
2133
+ test("onClick fires for a click anywhere on the editor", () => {
2134
+ const onClick: jest.Mock = jest.fn();
2135
+
2136
+ render(<CodeEditor type={CodeType.JSON} value="{}" onClick={onClick} />);
2137
+
2138
+ fireEvent.click(getInput());
2139
+
2140
+ expect(onClick).toHaveBeenCalledTimes(1);
2141
+ });
2142
+
2143
+ test("dataTestId lands on the root, with the language beside it", () => {
2144
+ render(
2145
+ <CodeEditor type={CodeType.JSON} value="{}" dataTestId="request-body" />,
2146
+ );
2147
+
2148
+ const root: HTMLElement = screen.getByTestId("request-body");
2149
+
2150
+ expect(root).toHaveAttribute("data-code-type", "json");
2151
+ expect(root).toContainElement(getInput());
2152
+ });
2153
+
2154
+ test("an error paints the red border", () => {
2155
+ const { rerender } = render(<CodeEditor type={CodeType.JSON} value="{}" />);
2156
+
2157
+ const root: () => HTMLElement = (): HTMLElement => {
2158
+ return document.querySelector(".ou-code-editor") as HTMLElement;
2159
+ };
2160
+
2161
+ expect(root()).toHaveClass("border-gray-300");
2162
+ expect(root()).not.toHaveClass("border-red-300");
2163
+
2164
+ rerender(<CodeEditor type={CodeType.JSON} value="{}" error="Broken." />);
2165
+
2166
+ expect(root()).toHaveClass("border-red-300");
2167
+ expect(root()).not.toHaveClass("border-gray-300");
2168
+ });
2169
+
2170
+ test("a caller's className is added to the root", () => {
2171
+ render(
2172
+ <CodeEditor type={CodeType.JSON} value="{}" className="mt-4 custom" />,
2173
+ );
2174
+
2175
+ expect(document.querySelector(".ou-code-editor")).toHaveClass(
2176
+ "mt-4",
2177
+ "custom",
2178
+ );
2179
+ });
2180
+
2181
+ test("the hint is shown in the toolbar", () => {
2182
+ render(
2183
+ <CodeEditor type={CodeType.JSON} value="{}" hint="Paste the export." />,
2184
+ );
2185
+
2186
+ expect(screen.getByTestId("code-editor-hint")).toHaveTextContent(
2187
+ "Paste the export.",
2188
+ );
2189
+ });
2190
+
2191
+ test("an unknown type falls back to plain text rather than crashing", () => {
2192
+ render(
2193
+ <CodeEditor type={"cobol" as unknown as CodeType} value="MOVE A TO B." />,
2194
+ );
2195
+
2196
+ expect(screen.getByTestId("code-editor-language")).toHaveTextContent(
2197
+ "Text",
2198
+ );
2199
+ expect(layerText()).toBe("MOVE A TO B.\n");
2200
+ });
2201
+ });
2202
+
2203
+ describe("applyEditToTextarea", () => {
2204
+ let textarea: HTMLTextAreaElement;
2205
+ let inputs: Array<string>;
2206
+
2207
+ beforeEach(() => {
2208
+ textarea = document.createElement("textarea");
2209
+ document.body.appendChild(textarea);
2210
+ textarea.value = "hello world";
2211
+ inputs = [];
2212
+ textarea.addEventListener("input", () => {
2213
+ inputs.push(textarea.value);
2214
+ });
2215
+ textarea.focus();
2216
+ });
2217
+
2218
+ afterEach(() => {
2219
+ textarea.remove();
2220
+ delete (document as unknown as { execCommand?: unknown }).execCommand;
2221
+ });
2222
+
2223
+ type StubFunction = (
2224
+ behaviour: (command: string, value?: string) => boolean,
2225
+ ) => jest.Mock;
2226
+
2227
+ const stubExecCommand: StubFunction = (
2228
+ behaviour: (command: string, value?: string) => boolean,
2229
+ ): jest.Mock => {
2230
+ const execCommand: jest.Mock = jest.fn(
2231
+ (command: string, _showUi: boolean, value?: string): boolean => {
2232
+ return behaviour(command, value);
2233
+ },
2234
+ );
2235
+
2236
+ Object.defineProperty(document, "execCommand", {
2237
+ value: execCommand,
2238
+ configurable: true,
2239
+ writable: true,
2240
+ });
2241
+
2242
+ return execCommand;
2243
+ };
2244
+
2245
+ // What a browser does for insertText: replace the selection, fire input.
2246
+ const browserInsert: (command: string, value?: string) => boolean = (
2247
+ command: string,
2248
+ value?: string,
2249
+ ): boolean => {
2250
+ if (command === "insertText") {
2251
+ textarea.setRangeText(
2252
+ value || "",
2253
+ textarea.selectionStart,
2254
+ textarea.selectionEnd,
2255
+ "end",
2256
+ );
2257
+ }
2258
+
2259
+ if (command === "delete") {
2260
+ textarea.setRangeText(
2261
+ "",
2262
+ textarea.selectionStart,
2263
+ textarea.selectionEnd,
2264
+ "end",
2265
+ );
2266
+ }
2267
+
2268
+ return true;
2269
+ };
2270
+
2271
+ const EDIT: TextEdit = {
2272
+ from: 6,
2273
+ to: 11,
2274
+ insert: "there",
2275
+ selectionStart: 11,
2276
+ selectionEnd: 11,
2277
+ };
2278
+
2279
+ test("goes through execCommand, so the edit is one step on the undo stack", () => {
2280
+ const execCommand: jest.Mock = stubExecCommand(browserInsert);
2281
+
2282
+ applyEditToTextarea(textarea, EDIT);
2283
+
2284
+ expect(execCommand).toHaveBeenCalledWith("insertText", false, "there");
2285
+ expect(textarea.value).toBe("hello there");
2286
+ // The browser fires its own input event; no second one is faked.
2287
+ expect(inputs).toEqual([]);
2288
+ expect([textarea.selectionStart, textarea.selectionEnd]).toEqual([11, 11]);
2289
+ });
2290
+
2291
+ test("an empty insertion over a range is a delete", () => {
2292
+ const execCommand: jest.Mock = stubExecCommand(browserInsert);
2293
+
2294
+ applyEditToTextarea(textarea, {
2295
+ from: 5,
2296
+ to: 11,
2297
+ insert: "",
2298
+ selectionStart: 5,
2299
+ selectionEnd: 5,
2300
+ });
2301
+
2302
+ expect(execCommand).toHaveBeenCalledWith("delete", false);
2303
+ expect(textarea.value).toBe("hello");
2304
+ });
2305
+
2306
+ test("a caret move alone touches no text", () => {
2307
+ const execCommand: jest.Mock = stubExecCommand(browserInsert);
2308
+
2309
+ applyEditToTextarea(textarea, {
2310
+ from: 3,
2311
+ to: 3,
2312
+ insert: "",
2313
+ selectionStart: 4,
2314
+ selectionEnd: 4,
2315
+ });
2316
+
2317
+ expect(execCommand).not.toHaveBeenCalled();
2318
+ expect(inputs).toEqual([]);
2319
+ expect(textarea.value).toBe("hello world");
2320
+ expect([textarea.selectionStart, textarea.selectionEnd]).toEqual([4, 4]);
2321
+ });
2322
+
2323
+ test("writes the value and fires input itself where execCommand is missing", () => {
2324
+ applyEditToTextarea(textarea, EDIT);
2325
+
2326
+ expect(textarea.value).toBe("hello there");
2327
+ expect(inputs).toEqual(["hello there"]);
2328
+ expect([textarea.selectionStart, textarea.selectionEnd]).toEqual([11, 11]);
2329
+ });
2330
+
2331
+ test("falls back when execCommand declines", () => {
2332
+ stubExecCommand((): boolean => {
2333
+ return false;
2334
+ });
2335
+
2336
+ applyEditToTextarea(textarea, EDIT);
2337
+
2338
+ expect(textarea.value).toBe("hello there");
2339
+ expect(inputs).toEqual(["hello there"]);
2340
+ });
2341
+
2342
+ test("falls back when execCommand claims success but leaves the wrong text", () => {
2343
+ stubExecCommand((): boolean => {
2344
+ return true;
2345
+ });
2346
+
2347
+ applyEditToTextarea(textarea, EDIT);
2348
+
2349
+ expect(textarea.value).toBe("hello there");
2350
+ expect(inputs).toEqual(["hello there"]);
2351
+ });
2352
+
2353
+ test("falls back when execCommand throws", () => {
2354
+ stubExecCommand((): boolean => {
2355
+ throw new Error("not supported");
2356
+ });
2357
+
2358
+ applyEditToTextarea(textarea, EDIT);
2359
+
2360
+ expect(textarea.value).toBe("hello there");
2361
+ });
2362
+ });