@oneuptime/common 12.0.3 → 12.0.5

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 (516) hide show
  1. package/Models/DatabaseModels/AIRun.ts +38 -1
  2. package/Models/DatabaseModels/AlertFeed.ts +30 -0
  3. package/Models/DatabaseModels/CephCluster.ts +14 -0
  4. package/Models/DatabaseModels/CodeRepository.ts +14 -6
  5. package/Models/DatabaseModels/Dashboard.ts +28 -0
  6. package/Models/DatabaseModels/DockerHost.ts +14 -0
  7. package/Models/DatabaseModels/GlobalOidcProject.ts +18 -0
  8. package/Models/DatabaseModels/GlobalSsoProject.ts +18 -0
  9. package/Models/DatabaseModels/Incident.ts +2 -0
  10. package/Models/DatabaseModels/IncidentEpisode.ts +1 -0
  11. package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +1 -0
  12. package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
  13. package/Models/DatabaseModels/IncidentFeed.ts +30 -0
  14. package/Models/DatabaseModels/IncidentPublicNote.ts +1 -0
  15. package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
  16. package/Models/DatabaseModels/IoTFleet.ts +14 -0
  17. package/Models/DatabaseModels/KubernetesCluster.ts +14 -0
  18. package/Models/DatabaseModels/LogSavedView.ts +33 -0
  19. package/Models/DatabaseModels/NetworkDevice.ts +14 -0
  20. package/Models/DatabaseModels/NetworkInterface.ts +18 -0
  21. package/Models/DatabaseModels/Project.ts +247 -11
  22. package/Models/DatabaseModels/ProxmoxCluster.ts +14 -0
  23. package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -0
  24. package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +1 -0
  25. package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
  26. package/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.ts +4 -4
  27. package/Models/DatabaseModels/Service.ts +30 -0
  28. package/Models/DatabaseModels/StatusPage.ts +28 -0
  29. package/Models/DatabaseModels/StatusPageAnnouncement.ts +1 -0
  30. package/Models/DatabaseModels/StatusPagePrivateUser.ts +30 -0
  31. package/Models/DatabaseModels/User.ts +30 -0
  32. package/Server/API/AIAgentDataAPI.ts +75 -55
  33. package/Server/API/AIInvestigationAPI.ts +175 -17
  34. package/Server/API/DashboardAPI.ts +429 -22
  35. package/Server/API/GitHubAPI.ts +119 -283
  36. package/Server/API/StatusPageAPI.ts +38 -19
  37. package/Server/API/UserAPI.ts +263 -1
  38. package/Server/EnvironmentConfig.ts +23 -3
  39. package/Server/Infrastructure/ClickhouseConfig.ts +15 -1
  40. package/Server/Infrastructure/ClickhouseDatabase.ts +1 -1
  41. package/Server/Infrastructure/GlobalCache.ts +37 -0
  42. package/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.ts +21 -0
  43. package/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.ts +68 -0
  44. package/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.ts +27 -0
  45. package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +100 -0
  46. package/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.ts +89 -0
  47. package/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.ts +27 -0
  48. package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +214 -0
  49. package/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.ts +69 -0
  50. package/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.ts +40 -0
  51. package/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.ts +26 -0
  52. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +20 -0
  53. package/Server/Services/AIRunService.ts +78 -10
  54. package/Server/Services/AIService.ts +94 -22
  55. package/Server/Services/AlertFeedService.ts +5 -0
  56. package/Server/Services/AnalyticsDatabaseService.ts +101 -1
  57. package/Server/Services/CodeRepositoryService.ts +105 -2
  58. package/Server/Services/DatabaseService.ts +227 -3
  59. package/Server/Services/IncidentFeedService.ts +5 -0
  60. package/Server/Services/LlmLogService.ts +46 -2
  61. package/Server/Services/OpenTelemetryIngestService.ts +230 -26
  62. package/Server/Services/UserService.ts +184 -0
  63. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +6 -0
  64. package/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.ts +4 -0
  65. package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +8 -5
  66. package/Server/Utils/AI/CodeFix/FixRunBudget.ts +90 -16
  67. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +2 -0
  68. package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +2 -0
  69. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +223 -22
  70. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +25 -24
  71. package/Server/Utils/AI/SRE/ConfidenceSignal.ts +182 -71
  72. package/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.ts +252 -43
  73. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +25 -24
  74. package/Server/Utils/AI/SRE/InstrumentationTaskTrigger.ts +47 -13
  75. package/Server/Utils/AI/SRE/InvestigationGrader.ts +9 -5
  76. package/Server/Utils/AI/SRE/InvestigationQueue.ts +141 -24
  77. package/Server/Utils/AI/SRE/InvestigationSubjectLock.ts +66 -0
  78. package/Server/Utils/AI/SRE/PostedRootCause.ts +94 -3
  79. package/Server/Utils/AI/SRE/README.md +1 -1
  80. package/Server/Utils/AI/SRE/SubjectCodeFixRun.ts +132 -10
  81. package/Server/Utils/AI/Toolbox/CodeTools.ts +45 -4
  82. package/Server/Utils/AnalyticsDatabase/ClusterConfig.ts +24 -0
  83. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +13 -0
  84. package/Server/Utils/CodeRepository/GitHub/GitHub.ts +157 -4
  85. package/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.ts +130 -0
  86. package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +29 -0
  87. package/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.ts +56 -0
  88. package/Server/Utils/Monitor/MonitorMetricUtil.ts +46 -0
  89. package/Server/Utils/Monitor/MonitorTemplateUtil.ts +1 -0
  90. package/Server/Utils/PasswordHash.ts +330 -0
  91. package/Tests/App/Dashboard/AIInvestigationHeaderStatus.test.tsx +251 -0
  92. package/Tests/App/Dashboard/EventStatusPanel.test.tsx +747 -0
  93. package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +480 -0
  94. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +1184 -0
  95. package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +500 -0
  96. package/Tests/App/Dashboard/OverviewCustomFields.test.tsx +333 -0
  97. package/Tests/App/Dashboard/PortMonitorCriteriaFilter.test.ts +93 -0
  98. package/Tests/App/Dashboard/UserCustomFields.test.tsx +392 -0
  99. package/Tests/App/StatusPage/PublicStatusPageAPIErrorHandling.test.ts +117 -0
  100. package/Tests/App/StatusPage/StatusPageModelAPIPolymorphism.test.ts +223 -0
  101. package/Tests/Models/DatabaseModels/PermissionCatalogueCoverage.test.ts +243 -0
  102. package/Tests/Server/API/AIAgentDataFixFromIncidentContext.test.ts +266 -0
  103. package/Tests/Server/API/AIAgentDataRepositoryToken.test.ts +381 -0
  104. package/Tests/Server/API/AIInvestigationAPI.test.ts +594 -0
  105. package/Tests/Server/API/AIInvestigationCreateFixTask.test.ts +80 -0
  106. package/Tests/Server/API/DashboardMasterPasswordAPI.test.ts +172 -1
  107. package/Tests/Server/API/DashboardPublicAttributeValuesAPI.test.ts +943 -0
  108. package/Tests/Server/API/DashboardPublicMetricsAggregateAPI.test.ts +1296 -0
  109. package/Tests/Server/API/DashboardPublicTemplatePayloads.test.ts +598 -0
  110. package/Tests/Server/API/GitHubAppInstallationBindingAPI.test.ts +539 -0
  111. package/Tests/Server/API/Helpers.ts +6 -1
  112. package/Tests/Server/API/StatusPageMasterPasswordAPI.test.ts +335 -0
  113. package/Tests/Server/API/UserProjectsAPI.test.ts +852 -0
  114. package/Tests/Server/Infrastructure/GlobalCache.test.ts +128 -0
  115. package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +215 -0
  116. package/Tests/Server/Services/AIRunCodeFixClaim.test.ts +59 -5
  117. package/Tests/Server/Services/AIRunHumanVerdict.test.ts +127 -58
  118. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +396 -20
  119. package/Tests/Server/Services/AddMasterPasswordSaltMigration.test.ts +283 -0
  120. package/Tests/Server/Services/AddPerUserPasswordSaltMigration.test.ts +178 -0
  121. package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +582 -0
  122. package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +30 -1
  123. package/Tests/Server/Services/CodeRepositoryInstallationBinding.test.ts +305 -0
  124. package/Tests/Server/Services/CodeRepositoryResolutionBinding.test.ts +147 -0
  125. package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +880 -0
  126. package/Tests/Server/Services/DatabaseServiceUpdateColumnsWithoutHooks.test.ts +54 -0
  127. package/Tests/Server/Services/FeedAIRunAssociation.test.ts +125 -0
  128. package/Tests/Server/Services/FixFromIncidentTaskTrigger.test.ts +418 -57
  129. package/Tests/Server/Services/InstrumentationTaskTrigger.test.ts +173 -14
  130. package/Tests/Server/Services/MasterPasswordScrypt.test.ts +567 -0
  131. package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +568 -0
  132. package/Tests/Server/Services/RestoreDroppedUniqueIndexesMigration.test.ts +386 -0
  133. package/Tests/Server/Services/RestoreServiceLowerNameIndexMigration.test.ts +240 -0
  134. package/Tests/Server/Services/SeparateIncidentAlertAiSettingsMigration.test.ts +194 -0
  135. package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +885 -0
  136. package/Tests/Server/Services/WidenHashedStringColumnsForScryptMigration.test.ts +220 -0
  137. package/Tests/Server/Utils/AI/AIAlertGating.test.ts +37 -4
  138. package/Tests/Server/Utils/AI/AIConfidenceSignal.test.ts +330 -29
  139. package/Tests/Server/Utils/AI/AIIncidentGating.test.ts +25 -0
  140. package/Tests/Server/Utils/AI/AIInvestigationQueue.test.ts +293 -5
  141. package/Tests/Server/Utils/AI/CodeFixAgentCompletion.test.ts +59 -1
  142. package/Tests/Server/Utils/AI/CodeTools.test.ts +47 -1
  143. package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +10 -0
  144. package/Tests/Server/Utils/AI/FixFromIncidentAutoTrigger.test.ts +342 -19
  145. package/Tests/Server/Utils/AI/FixRunBudget.test.ts +214 -30
  146. package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +2 -0
  147. package/Tests/Server/Utils/AI/InvestigationGrader.test.ts +65 -17
  148. package/Tests/Server/Utils/AI/InvestigationQueueRemediationExecution.test.ts +3 -0
  149. package/Tests/Server/Utils/AI/InvestigationSettlement.test.ts +313 -20
  150. package/Tests/Server/Utils/AI/PostedRootCause.test.ts +254 -0
  151. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +4 -0
  152. package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +2 -0
  153. package/Tests/Server/Utils/AI/SubjectCodeFixRunDedupe.test.ts +44 -0
  154. package/Tests/Server/Utils/AnalyticsDatabase/ClusterAwareSchema.test.ts +30 -0
  155. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +120 -0
  156. package/Tests/Server/Utils/CodeRepository/GitHubInstallationBinding.test.ts +292 -0
  157. package/Tests/Server/Utils/GitHubInstallationOwnershipVerification.test.ts +340 -0
  158. package/Tests/Server/Utils/GitHubWebhookAndTreeCacheIsolation.test.ts +250 -0
  159. package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaPortTimings.test.ts +212 -0
  160. package/Tests/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.test.ts +401 -0
  161. package/Tests/Server/Utils/Monitor/MonitorMetricUtilPortTimings.test.ts +243 -0
  162. package/Tests/Server/Utils/PasswordHash.test.ts +585 -0
  163. package/Tests/Types/Dashboard/DashboardTemplates.test.ts +111 -0
  164. package/Tests/Types/Database/ColumnLength.test.ts +7 -1
  165. package/Tests/Types/HashedStringPerUserSalt.test.ts +476 -0
  166. package/Tests/Types/Monitor/CephMetricCatalog.test.ts +104 -0
  167. package/Tests/Types/Monitor/CriteriaFilter.test.ts +4 -0
  168. package/Tests/Types/Monitor/DockerAlertTemplates.test.ts +293 -0
  169. package/Tests/Types/Monitor/DockerMetricCatalog.test.ts +109 -0
  170. package/Tests/Types/Monitor/DockerSwarmMetricCatalog.test.ts +118 -0
  171. package/Tests/Types/Monitor/HostAlertTemplates.test.ts +249 -0
  172. package/Tests/Types/Monitor/HostMetricCatalog.test.ts +104 -0
  173. package/Tests/Types/Monitor/IotAlertTemplates.test.ts +320 -0
  174. package/Tests/Types/Monitor/IotMetricCatalog.test.ts +104 -0
  175. package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +118 -0
  176. package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +42 -0
  177. package/Tests/Types/Monitor/MonitorStep.test.ts +38 -0
  178. package/Tests/Types/Monitor/MonitorStepDomainMonitor.test.ts +75 -0
  179. package/Tests/Types/Monitor/PodmanAlertTemplates.test.ts +271 -0
  180. package/Tests/Types/Monitor/PodmanMetricCatalog.test.ts +109 -0
  181. package/Tests/Types/Monitor/ProxmoxMetricCatalog.test.ts +107 -0
  182. package/Tests/Types/Monitor/SnmpMonitor/SnmpVendorTemplate.test.ts +151 -0
  183. package/Tests/Types/Permission.test.ts +137 -0
  184. package/Tests/Types/Rum/SessionReplayMaskingMode.test.ts +101 -0
  185. package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +5 -5
  186. package/Tests/UI/Components/CardModelDetailEdit.test.tsx +3 -5
  187. package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +696 -0
  188. package/Tests/UI/Components/ErrorBoundary.test.tsx +158 -0
  189. package/Tests/UI/Components/FeedItemSafeMode.test.tsx +74 -0
  190. package/Tests/UI/Components/MoreMenu.test.tsx +756 -0
  191. package/Tests/UI/Components/StatusPage/ResourceGroupSection.test.tsx +62 -0
  192. package/Tests/UI/Monitor/PortMonitorView.test.tsx +237 -0
  193. package/Tests/UI/Telemetry/TelemetrySnapshotWindowAlert.test.tsx +91 -0
  194. package/Tests/UI/Utils/DownloadFile.test.ts +103 -0
  195. package/Tests/UI/Utils/ErrorSupportBundle.test.ts +582 -0
  196. package/Tests/UI/Utils/Project.test.ts +31 -0
  197. package/Tests/UI/Utils/StatusPageModelAPIInjection.test.ts +245 -0
  198. package/Tests/UI/Utils/UseDashboardGridDnd.test.tsx +820 -0
  199. package/Tests/UI/Utils/UserProjectsModelAPI.test.ts +714 -0
  200. package/Tests/Utils/Dashboard/DashboardViewConfig.test.ts +329 -0
  201. package/Tests/Utils/Dashboard/GridLayout.test.ts +951 -0
  202. package/Tests/Utils/Monitor/MonitorMetricType.test.ts +117 -0
  203. package/Tests/Utils/RecordingRuleExpression.test.ts +327 -0
  204. package/Tests/Utils/StatusPage/GroupNestingLayout.test.ts +186 -0
  205. package/Tests/Utils/StatusPage/GroupTree.test.ts +456 -0
  206. package/Tests/Utils/StatusPage/OverviewGroupHierarchyVisibility.test.ts +588 -0
  207. package/Tests/Utils/TeamMembersByProject.test.ts +655 -0
  208. package/Tests/Utils/Telemetry/SavedViewTimeRange.test.ts +244 -0
  209. package/Tests/Utils/Telemetry/TelemetryQueryTimeRange.test.ts +825 -0
  210. package/Types/AI/AIRunCodeFixRecommendation.ts +17 -0
  211. package/Types/AI/CodeFixTaskContext.ts +63 -3
  212. package/Types/AI/CodeFixTaskType.ts +3 -3
  213. package/Types/Database/ColumnLength.ts +9 -1
  214. package/Types/Database/TableColumn.ts +11 -0
  215. package/Types/Database/UnsynchronizedIndex.ts +48 -0
  216. package/Types/HashedString.ts +140 -4
  217. package/Types/Monitor/CriteriaFilter.ts +4 -0
  218. package/Types/Monitor/DomainMonitor/DomainLookupMethod.ts +23 -0
  219. package/Types/Monitor/DomainMonitor/DomainMonitorResponse.ts +7 -0
  220. package/Types/Monitor/MonitorCriteriaInstance.ts +24 -7
  221. package/Types/Monitor/MonitorMetricType.ts +8 -0
  222. package/Types/Monitor/MonitorStep.ts +10 -1
  223. package/Types/Monitor/MonitorStepDomainMonitor.ts +21 -0
  224. package/Types/Monitor/PortMonitor/PortMonitorTimings.ts +10 -0
  225. package/Types/Permission.ts +23 -175
  226. package/Types/Probe/ProbeMonitorResponse.ts +6 -0
  227. package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +2 -3
  228. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +85 -11
  229. package/UI/Components/ErrorBoundary.tsx +138 -11
  230. package/UI/Components/Feed/FeedItem.tsx +9 -2
  231. package/UI/Components/ModelDetail/CardModelDetail.tsx +0 -12
  232. package/UI/Components/ModelTable/BaseModelTable.tsx +1 -1
  233. package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +12 -2
  234. package/UI/Components/MoreMenu/MoreMenu.tsx +285 -97
  235. package/UI/Components/MoreMenu/MoreMenuItem.tsx +8 -5
  236. package/UI/Components/Navbar/NavBar.tsx +12 -2
  237. package/UI/Components/Navbar/NavBarMenuModal.tsx +19 -7
  238. package/UI/Utils/DownloadFile.ts +16 -8
  239. package/UI/Utils/ErrorSupportBundle.ts +805 -0
  240. package/UI/Utils/ModelAPI/ModelAPI.ts +19 -8
  241. package/UI/Utils/ModelAPI/UserProjectsModelAPI.ts +257 -0
  242. package/UI/Utils/Project.ts +4 -0
  243. package/UI/Utils/StatusPage.ts +8 -2
  244. package/UI/Utils/UseDashboardGridDnd.ts +917 -0
  245. package/Utils/Dashboard/DashboardViewConfig.ts +80 -36
  246. package/Utils/Dashboard/GridLayout.ts +523 -0
  247. package/Utils/Metrics/RecordingRuleExpression.ts +19 -17
  248. package/Utils/Monitor/MonitorMetricType.ts +44 -2
  249. package/Utils/StatusPage/GroupNestingLayout.ts +69 -0
  250. package/Utils/StatusPage/GroupTree.ts +157 -66
  251. package/Utils/StatusPage/ResourceUptime.ts +37 -2
  252. package/Utils/TeamMembersByProject.ts +237 -0
  253. package/Utils/Telemetry/SavedViewTimeRange.ts +114 -0
  254. package/Utils/Telemetry/TelemetryQueryTimeRange.ts +334 -0
  255. package/build/dist/Models/DatabaseModels/AIRun.js +39 -1
  256. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  257. package/build/dist/Models/DatabaseModels/AlertFeed.js +32 -0
  258. package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
  259. package/build/dist/Models/DatabaseModels/CephCluster.js +16 -1
  260. package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
  261. package/build/dist/Models/DatabaseModels/CodeRepository.js +14 -6
  262. package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -1
  263. package/build/dist/Models/DatabaseModels/Dashboard.js +29 -0
  264. package/build/dist/Models/DatabaseModels/Dashboard.js.map +1 -1
  265. package/build/dist/Models/DatabaseModels/DockerHost.js +16 -1
  266. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  267. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +16 -1
  268. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
  269. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +16 -1
  270. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -1
  271. package/build/dist/Models/DatabaseModels/Incident.js +2 -0
  272. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  273. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +1 -0
  274. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  275. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +1 -0
  276. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
  277. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
  278. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
  279. package/build/dist/Models/DatabaseModels/IncidentFeed.js +32 -0
  280. package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
  281. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +1 -0
  282. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
  283. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
  284. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
  285. package/build/dist/Models/DatabaseModels/IoTFleet.js +16 -1
  286. package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
  287. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +16 -1
  288. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  289. package/build/dist/Models/DatabaseModels/LogSavedView.js +33 -0
  290. package/build/dist/Models/DatabaseModels/LogSavedView.js.map +1 -1
  291. package/build/dist/Models/DatabaseModels/NetworkDevice.js +16 -1
  292. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  293. package/build/dist/Models/DatabaseModels/NetworkInterface.js +16 -1
  294. package/build/dist/Models/DatabaseModels/NetworkInterface.js.map +1 -1
  295. package/build/dist/Models/DatabaseModels/Project.js +257 -13
  296. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  297. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +16 -1
  298. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
  299. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -0
  300. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  301. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +1 -0
  302. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
  303. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
  304. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
  305. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.js +4 -4
  306. package/build/dist/Models/DatabaseModels/Service.js +28 -1
  307. package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
  308. package/build/dist/Models/DatabaseModels/StatusPage.js +29 -0
  309. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  310. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +1 -0
  311. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
  312. package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js +32 -0
  313. package/build/dist/Models/DatabaseModels/StatusPagePrivateUser.js.map +1 -1
  314. package/build/dist/Models/DatabaseModels/User.js +32 -0
  315. package/build/dist/Models/DatabaseModels/User.js.map +1 -1
  316. package/build/dist/Server/API/AIAgentDataAPI.js +49 -48
  317. package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
  318. package/build/dist/Server/API/AIInvestigationAPI.js +111 -11
  319. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  320. package/build/dist/Server/API/DashboardAPI.js +312 -15
  321. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  322. package/build/dist/Server/API/GitHubAPI.js +94 -167
  323. package/build/dist/Server/API/GitHubAPI.js.map +1 -1
  324. package/build/dist/Server/API/StatusPageAPI.js +29 -11
  325. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  326. package/build/dist/Server/API/UserAPI.js +187 -2
  327. package/build/dist/Server/API/UserAPI.js.map +1 -1
  328. package/build/dist/Server/EnvironmentConfig.js +21 -3
  329. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  330. package/build/dist/Server/Infrastructure/ClickhouseConfig.js +12 -1
  331. package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
  332. package/build/dist/Server/Infrastructure/ClickhouseDatabase.js +1 -1
  333. package/build/dist/Server/Infrastructure/ClickhouseDatabase.js.map +1 -1
  334. package/build/dist/Server/Infrastructure/GlobalCache.js +29 -0
  335. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  336. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js +14 -0
  337. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786018109307-AddPerUserPasswordSalt.js.map +1 -0
  338. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js +59 -0
  339. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786023262402-WidenHashedStringColumnsForScrypt.js.map +1 -0
  340. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.js +22 -0
  341. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.js.map +1 -0
  342. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +93 -0
  343. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js.map +1 -0
  344. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.js +36 -0
  345. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.js.map +1 -0
  346. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.js +18 -0
  347. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.js.map +1 -0
  348. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js +101 -0
  349. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.js.map +1 -0
  350. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js +66 -0
  351. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786300000000-QuarantineUnboundGitHubInstallations.js.map +1 -0
  352. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.js +19 -0
  353. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.js.map +1 -0
  354. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.js +19 -0
  355. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.js.map +1 -0
  356. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +20 -0
  357. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  358. package/build/dist/Server/Services/AIRunService.js +58 -12
  359. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  360. package/build/dist/Server/Services/AIService.js +74 -21
  361. package/build/dist/Server/Services/AIService.js.map +1 -1
  362. package/build/dist/Server/Services/AlertFeedService.js +3 -0
  363. package/build/dist/Server/Services/AlertFeedService.js.map +1 -1
  364. package/build/dist/Server/Services/AnalyticsDatabaseService.js +86 -2
  365. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  366. package/build/dist/Server/Services/CodeRepositoryService.js +90 -2
  367. package/build/dist/Server/Services/CodeRepositoryService.js.map +1 -1
  368. package/build/dist/Server/Services/DatabaseService.js +180 -3
  369. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  370. package/build/dist/Server/Services/IncidentFeedService.js +3 -0
  371. package/build/dist/Server/Services/IncidentFeedService.js.map +1 -1
  372. package/build/dist/Server/Services/LlmLogService.js +22 -1
  373. package/build/dist/Server/Services/LlmLogService.js.map +1 -1
  374. package/build/dist/Server/Services/OpenTelemetryIngestService.js +155 -25
  375. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  376. package/build/dist/Server/Services/UserService.js +165 -0
  377. package/build/dist/Server/Services/UserService.js.map +1 -1
  378. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +3 -0
  379. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
  380. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js +4 -0
  381. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js.map +1 -1
  382. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +8 -5
  383. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -1
  384. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +63 -24
  385. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
  386. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +2 -0
  387. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  388. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +2 -0
  389. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
  390. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +156 -28
  391. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  392. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +21 -19
  393. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  394. package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js +128 -49
  395. package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js.map +1 -1
  396. package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js +170 -31
  397. package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js.map +1 -1
  398. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +21 -19
  399. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  400. package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js +33 -9
  401. package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js.map +1 -1
  402. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +9 -5
  403. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js.map +1 -1
  404. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js +121 -41
  405. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -1
  406. package/build/dist/Server/Utils/AI/SRE/InvestigationSubjectLock.js +46 -0
  407. package/build/dist/Server/Utils/AI/SRE/InvestigationSubjectLock.js.map +1 -0
  408. package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js +61 -14
  409. package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js.map +1 -1
  410. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js +79 -1
  411. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js.map +1 -1
  412. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js +31 -1
  413. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js.map +1 -1
  414. package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js +21 -0
  415. package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js.map +1 -1
  416. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +13 -0
  417. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  418. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +126 -4
  419. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
  420. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js +136 -0
  421. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubInstallationBinding.js.map +1 -0
  422. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +20 -3
  423. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
  424. package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js +43 -0
  425. package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js.map +1 -1
  426. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +33 -0
  427. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
  428. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +1 -0
  429. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
  430. package/build/dist/Server/Utils/PasswordHash.js +258 -0
  431. package/build/dist/Server/Utils/PasswordHash.js.map +1 -0
  432. package/build/dist/Types/AI/AIRunCodeFixRecommendation.js +18 -0
  433. package/build/dist/Types/AI/AIRunCodeFixRecommendation.js.map +1 -0
  434. package/build/dist/Types/AI/CodeFixTaskContext.js +23 -3
  435. package/build/dist/Types/AI/CodeFixTaskContext.js.map +1 -1
  436. package/build/dist/Types/AI/CodeFixTaskType.js +3 -3
  437. package/build/dist/Types/Database/ColumnLength.js +9 -1
  438. package/build/dist/Types/Database/ColumnLength.js.map +1 -1
  439. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  440. package/build/dist/Types/Database/UnsynchronizedIndex.js +46 -0
  441. package/build/dist/Types/Database/UnsynchronizedIndex.js.map +1 -0
  442. package/build/dist/Types/HashedString.js +96 -5
  443. package/build/dist/Types/HashedString.js.map +1 -1
  444. package/build/dist/Types/Monitor/CriteriaFilter.js +4 -0
  445. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  446. package/build/dist/Types/Monitor/DomainMonitor/DomainLookupMethod.js +24 -0
  447. package/build/dist/Types/Monitor/DomainMonitor/DomainLookupMethod.js.map +1 -0
  448. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +24 -7
  449. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  450. package/build/dist/Types/Monitor/MonitorMetricType.js +7 -0
  451. package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
  452. package/build/dist/Types/Monitor/MonitorStep.js +6 -1
  453. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  454. package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js +15 -0
  455. package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js.map +1 -1
  456. package/build/dist/Types/Monitor/PortMonitor/PortMonitorTimings.js +2 -0
  457. package/build/dist/Types/Monitor/PortMonitor/PortMonitorTimings.js.map +1 -0
  458. package/build/dist/Types/Permission.js +23 -155
  459. package/build/dist/Types/Permission.js.map +1 -1
  460. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +2 -5
  461. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
  462. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +51 -10
  463. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  464. package/build/dist/UI/Components/ErrorBoundary.js +67 -8
  465. package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
  466. package/build/dist/UI/Components/Feed/FeedItem.js +2 -2
  467. package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
  468. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +0 -12
  469. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  470. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +1 -1
  471. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  472. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +12 -2
  473. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
  474. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +193 -65
  475. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  476. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +1 -1
  477. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  478. package/build/dist/UI/Components/Navbar/NavBar.js +3 -1
  479. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  480. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +19 -7
  481. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
  482. package/build/dist/UI/Utils/DownloadFile.js +15 -8
  483. package/build/dist/UI/Utils/DownloadFile.js.map +1 -1
  484. package/build/dist/UI/Utils/ErrorSupportBundle.js +476 -0
  485. package/build/dist/UI/Utils/ErrorSupportBundle.js.map +1 -0
  486. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +18 -8
  487. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  488. package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js +156 -0
  489. package/build/dist/UI/Utils/ModelAPI/UserProjectsModelAPI.js.map +1 -0
  490. package/build/dist/UI/Utils/Project.js +3 -0
  491. package/build/dist/UI/Utils/Project.js.map +1 -1
  492. package/build/dist/UI/Utils/StatusPage.js +4 -4
  493. package/build/dist/UI/Utils/StatusPage.js.map +1 -1
  494. package/build/dist/UI/Utils/UseDashboardGridDnd.js +551 -0
  495. package/build/dist/UI/Utils/UseDashboardGridDnd.js.map +1 -0
  496. package/build/dist/Utils/Dashboard/DashboardViewConfig.js +42 -26
  497. package/build/dist/Utils/Dashboard/DashboardViewConfig.js.map +1 -1
  498. package/build/dist/Utils/Dashboard/GridLayout.js +338 -0
  499. package/build/dist/Utils/Dashboard/GridLayout.js.map +1 -0
  500. package/build/dist/Utils/Metrics/RecordingRuleExpression.js +19 -17
  501. package/build/dist/Utils/Metrics/RecordingRuleExpression.js.map +1 -1
  502. package/build/dist/Utils/Monitor/MonitorMetricType.js +38 -6
  503. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  504. package/build/dist/Utils/StatusPage/GroupNestingLayout.js +46 -0
  505. package/build/dist/Utils/StatusPage/GroupNestingLayout.js.map +1 -1
  506. package/build/dist/Utils/StatusPage/GroupTree.js +79 -34
  507. package/build/dist/Utils/StatusPage/GroupTree.js.map +1 -1
  508. package/build/dist/Utils/StatusPage/ResourceUptime.js +22 -1
  509. package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
  510. package/build/dist/Utils/TeamMembersByProject.js +145 -0
  511. package/build/dist/Utils/TeamMembersByProject.js.map +1 -0
  512. package/build/dist/Utils/Telemetry/SavedViewTimeRange.js +79 -0
  513. package/build/dist/Utils/Telemetry/SavedViewTimeRange.js.map +1 -0
  514. package/build/dist/Utils/Telemetry/TelemetryQueryTimeRange.js +236 -0
  515. package/build/dist/Utils/Telemetry/TelemetryQueryTimeRange.js.map +1 -0
  516. package/package.json +1 -1
@@ -0,0 +1,756 @@
1
+ import MoreMenu, {
2
+ ComponentProps as MoreMenuProps,
3
+ } from "../../../UI/Components/MoreMenu/MoreMenu";
4
+ import MoreMenuItem from "../../../UI/Components/MoreMenu/MoreMenuItem";
5
+ import MoreMenuSection from "../../../UI/Components/MoreMenu/MoreMenuSection";
6
+ import "@testing-library/jest-dom";
7
+ import {
8
+ cleanup,
9
+ fireEvent,
10
+ render,
11
+ screen,
12
+ waitFor,
13
+ } from "@testing-library/react";
14
+ import userEvent from "@testing-library/user-event";
15
+ import { afterEach, beforeEach, describe, jest, test } from "@jest/globals";
16
+ import React, { act, ReactElement } from "react";
17
+
18
+ jest.setTimeout(30000);
19
+
20
+ jest.mock("react-i18next", () => {
21
+ return {
22
+ useTranslation: () => {
23
+ return {
24
+ t: (key: string, options?: { defaultValue?: string }): string => {
25
+ return options?.defaultValue ?? key;
26
+ },
27
+ };
28
+ },
29
+ };
30
+ });
31
+
32
+ type TriggerVariant =
33
+ | "default"
34
+ | "custom"
35
+ | "custom-native"
36
+ | "custom-unstyled";
37
+ type OpenMethod = "click" | "Enter" | "Space";
38
+
39
+ interface MenuHarness {
40
+ trigger: HTMLElement;
41
+ outsideButton: HTMLButtonElement;
42
+ onSelect: Array<SelectionMock>;
43
+ }
44
+
45
+ type MoreMenuTestProps = Omit<MoreMenuProps, "children">;
46
+ type SelectionMock = ReturnType<typeof jest.fn>;
47
+ type UserEventController = ReturnType<typeof userEvent.setup>;
48
+
49
+ async function userClick(
50
+ user: UserEventController,
51
+ element: Element,
52
+ ): Promise<void> {
53
+ await act(async () => {
54
+ await user.click(element);
55
+ });
56
+ }
57
+
58
+ async function userKeyboard(
59
+ user: UserEventController,
60
+ keys: string,
61
+ ): Promise<void> {
62
+ await act(async () => {
63
+ await user.keyboard(keys);
64
+ });
65
+ }
66
+
67
+ async function userTab(
68
+ user: UserEventController,
69
+ shift: boolean,
70
+ ): Promise<void> {
71
+ await act(async () => {
72
+ await user.tab({ shift: shift });
73
+ });
74
+ }
75
+
76
+ const ACTION_LABELS: ReadonlyArray<string> = [
77
+ "First action",
78
+ "Second action",
79
+ "Third action",
80
+ ];
81
+
82
+ let originalRequestAnimationFrame:
83
+ | typeof window.requestAnimationFrame
84
+ | undefined;
85
+
86
+ beforeEach(() => {
87
+ originalRequestAnimationFrame = window.requestAnimationFrame;
88
+
89
+ /*
90
+ * MoreMenu restores focus on the next animation frame. JSDOM does not
91
+ * consistently expose requestAnimationFrame, so model that browser turn
92
+ * with a zero-delay timer and let waitFor observe the resulting focus.
93
+ */
94
+ Object.defineProperty(window, "requestAnimationFrame", {
95
+ configurable: true,
96
+ writable: true,
97
+ value: (callback: FrameRequestCallback): number => {
98
+ return window.setTimeout(() => {
99
+ callback(0);
100
+ }, 0);
101
+ },
102
+ });
103
+ });
104
+
105
+ afterEach(() => {
106
+ cleanup();
107
+
108
+ if (originalRequestAnimationFrame) {
109
+ Object.defineProperty(window, "requestAnimationFrame", {
110
+ configurable: true,
111
+ writable: true,
112
+ value: originalRequestAnimationFrame,
113
+ });
114
+ } else {
115
+ Reflect.deleteProperty(window, "requestAnimationFrame");
116
+ }
117
+
118
+ jest.clearAllMocks();
119
+ });
120
+
121
+ function renderMenu(
122
+ triggerVariant: TriggerVariant = "default",
123
+ props: MoreMenuTestProps = {},
124
+ useSection: boolean = false,
125
+ ): MenuHarness {
126
+ const onSelect: Array<SelectionMock> = ACTION_LABELS.map(() => {
127
+ return jest.fn();
128
+ });
129
+
130
+ const menuItems: Array<ReactElement> = ACTION_LABELS.map(
131
+ (label: string, index: number): ReactElement => {
132
+ return (
133
+ <MoreMenuItem
134
+ key={label}
135
+ text={label}
136
+ onClick={() => {
137
+ onSelect[index]!();
138
+ }}
139
+ />
140
+ );
141
+ },
142
+ );
143
+
144
+ const children: Array<ReactElement> = useSection
145
+ ? [
146
+ <MoreMenuSection key="state-changes" title="State changes">
147
+ {menuItems}
148
+ </MoreMenuSection>,
149
+ ]
150
+ : menuItems;
151
+
152
+ const menuProps: MoreMenuTestProps = {
153
+ text: "Event actions",
154
+ ...props,
155
+ };
156
+
157
+ if (triggerVariant === "custom") {
158
+ menuProps.elementToBeShownInsteadOfButton = (
159
+ <span aria-hidden="true">•••</span>
160
+ );
161
+ menuProps.triggerClassName = props.triggerClassName || "custom-trigger";
162
+ } else if (triggerVariant === "custom-native") {
163
+ menuProps.elementToBeShownInsteadOfButton = (
164
+ <button type="button" aria-label="Event actions">
165
+ •••
166
+ </button>
167
+ );
168
+ } else if (triggerVariant === "custom-unstyled") {
169
+ menuProps.elementToBeShownInsteadOfButton = (
170
+ <div aria-hidden="true">•••</div>
171
+ );
172
+ menuProps.triggerClassName = undefined;
173
+ }
174
+
175
+ render(
176
+ <div>
177
+ <MoreMenu {...menuProps}>{children}</MoreMenu>
178
+ <button type="button">Outside target</button>
179
+ </div>,
180
+ );
181
+
182
+ return {
183
+ trigger: screen.getByRole("button", {
184
+ name: menuProps.text || "More options",
185
+ }),
186
+ outsideButton: screen.getByRole("button", {
187
+ name: "Outside target",
188
+ }) as HTMLButtonElement,
189
+ onSelect,
190
+ };
191
+ }
192
+
193
+ async function openMenu(
194
+ trigger: HTMLElement,
195
+ method: OpenMethod = "click",
196
+ ): Promise<void> {
197
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
198
+
199
+ if (method === "click") {
200
+ await userClick(user, trigger);
201
+ } else {
202
+ trigger.focus();
203
+ await userKeyboard(user, method === "Enter" ? "{Enter}" : "[Space]");
204
+ }
205
+
206
+ expect(screen.getByRole("menu")).toBeInTheDocument();
207
+ }
208
+
209
+ describe("MoreMenu trigger accessibility", () => {
210
+ test("the default trigger uses the supplied accessible name", () => {
211
+ const { trigger } = renderMenu("default");
212
+
213
+ expect(trigger).toHaveAccessibleName("Event actions");
214
+ expect(trigger.tagName).toBe("BUTTON");
215
+ expect(trigger).toHaveAttribute("aria-haspopup", "menu");
216
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
217
+ });
218
+
219
+ test("the default trigger falls back to 'More options' when text is omitted", () => {
220
+ const { trigger } = renderMenu("default", { text: undefined });
221
+
222
+ expect(trigger).toHaveAccessibleName("More options");
223
+ });
224
+
225
+ test("a custom trigger exposes the supplied accessible name and menu semantics", () => {
226
+ const { trigger } = renderMenu("custom");
227
+
228
+ expect(trigger).toHaveAccessibleName("Event actions");
229
+ expect(trigger.tagName).toBe("BUTTON");
230
+ expect(trigger).toHaveAttribute("aria-haspopup", "menu");
231
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
232
+ });
233
+
234
+ test("a custom trigger falls back to 'More options' when text is omitted", () => {
235
+ renderMenu("custom", { text: undefined });
236
+
237
+ expect(
238
+ screen.getByRole("button", { name: "More options" }),
239
+ ).toBeInTheDocument();
240
+ });
241
+
242
+ test("triggerClassName is applied to the focusable custom trigger", () => {
243
+ const { trigger } = renderMenu("custom", {
244
+ triggerClassName: "event-actions-trigger h-9",
245
+ });
246
+
247
+ expect(trigger).toHaveClass("event-actions-trigger", "h-9");
248
+ });
249
+
250
+ test("an unstyled non-interactive custom element keeps a keyboard-operable wrapper", () => {
251
+ const { trigger } = renderMenu("custom-unstyled");
252
+
253
+ expect(trigger.tagName).toBe("DIV");
254
+ expect(trigger).toHaveAttribute("role", "button");
255
+ expect(trigger).toHaveAttribute("tabindex", "0");
256
+ expect(trigger).toHaveAttribute("aria-haspopup", "menu");
257
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
258
+ });
259
+
260
+ test("an existing native custom button is enhanced without a nested trigger", () => {
261
+ const { trigger } = renderMenu("custom-native");
262
+
263
+ expect(trigger.tagName).toBe("BUTTON");
264
+ expect(trigger.parentElement).not.toHaveAttribute("role", "button");
265
+ expect(
266
+ screen.getAllByRole("button", { name: "Event actions" }),
267
+ ).toHaveLength(1);
268
+ expect(trigger).toHaveAttribute("aria-haspopup", "menu");
269
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
270
+ });
271
+ });
272
+
273
+ describe.each<{
274
+ label: string;
275
+ variant: TriggerVariant;
276
+ }>([
277
+ { label: "default trigger", variant: "default" },
278
+ { label: "custom trigger", variant: "custom" },
279
+ { label: "native custom trigger", variant: "custom-native" },
280
+ { label: "unstyled custom trigger", variant: "custom-unstyled" },
281
+ ])(
282
+ "MoreMenu opening with the $label",
283
+ ({ variant }: { variant: TriggerVariant }) => {
284
+ test.each(["click", "Enter", "Space"])(
285
+ "%s opens the menu",
286
+ async (method: string) => {
287
+ const { trigger } = renderMenu(variant);
288
+
289
+ await openMenu(trigger, method as OpenMethod);
290
+
291
+ expect(trigger).toHaveAttribute("aria-expanded", "true");
292
+ },
293
+ );
294
+
295
+ test("aria-expanded and aria-controls track the open menu", async () => {
296
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
297
+ const { trigger } = renderMenu(variant);
298
+
299
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
300
+ expect(trigger).not.toHaveAttribute("aria-controls");
301
+
302
+ await userClick(user, trigger);
303
+
304
+ const menu: HTMLElement = screen.getByRole("menu");
305
+
306
+ expect(trigger).toHaveAttribute("aria-expanded", "true");
307
+ expect(trigger).toHaveAttribute("aria-controls", menu.id);
308
+ expect(menu).toHaveAttribute("aria-labelledby", trigger.id);
309
+
310
+ await userClick(user, trigger);
311
+
312
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
313
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
314
+ expect(trigger).not.toHaveAttribute("aria-controls");
315
+ });
316
+ },
317
+ );
318
+
319
+ describe("MoreMenu keyboard navigation", () => {
320
+ test("opening focuses the first item and ArrowDown/ArrowUp move and wrap focus", async () => {
321
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
322
+ const { trigger } = renderMenu();
323
+
324
+ await openMenu(trigger);
325
+
326
+ const items: Array<HTMLElement> = screen.getAllByRole("menuitem");
327
+
328
+ await waitFor(() => {
329
+ expect(items[0]).toHaveFocus();
330
+ });
331
+
332
+ await userKeyboard(user, "{ArrowDown}");
333
+ expect(items[1]).toHaveFocus();
334
+
335
+ await userKeyboard(user, "{ArrowDown}");
336
+ expect(items[2]).toHaveFocus();
337
+
338
+ await userKeyboard(user, "{ArrowDown}");
339
+ expect(items[0]).toHaveFocus();
340
+
341
+ await userKeyboard(user, "{ArrowUp}");
342
+ expect(items[2]).toHaveFocus();
343
+ });
344
+
345
+ test("Home and End jump to the first and last items", async () => {
346
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
347
+ const { trigger } = renderMenu();
348
+
349
+ await openMenu(trigger);
350
+
351
+ const items: Array<HTMLElement> = screen.getAllByRole("menuitem");
352
+
353
+ await waitFor(() => {
354
+ expect(items[0]).toHaveFocus();
355
+ });
356
+
357
+ await userKeyboard(user, "{End}");
358
+ expect(items[2]).toHaveFocus();
359
+
360
+ await userKeyboard(user, "{Home}");
361
+ expect(items[0]).toHaveFocus();
362
+ });
363
+
364
+ test("section labels and dividers are skipped while nested actions receive roving focus", async () => {
365
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
366
+ const { trigger } = renderMenu("default", {}, true);
367
+
368
+ await openMenu(trigger);
369
+
370
+ const items: Array<HTMLElement> = screen.getAllByRole("menuitem");
371
+
372
+ expect(items).toHaveLength(ACTION_LABELS.length);
373
+ expect(
374
+ items.map((item: HTMLElement) => {
375
+ return item.textContent;
376
+ }),
377
+ ).toEqual(ACTION_LABELS);
378
+ items.forEach((item: HTMLElement) => {
379
+ expect(item.querySelector('[role="menuitem"]')).toBeNull();
380
+ });
381
+ expect(screen.getByText("STATE CHANGES")).not.toHaveAttribute(
382
+ "role",
383
+ "menuitem",
384
+ );
385
+
386
+ await waitFor(() => {
387
+ expect(items[0]).toHaveFocus();
388
+ });
389
+
390
+ await userKeyboard(user, "{ArrowDown}");
391
+ expect(items[1]).toHaveFocus();
392
+
393
+ await userKeyboard(user, "{End}");
394
+ expect(items[2]).toHaveFocus();
395
+
396
+ await userKeyboard(user, "{ArrowDown}");
397
+ expect(items[0]).toHaveFocus();
398
+ });
399
+
400
+ test("an established custom child button remains keyboard reachable and dismisses the menu", async () => {
401
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
402
+ const customAction: SelectionMock = jest.fn();
403
+
404
+ render(
405
+ <MoreMenu text="Custom children">
406
+ {[
407
+ <div key="custom-wrapper" className="px-2 py-1">
408
+ <button type="button" onClick={customAction}>
409
+ Custom child action
410
+ </button>
411
+ </div>,
412
+ ]}
413
+ </MoreMenu>,
414
+ );
415
+
416
+ const trigger: HTMLElement = screen.getByRole("button", {
417
+ name: "Custom children",
418
+ });
419
+ await openMenu(trigger);
420
+
421
+ const customItem: HTMLElement = await screen.findByRole("menuitem", {
422
+ name: "Custom child action",
423
+ });
424
+ await waitFor(() => {
425
+ expect(customItem).toHaveFocus();
426
+ });
427
+
428
+ await userKeyboard(user, "{Enter}");
429
+
430
+ expect(customAction).toHaveBeenCalledTimes(1);
431
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
432
+ });
433
+
434
+ test("navigation keys prevent their native scrolling behavior", async () => {
435
+ const { trigger } = renderMenu();
436
+
437
+ await openMenu(trigger);
438
+
439
+ const items: Array<HTMLElement> = screen.getAllByRole("menuitem");
440
+
441
+ await waitFor(() => {
442
+ expect(items[0]).toHaveFocus();
443
+ });
444
+
445
+ expect(fireEvent.keyDown(items[0]!, { key: "ArrowDown" })).toBe(false);
446
+ expect(items[1]).toHaveFocus();
447
+
448
+ expect(fireEvent.keyDown(items[1]!, { key: "End" })).toBe(false);
449
+ expect(items[2]).toHaveFocus();
450
+
451
+ expect(fireEvent.keyDown(items[2]!, { key: "Home" })).toBe(false);
452
+ expect(items[0]).toHaveFocus();
453
+ });
454
+
455
+ test("Escape closes the menu and restores focus to the trigger", async () => {
456
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
457
+ const { trigger } = renderMenu();
458
+
459
+ await openMenu(trigger);
460
+ await waitFor(() => {
461
+ expect(screen.getAllByRole("menuitem")[0]).toHaveFocus();
462
+ });
463
+
464
+ await userKeyboard(user, "{Escape}");
465
+
466
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
467
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
468
+ await waitFor(() => {
469
+ expect(trigger).toHaveFocus();
470
+ });
471
+ });
472
+
473
+ test("Escape closes an empty loading menu and restores focus", async () => {
474
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
475
+
476
+ render(
477
+ <MoreMenu text="Loading actions">
478
+ {[<div key="loader">Loading…</div>]}
479
+ </MoreMenu>,
480
+ );
481
+
482
+ const trigger: HTMLElement = screen.getByRole("button", {
483
+ name: "Loading actions",
484
+ });
485
+
486
+ await userClick(user, trigger);
487
+ expect(screen.getByRole("menu")).toBeInTheDocument();
488
+ expect(screen.queryByRole("menuitem")).not.toBeInTheDocument();
489
+
490
+ await userKeyboard(user, "{Escape}");
491
+
492
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
493
+ await waitFor(() => {
494
+ expect(trigger).toHaveFocus();
495
+ });
496
+ });
497
+
498
+ test("items that replace an open loading state join roving focus immediately", async () => {
499
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
500
+ const firstAction: SelectionMock = jest.fn();
501
+ const secondAction: SelectionMock = jest.fn();
502
+ const { rerender } = render(
503
+ <MoreMenu text="Async actions">
504
+ {[<div key="loader">Loading…</div>]}
505
+ </MoreMenu>,
506
+ );
507
+
508
+ const trigger: HTMLElement = screen.getByRole("button", {
509
+ name: "Async actions",
510
+ });
511
+ await userClick(user, trigger);
512
+
513
+ rerender(
514
+ <MoreMenu text="Async actions">
515
+ {[
516
+ <MoreMenuItem
517
+ key="first"
518
+ text="First loaded action"
519
+ onClick={firstAction}
520
+ />,
521
+ <MoreMenuItem
522
+ key="second"
523
+ text="Second loaded action"
524
+ onClick={secondAction}
525
+ />,
526
+ ]}
527
+ </MoreMenu>,
528
+ );
529
+
530
+ const loadedItems: Array<HTMLElement> =
531
+ await screen.findAllByRole("menuitem");
532
+ await waitFor(() => {
533
+ expect(loadedItems[0]).toHaveFocus();
534
+ });
535
+
536
+ await userKeyboard(user, "{ArrowDown}{Enter}");
537
+
538
+ expect(firstAction).not.toHaveBeenCalled();
539
+ expect(secondAction).toHaveBeenCalledTimes(1);
540
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
541
+ });
542
+
543
+ test.each([
544
+ { direction: "forward", shift: false },
545
+ { direction: "backward", shift: true },
546
+ ])(
547
+ "Tab $direction dismisses the menu and follows the native focus order",
548
+ async ({ shift }: { shift: boolean }) => {
549
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
550
+ const { trigger, outsideButton } = renderMenu();
551
+
552
+ await openMenu(trigger);
553
+ await waitFor(() => {
554
+ expect(screen.getAllByRole("menuitem")[0]).toHaveFocus();
555
+ });
556
+
557
+ await userTab(user, shift);
558
+
559
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
560
+ expect(shift ? trigger : outsideButton).toHaveFocus();
561
+ },
562
+ );
563
+
564
+ test("disabled items are skipped by roving focus and cannot be activated", async () => {
565
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
566
+ const disabledAction: SelectionMock = jest.fn();
567
+ const enabledAction: SelectionMock = jest.fn();
568
+
569
+ render(
570
+ <MoreMenu text="Mixed actions">
571
+ {[
572
+ <MoreMenuItem
573
+ key="disabled"
574
+ text="Disabled action"
575
+ isDisabled={true}
576
+ onClick={disabledAction}
577
+ />,
578
+ <MoreMenuItem
579
+ key="enabled"
580
+ text="Enabled action"
581
+ onClick={enabledAction}
582
+ />,
583
+ ]}
584
+ </MoreMenu>,
585
+ );
586
+
587
+ const trigger: HTMLElement = screen.getByRole("button", {
588
+ name: "Mixed actions",
589
+ });
590
+ await openMenu(trigger);
591
+
592
+ const disabledItem: HTMLElement = screen.getByRole("menuitem", {
593
+ name: "Disabled action",
594
+ });
595
+ const enabledItem: HTMLElement = screen.getByRole("menuitem", {
596
+ name: "Enabled action",
597
+ });
598
+
599
+ expect(disabledItem).toBeDisabled();
600
+ expect(disabledItem).toHaveAttribute("tabindex", "-1");
601
+ await waitFor(() => {
602
+ expect(enabledItem).toHaveFocus();
603
+ });
604
+
605
+ fireEvent.click(disabledItem);
606
+ expect(disabledAction).not.toHaveBeenCalled();
607
+ expect(screen.getByRole("menu")).toBeInTheDocument();
608
+ expect(enabledItem).toHaveFocus();
609
+
610
+ await userKeyboard(user, "{Enter}");
611
+ expect(enabledAction).toHaveBeenCalledTimes(1);
612
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
613
+ });
614
+ });
615
+
616
+ describe("MoreMenu selection and dismissal", () => {
617
+ test("clicking an item runs its action exactly once and closes the menu", async () => {
618
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
619
+ const { trigger, onSelect } = renderMenu();
620
+
621
+ await openMenu(trigger);
622
+ await userClick(user, screen.getByText("Second action"));
623
+
624
+ expect(onSelect[0]).not.toHaveBeenCalled();
625
+ expect(onSelect[1]).toHaveBeenCalledTimes(1);
626
+ expect(onSelect[2]).not.toHaveBeenCalled();
627
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
628
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
629
+ });
630
+
631
+ test.each([
632
+ { key: "Enter", keySequence: "{Enter}", useSection: false },
633
+ { key: "Space", keySequence: "[Space]", useSection: true },
634
+ ])(
635
+ "$key invokes the exact focused action once and closes the menu",
636
+ async ({
637
+ keySequence,
638
+ useSection,
639
+ }: {
640
+ keySequence: string;
641
+ useSection: boolean;
642
+ }) => {
643
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
644
+ const { trigger, onSelect } = renderMenu("default", {}, useSection);
645
+
646
+ await openMenu(trigger);
647
+ await userKeyboard(user, "{ArrowDown}");
648
+ expect(screen.getAllByRole("menuitem")[1]).toHaveFocus();
649
+
650
+ await userKeyboard(user, keySequence);
651
+
652
+ expect(onSelect[0]).not.toHaveBeenCalled();
653
+ expect(onSelect[1]).toHaveBeenCalledTimes(1);
654
+ expect(onSelect[2]).not.toHaveBeenCalled();
655
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
656
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
657
+ },
658
+ );
659
+
660
+ test("an outside click closes the menu without selecting or stealing focus", async () => {
661
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
662
+ const { trigger, outsideButton, onSelect } = renderMenu();
663
+
664
+ await openMenu(trigger);
665
+ await userClick(user, outsideButton);
666
+
667
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
668
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
669
+ expect(outsideButton).toHaveFocus();
670
+ onSelect.forEach((callback: SelectionMock) => {
671
+ expect(callback).not.toHaveBeenCalled();
672
+ });
673
+ });
674
+ });
675
+
676
+ describe("MoreMenu disabled contract", () => {
677
+ test("disabling an open menu closes it immediately", async () => {
678
+ const action: SelectionMock = jest.fn();
679
+ const children: Array<ReactElement> = [
680
+ <MoreMenuItem key="action" text="Action" onClick={action} />,
681
+ ];
682
+ const { rerender } = render(
683
+ <MoreMenu text="Dynamic actions">{children}</MoreMenu>,
684
+ );
685
+
686
+ const trigger: HTMLElement = screen.getByRole("button", {
687
+ name: "Dynamic actions",
688
+ });
689
+ await openMenu(trigger);
690
+
691
+ rerender(
692
+ <MoreMenu text="Dynamic actions" isDisabled={true}>
693
+ {children}
694
+ </MoreMenu>,
695
+ );
696
+
697
+ await waitFor(() => {
698
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
699
+ });
700
+ expect(trigger).toBeDisabled();
701
+ expect(action).not.toHaveBeenCalled();
702
+ });
703
+
704
+ test("the disabled default trigger is a disabled native button and cannot open", async () => {
705
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
706
+ const { trigger } = renderMenu("default", { isDisabled: true });
707
+
708
+ expect(trigger).toBeDisabled();
709
+ expect(trigger).toHaveAttribute("aria-disabled", "true");
710
+
711
+ await userClick(user, trigger);
712
+
713
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
714
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
715
+ });
716
+
717
+ test.each([
718
+ { label: "styled custom", variant: "custom" as TriggerVariant },
719
+ {
720
+ label: "existing native custom",
721
+ variant: "custom-native" as TriggerVariant,
722
+ },
723
+ ])(
724
+ "the disabled $label trigger ignores click, Enter, and Space",
725
+ async ({ variant }: { variant: TriggerVariant }) => {
726
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
727
+ const { trigger } = renderMenu(variant, { isDisabled: true });
728
+
729
+ expect(trigger).toBeDisabled();
730
+
731
+ await userClick(user, trigger);
732
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
733
+
734
+ trigger.focus();
735
+ await userKeyboard(user, "{Enter}[Space]");
736
+
737
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
738
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
739
+ },
740
+ );
741
+
742
+ test("a disabled unstyled custom trigger is aria-disabled, leaves the tab order, and cannot open", async () => {
743
+ const user: ReturnType<typeof userEvent.setup> = userEvent.setup();
744
+ const { trigger } = renderMenu("custom-unstyled", { isDisabled: true });
745
+
746
+ expect(trigger).toHaveAttribute("aria-disabled", "true");
747
+ expect(trigger).toHaveAttribute("tabindex", "-1");
748
+
749
+ await userClick(user, trigger);
750
+ fireEvent.keyDown(trigger, { key: "Enter" });
751
+ fireEvent.keyDown(trigger, { key: " " });
752
+
753
+ expect(screen.queryByRole("menu")).not.toBeInTheDocument();
754
+ expect(trigger).toHaveAttribute("aria-expanded", "false");
755
+ });
756
+ });