@oneuptime/common 12.0.13 → 12.0.14

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 (376) hide show
  1. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +14 -0
  2. package/Models/DatabaseModels/UserCall.ts +94 -3
  3. package/Models/DatabaseModels/UserEmail.ts +94 -3
  4. package/Models/DatabaseModels/UserIncomingCallNumber.ts +94 -3
  5. package/Models/DatabaseModels/UserPush.ts +37 -0
  6. package/Models/DatabaseModels/UserSMS.ts +94 -3
  7. package/Models/DatabaseModels/UserWhatsApp.ts +93 -2
  8. package/Server/API/BaseAPI.ts +79 -14
  9. package/Server/API/UserCallAPI.ts +53 -45
  10. package/Server/API/UserEmailAPI.ts +53 -44
  11. package/Server/API/UserIncomingCallNumberAPI.ts +48 -38
  12. package/Server/API/UserNotificationMethodAdminAPI.ts +481 -0
  13. package/Server/API/UserPushAPI.ts +126 -2
  14. package/Server/API/UserSmsAPI.ts +53 -44
  15. package/Server/API/UserWhatsAppAPI.ts +53 -50
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.ts +154 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.ts +17 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.ts +45 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  20. package/Server/Middleware/VerificationCodeRateLimit.ts +586 -0
  21. package/Server/Services/AnalyticsDatabaseService.ts +169 -0
  22. package/Server/Services/DatabaseService.ts +101 -0
  23. package/Server/Services/LogAggregationService.ts +5 -0
  24. package/Server/Services/PushNotificationService.ts +127 -12
  25. package/Server/Services/TeamMemberService.ts +53 -13
  26. package/Server/Services/UserCallService.ts +55 -24
  27. package/Server/Services/UserEmailService.ts +83 -17
  28. package/Server/Services/UserIncomingCallNumberService.ts +49 -85
  29. package/Server/Services/UserNotificationMethodAdminService.ts +1527 -0
  30. package/Server/Services/UserNotificationRuleService.ts +26 -0
  31. package/Server/Services/UserPushService.ts +89 -0
  32. package/Server/Services/UserSmsService.ts +50 -17
  33. package/Server/Services/UserWhatsAppService.ts +48 -15
  34. package/Server/Types/Database/QueryHelper.ts +30 -0
  35. package/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.ts +59 -0
  36. package/Server/Utils/ChannelVerification.ts +510 -0
  37. package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +13 -16
  38. package/Server/Utils/Monitor/MonitorAlert.ts +24 -21
  39. package/Server/Utils/Monitor/MonitorIncident.ts +21 -18
  40. package/Server/Utils/Monitor/MonitorResourceContext.ts +107 -0
  41. package/Server/Utils/Monitor/MonitorStepResourceIdentity.ts +442 -0
  42. package/Server/Utils/Monitor/SeriesResourceLabels.ts +3 -3
  43. package/Server/Utils/Monitor/SeriesResourceLinker.ts +96 -28
  44. package/Server/Utils/TotpAuth.ts +137 -9
  45. package/Server/Utils/UserRegistrationToken.ts +182 -0
  46. package/Server/Utils/VerificationCode.ts +134 -0
  47. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +173 -94
  48. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +1151 -0
  49. package/Tests/App/Dashboard/AdminUserOnCallPages.test.tsx +905 -0
  50. package/Tests/App/Dashboard/DashboardCommandPalette.test.tsx +338 -0
  51. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +1 -1
  52. package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +28 -0
  53. package/Tests/Models/UserPushCriticalAlertColumn.test.ts +172 -0
  54. package/Tests/Server/API/BaseAPIGetListParallel.test.ts +519 -0
  55. package/Tests/Server/API/NotificationChannelVerificationAPI.test.ts +543 -0
  56. package/Tests/Server/API/UserIncomingCallNumberApi.test.ts +108 -18
  57. package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +963 -0
  58. package/Tests/Server/API/UserPushCriticalAlertsApi.test.ts +114 -0
  59. package/Tests/Server/API/UserSmsApi.test.ts +107 -14
  60. package/Tests/Server/API/UserTotpAuthAPI.test.ts +427 -0
  61. package/Tests/Server/Infrastructure/Postgres/FixTotpOtpUrlAlgorithmMigration.test.ts +315 -0
  62. package/Tests/Server/Middleware/VerificationCodeRateLimit.test.ts +775 -0
  63. package/Tests/Server/Services/AddNetworkDeviceReachabilityColumnsMigration.test.ts +10 -62
  64. package/Tests/Server/Services/AnalyticsDatabasePaginationStability.test.ts +33 -13
  65. package/Tests/Server/Services/AnalyticsDatabaseSortKeyBoundary.test.ts +658 -0
  66. package/Tests/Server/Services/CriticalOnCallAlertDelivery.test.ts +458 -0
  67. package/Tests/Server/Services/CriticalPushAlertPayload.test.ts +301 -0
  68. package/Tests/Server/Services/DatabaseServiceAtomicIncrement.test.ts +143 -0
  69. package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +1 -0
  70. package/Tests/Server/Services/LogAggregationScanMemory.test.ts +421 -0
  71. package/Tests/Server/Services/LogAggregationService.test.ts +2 -2
  72. package/Tests/Server/Services/NotificationChannelCodeIssuance.test.ts +401 -0
  73. package/Tests/Server/Services/PushNotificationDeliveryOptions.test.ts +204 -0
  74. package/Tests/Server/Services/TeamMemberAutoAcceptInvitation.test.ts +21 -1
  75. package/Tests/Server/Services/TeamMemberInviteRegistrationToken.test.ts +290 -0
  76. package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +948 -0
  77. package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +1 -0
  78. package/Tests/Server/Services/UserPushCriticalAlertToggle.test.ts +255 -0
  79. package/Tests/Server/Services/UserTotpAuthEnrolment.test.ts +218 -0
  80. package/Tests/Server/TestingUtils/AuthenticatorApp.ts +248 -0
  81. package/Tests/Server/Types/Database/Permissions/NotificationChannelVerificationColumns.test.ts +135 -0
  82. package/Tests/Server/Types/Database/Permissions/OnCallScheduleRelationSelect.test.ts +504 -0
  83. package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAnyOf.test.ts +91 -0
  84. package/Tests/Server/Types/Workflow/Components/JsonToText.test.ts +384 -0
  85. package/Tests/Server/Types/Workflow/Components/MergeJson.test.ts +403 -0
  86. package/Tests/Server/Utils/Browser.test.ts +162 -0
  87. package/Tests/Server/Utils/ChannelVerification.test.ts +689 -0
  88. package/Tests/Server/Utils/Express.test.ts +371 -0
  89. package/Tests/Server/Utils/Monitor/Criteria/CustomCodeMonitorCriteria.test.ts +707 -0
  90. package/Tests/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.test.ts +698 -0
  91. package/Tests/Server/Utils/Monitor/Criteria/IncomingEmailBodyCriteria.test.ts +243 -0
  92. package/Tests/Server/Utils/Monitor/Criteria/IncomingRequestBodyCriteria.test.ts +278 -0
  93. package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +160 -20
  94. package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +13 -8
  95. package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +162 -18
  96. package/Tests/Server/Utils/Monitor/MonitorResourceContext.test.ts +349 -0
  97. package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +690 -0
  98. package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +7 -2
  99. package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +92 -18
  100. package/Tests/Server/Utils/PushNotificationUtilCreators.test.ts +267 -0
  101. package/Tests/Server/Utils/Telemetry/OneuptimeLabel.test.ts +439 -0
  102. package/Tests/Server/Utils/TotpAuth.test.ts +719 -0
  103. package/Tests/Server/Utils/UserRegistrationToken.test.ts +322 -0
  104. package/Tests/Server/Utils/VerificationCode.test.ts +296 -0
  105. package/Tests/Types/AutoRemediation/AutoRemediationSuggestionStatus.test.ts +192 -0
  106. package/Tests/Types/Call/CallRequest.test.ts +189 -0
  107. package/Tests/Types/Dashboard/DashboardSize.test.ts +226 -0
  108. package/Tests/Types/Incident/IncidentSlaStatus.test.ts +191 -0
  109. package/Tests/Types/Metrics/MetricDashboardMetricType.test.ts +278 -0
  110. package/Tests/Types/Metrics/MetricPipelineRuleType.test.ts +517 -0
  111. package/Tests/Types/Monitor/IncomingMonitorDefaultCriteria.test.ts +470 -0
  112. package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +6 -4
  113. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +262 -5
  114. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +1 -1
  115. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +1 -1
  116. package/Tests/Types/Monitor/ServiceAlertTemplates.test.ts +905 -0
  117. package/Tests/Types/Service/ServiceLanguage.test.ts +255 -0
  118. package/Tests/Types/TextRandomGeneration.test.ts +211 -0
  119. package/Tests/UI/Components/Button.test.tsx +175 -1
  120. package/Tests/UI/Components/CodeBlockLanguages.test.tsx +236 -0
  121. package/Tests/UI/Components/CommandPalette/CommandPalette.test.tsx +568 -0
  122. package/Tests/UI/Components/CommandPalette/CommandPaletteKeyboard.test.tsx +239 -0
  123. package/Tests/UI/Components/CommandPalette/CommandPaletteProviders.test.tsx +400 -0
  124. package/Tests/UI/Components/CommandPalette/PaletteFilter.test.ts +234 -0
  125. package/Tests/UI/Components/CommandPalette/RecentCommands.test.ts +97 -0
  126. package/Tests/UI/Components/CountModelSideMenuItemGuard.test.tsx +198 -0
  127. package/Tests/UI/Components/FeedItemSafeMode.test.tsx +1 -1
  128. package/Tests/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.test.ts +267 -0
  129. package/Tests/UI/Components/List.test.tsx +9 -2
  130. package/Tests/UI/Components/ListLoadingStates.test.tsx +173 -0
  131. package/Tests/UI/Components/MarkdownLazy.test.tsx +113 -0
  132. package/Tests/UI/Components/MarkdownMermaidRetry.test.tsx +110 -0
  133. package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +8 -0
  134. package/Tests/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.test.ts +692 -0
  135. package/Tests/UI/Components/MoreMenuMotion.test.tsx +118 -0
  136. package/Tests/UI/Components/NavBarCommandKGating.test.tsx +178 -0
  137. package/Tests/UI/Components/OrderedStatesList.test.tsx +19 -2
  138. package/Tests/UI/Components/SideOverMotion.test.tsx +109 -0
  139. package/Tests/UI/Components/Skeleton.test.tsx +71 -0
  140. package/Tests/UI/Components/TableLoadingStates.test.tsx +279 -0
  141. package/Tests/UI/Components/Tabs.test.tsx +24 -0
  142. package/Tests/UI/Components/Toast.test.tsx +130 -7
  143. package/Tests/UI/Components/ToastStacking.test.tsx +198 -0
  144. package/Tests/UI/Utils/Dropdown.test.ts +423 -0
  145. package/Tests/UI/Utils/ModelAPITenantHeader.test.ts +122 -0
  146. package/Tests/UI/Utils/ModelListCache.test.ts +338 -0
  147. package/Tests/UI/Utils/ProjectListCacheInvalidation.test.ts +225 -0
  148. package/Tests/Utils/API.test.ts +16 -2
  149. package/Tests/Utils/Dashboard/Components/DashboardKubernetesResourceListShared.test.ts +347 -0
  150. package/Tests/Utils/Dashboard/Components/DashboardListSharedArgs.test.ts +259 -0
  151. package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +466 -0
  152. package/Tests/Utils/Rum/UrlScrubberSegments.test.ts +362 -0
  153. package/Types/Email/EmailTemplateType.ts +1 -0
  154. package/Types/Monitor/MonitorCriteriaInstance.ts +41 -20
  155. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +131 -8
  156. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +48 -8
  157. package/Types/Monitor/ServiceAlertTemplates.ts +1352 -0
  158. package/Types/Permission.ts +35 -0
  159. package/Types/PushNotification/AndroidNotificationChannel.ts +26 -0
  160. package/Types/PushNotification/PushNotificationMessage.ts +15 -0
  161. package/Types/PushNotification/PushNotificationRequest.ts +8 -16
  162. package/Types/Service/ServiceLanguage.ts +144 -0
  163. package/Types/Text.ts +111 -19
  164. package/UI/Components/Button/Button.tsx +27 -14
  165. package/UI/Components/CodeBlock/CodeBlock.tsx +54 -7
  166. package/UI/Components/CodeBlock/LanguageRegistry.ts +103 -0
  167. package/UI/Components/CommandPalette/CommandPalette.tsx +790 -0
  168. package/UI/Components/CommandPalette/PaletteFilter.ts +196 -0
  169. package/UI/Components/CommandPalette/PaletteRow.tsx +173 -0
  170. package/UI/Components/CommandPalette/RecentCommands.ts +57 -0
  171. package/UI/Components/CommandPalette/Types.ts +49 -0
  172. package/UI/Components/CommandPalette/UseProviderSearch.ts +129 -0
  173. package/UI/Components/Dropdown/Dropdown.tsx +6 -1
  174. package/UI/Components/Feed/FeedItem.tsx +1 -1
  175. package/UI/Components/List/List.tsx +37 -5
  176. package/UI/Components/List/ListSkeleton.tsx +54 -0
  177. package/UI/Components/Markdown.tsx/LazyMarkdownViewer.tsx +6 -1
  178. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +71 -26
  179. package/UI/Components/Modal/Modal.tsx +2 -2
  180. package/UI/Components/ModelTable/BaseModelTable.tsx +1 -1
  181. package/UI/Components/ModelTable/useCustomFieldColumns.ts +11 -5
  182. package/UI/Components/MoreMenu/MoreMenu.tsx +39 -1
  183. package/UI/Components/Navbar/NavBar.tsx +14 -1
  184. package/UI/Components/OrderedStatesList/OrderedStatesList.tsx +50 -5
  185. package/UI/Components/SideMenu/CountModelSideMenuItem.tsx +40 -1
  186. package/UI/Components/SideOver/SideOver.tsx +37 -2
  187. package/UI/Components/Skeleton/Skeleton.tsx +53 -0
  188. package/UI/Components/Table/Table.tsx +74 -44
  189. package/UI/Components/Table/TableSkeletonRows.tsx +141 -0
  190. package/UI/Components/Tabs/Tab.tsx +1 -1
  191. package/UI/Components/Toast/Toast.tsx +118 -53
  192. package/UI/Components/Toast/ToastInit.tsx +82 -32
  193. package/UI/Styles/Theme.css +40 -0
  194. package/UI/Utils/ModelAPI/ModelAPI.ts +20 -10
  195. package/UI/Utils/ModelListCache.ts +188 -0
  196. package/UI/Utils/Project.ts +29 -0
  197. package/Utils/API.ts +6 -1
  198. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +14 -0
  199. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  200. package/build/dist/Models/DatabaseModels/UserCall.js +101 -3
  201. package/build/dist/Models/DatabaseModels/UserCall.js.map +1 -1
  202. package/build/dist/Models/DatabaseModels/UserEmail.js +101 -3
  203. package/build/dist/Models/DatabaseModels/UserEmail.js.map +1 -1
  204. package/build/dist/Models/DatabaseModels/UserIncomingCallNumber.js +101 -3
  205. package/build/dist/Models/DatabaseModels/UserIncomingCallNumber.js.map +1 -1
  206. package/build/dist/Models/DatabaseModels/UserPush.js +38 -0
  207. package/build/dist/Models/DatabaseModels/UserPush.js.map +1 -1
  208. package/build/dist/Models/DatabaseModels/UserSMS.js +101 -3
  209. package/build/dist/Models/DatabaseModels/UserSMS.js.map +1 -1
  210. package/build/dist/Models/DatabaseModels/UserWhatsApp.js +100 -2
  211. package/build/dist/Models/DatabaseModels/UserWhatsApp.js.map +1 -1
  212. package/build/dist/Server/API/BaseAPI.js +69 -13
  213. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  214. package/build/dist/Server/API/UserCallAPI.js +42 -38
  215. package/build/dist/Server/API/UserCallAPI.js.map +1 -1
  216. package/build/dist/Server/API/UserEmailAPI.js +42 -38
  217. package/build/dist/Server/API/UserEmailAPI.js.map +1 -1
  218. package/build/dist/Server/API/UserIncomingCallNumberAPI.js +37 -32
  219. package/build/dist/Server/API/UserIncomingCallNumberAPI.js.map +1 -1
  220. package/build/dist/Server/API/UserNotificationMethodAdminAPI.js +341 -0
  221. package/build/dist/Server/API/UserNotificationMethodAdminAPI.js.map +1 -0
  222. package/build/dist/Server/API/UserPushAPI.js +95 -2
  223. package/build/dist/Server/API/UserPushAPI.js.map +1 -1
  224. package/build/dist/Server/API/UserSmsAPI.js +42 -38
  225. package/build/dist/Server/API/UserSmsAPI.js.map +1 -1
  226. package/build/dist/Server/API/UserWhatsAppAPI.js +42 -40
  227. package/build/dist/Server/API/UserWhatsAppAPI.js.map +1 -1
  228. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.js +89 -0
  229. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787142779538-MigrationName.js.map +1 -0
  230. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.js +12 -0
  231. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787156982416-MigrationName.js.map +1 -0
  232. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.js +40 -0
  233. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787700000000-FixTotpOtpUrlAlgorithm.js.map +1 -0
  234. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  235. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  236. package/build/dist/Server/Middleware/VerificationCodeRateLimit.js +393 -0
  237. package/build/dist/Server/Middleware/VerificationCodeRateLimit.js.map +1 -0
  238. package/build/dist/Server/Services/AnalyticsDatabaseService.js +136 -0
  239. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  240. package/build/dist/Server/Services/DatabaseService.js +74 -0
  241. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  242. package/build/dist/Server/Services/LogAggregationService.js +5 -0
  243. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  244. package/build/dist/Server/Services/PushNotificationService.js +69 -30
  245. package/build/dist/Server/Services/PushNotificationService.js.map +1 -1
  246. package/build/dist/Server/Services/TeamMemberService.js +43 -7
  247. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  248. package/build/dist/Server/Services/UserCallService.js +54 -22
  249. package/build/dist/Server/Services/UserCallService.js.map +1 -1
  250. package/build/dist/Server/Services/UserEmailService.js +78 -17
  251. package/build/dist/Server/Services/UserEmailService.js.map +1 -1
  252. package/build/dist/Server/Services/UserIncomingCallNumberService.js +45 -74
  253. package/build/dist/Server/Services/UserIncomingCallNumberService.js.map +1 -1
  254. package/build/dist/Server/Services/UserNotificationMethodAdminService.js +1117 -0
  255. package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -0
  256. package/build/dist/Server/Services/UserNotificationRuleService.js +20 -0
  257. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  258. package/build/dist/Server/Services/UserPushService.js +77 -0
  259. package/build/dist/Server/Services/UserPushService.js.map +1 -1
  260. package/build/dist/Server/Services/UserSmsService.js +50 -17
  261. package/build/dist/Server/Services/UserSmsService.js.map +1 -1
  262. package/build/dist/Server/Services/UserWhatsAppService.js +48 -15
  263. package/build/dist/Server/Services/UserWhatsAppService.js.map +1 -1
  264. package/build/dist/Server/Types/Database/QueryHelper.js +27 -0
  265. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  266. package/build/dist/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.js +46 -0
  267. package/build/dist/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.js.map +1 -1
  268. package/build/dist/Server/Utils/ChannelVerification.js +348 -0
  269. package/build/dist/Server/Utils/ChannelVerification.js.map +1 -0
  270. package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +13 -12
  271. package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
  272. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +20 -17
  273. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  274. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +17 -14
  275. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  276. package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js +95 -0
  277. package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js.map +1 -0
  278. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js +313 -0
  279. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js.map +1 -0
  280. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +3 -3
  281. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +67 -26
  282. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -1
  283. package/build/dist/Server/Utils/TotpAuth.js +127 -8
  284. package/build/dist/Server/Utils/TotpAuth.js.map +1 -1
  285. package/build/dist/Server/Utils/UserRegistrationToken.js +170 -0
  286. package/build/dist/Server/Utils/UserRegistrationToken.js.map +1 -0
  287. package/build/dist/Server/Utils/VerificationCode.js +120 -0
  288. package/build/dist/Server/Utils/VerificationCode.js.map +1 -0
  289. package/build/dist/Types/Email/EmailTemplateType.js +1 -0
  290. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  291. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +42 -21
  292. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  293. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +77 -5
  294. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
  295. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +18 -6
  296. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
  297. package/build/dist/Types/Monitor/ServiceAlertTemplates.js +1028 -0
  298. package/build/dist/Types/Monitor/ServiceAlertTemplates.js.map +1 -0
  299. package/build/dist/Types/Permission.js +33 -0
  300. package/build/dist/Types/Permission.js.map +1 -1
  301. package/build/dist/Types/PushNotification/AndroidNotificationChannel.js +27 -0
  302. package/build/dist/Types/PushNotification/AndroidNotificationChannel.js.map +1 -0
  303. package/build/dist/Types/Service/ServiceLanguage.js +96 -0
  304. package/build/dist/Types/Service/ServiceLanguage.js.map +1 -0
  305. package/build/dist/Types/Text.js +82 -17
  306. package/build/dist/Types/Text.js.map +1 -1
  307. package/build/dist/UI/Components/Button/Button.js +22 -14
  308. package/build/dist/UI/Components/Button/Button.js.map +1 -1
  309. package/build/dist/UI/Components/CodeBlock/CodeBlock.js +35 -3
  310. package/build/dist/UI/Components/CodeBlock/CodeBlock.js.map +1 -1
  311. package/build/dist/UI/Components/CodeBlock/LanguageRegistry.js +57 -0
  312. package/build/dist/UI/Components/CodeBlock/LanguageRegistry.js.map +1 -0
  313. package/build/dist/UI/Components/CommandPalette/CommandPalette.js +439 -0
  314. package/build/dist/UI/Components/CommandPalette/CommandPalette.js.map +1 -0
  315. package/build/dist/UI/Components/CommandPalette/PaletteFilter.js +135 -0
  316. package/build/dist/UI/Components/CommandPalette/PaletteFilter.js.map +1 -0
  317. package/build/dist/UI/Components/CommandPalette/PaletteRow.js +83 -0
  318. package/build/dist/UI/Components/CommandPalette/PaletteRow.js.map +1 -0
  319. package/build/dist/UI/Components/CommandPalette/RecentCommands.js +47 -0
  320. package/build/dist/UI/Components/CommandPalette/RecentCommands.js.map +1 -0
  321. package/build/dist/UI/Components/CommandPalette/Types.js +2 -0
  322. package/build/dist/UI/Components/CommandPalette/Types.js.map +1 -0
  323. package/build/dist/UI/Components/CommandPalette/UseProviderSearch.js +88 -0
  324. package/build/dist/UI/Components/CommandPalette/UseProviderSearch.js.map +1 -0
  325. package/build/dist/UI/Components/Dropdown/Dropdown.js +6 -1
  326. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  327. package/build/dist/UI/Components/Feed/FeedItem.js +1 -1
  328. package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
  329. package/build/dist/UI/Components/List/List.js +19 -5
  330. package/build/dist/UI/Components/List/List.js.map +1 -1
  331. package/build/dist/UI/Components/List/ListSkeleton.js +21 -0
  332. package/build/dist/UI/Components/List/ListSkeleton.js.map +1 -0
  333. package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js.map +1 -1
  334. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +52 -25
  335. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  336. package/build/dist/UI/Components/Modal/Modal.js +2 -2
  337. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +1 -1
  338. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  339. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +9 -2
  340. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -1
  341. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +32 -1
  342. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  343. package/build/dist/UI/Components/Navbar/NavBar.js +5 -1
  344. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  345. package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js +29 -5
  346. package/build/dist/UI/Components/OrderedStatesList/OrderedStatesList.js.map +1 -1
  347. package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js +23 -1
  348. package/build/dist/UI/Components/SideMenu/CountModelSideMenuItem.js.map +1 -1
  349. package/build/dist/UI/Components/SideOver/SideOver.js +23 -2
  350. package/build/dist/UI/Components/SideOver/SideOver.js.map +1 -1
  351. package/build/dist/UI/Components/Skeleton/Skeleton.js +23 -0
  352. package/build/dist/UI/Components/Skeleton/Skeleton.js.map +1 -0
  353. package/build/dist/UI/Components/Table/Table.js +35 -24
  354. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  355. package/build/dist/UI/Components/Table/TableSkeletonRows.js +52 -0
  356. package/build/dist/UI/Components/Table/TableSkeletonRows.js.map +1 -0
  357. package/build/dist/UI/Components/Tabs/Tab.js +1 -1
  358. package/build/dist/UI/Components/Tabs/Tab.js.map +1 -1
  359. package/build/dist/UI/Components/Toast/Toast.js +77 -23
  360. package/build/dist/UI/Components/Toast/Toast.js.map +1 -1
  361. package/build/dist/UI/Components/Toast/ToastInit.js +37 -15
  362. package/build/dist/UI/Components/Toast/ToastInit.js.map +1 -1
  363. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +18 -9
  364. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  365. package/build/dist/UI/Utils/ModelListCache.js +126 -0
  366. package/build/dist/UI/Utils/ModelListCache.js.map +1 -0
  367. package/build/dist/UI/Utils/Project.js +24 -1
  368. package/build/dist/UI/Utils/Project.js.map +1 -1
  369. package/build/dist/Utils/API.js +6 -1
  370. package/build/dist/Utils/API.js.map +1 -1
  371. package/jest.config.json +1 -1
  372. package/package.json +2 -3
  373. package/Server/Utils/Monitor/MonitorClusterContext.ts +0 -182
  374. package/Tests/__mocks__/otpauth.js +0 -30
  375. package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js +0 -141
  376. package/build/dist/Server/Utils/Monitor/MonitorClusterContext.js.map +0 -1
@@ -0,0 +1,279 @@
1
+ import "@testing-library/jest-dom";
2
+ import { afterEach, describe, expect, jest, test } from "@jest/globals";
3
+ import { cleanup, render, screen } from "@testing-library/react";
4
+ import * as React from "react";
5
+ import Table, { BulkActionProps } from "../../../UI/Components/Table/Table";
6
+ import Columns from "../../../UI/Components/Table/Types/Columns";
7
+ import FieldType from "../../../UI/Components/Types/FieldType";
8
+ import SortOrder from "../../../Types/BaseDatabase/SortOrder";
9
+ import { ButtonStyleType } from "../../../UI/Components/Button/Button";
10
+
11
+ /*
12
+ * The table's four loading-adjacent faces, and the order they win in.
13
+ *
14
+ * A first load has nothing to show, so it shows the SHAPE of what is coming:
15
+ * skeleton rows whose cell structure mirrors the header exactly (drag handle,
16
+ * bulk checkbox, one cell per visible column). A refetch DOES have something
17
+ * to show - the parent never clears `data` while fetching - so the stale rows
18
+ * stay on screen, dimmed and unclickable, instead of being torn down to
19
+ * placeholders. Error and empty only speak once loading has finished; a stale
20
+ * error surfacing mid-refetch over live rows was the bug this priority
21
+ * prevents.
22
+ */
23
+
24
+ jest.mock("react-i18next", () => {
25
+ return {
26
+ useTranslation: () => {
27
+ return {
28
+ t: (key: string, opts?: { defaultValue?: string }): string => {
29
+ return opts?.defaultValue ?? key;
30
+ },
31
+ };
32
+ },
33
+ };
34
+ });
35
+
36
+ interface Row {
37
+ _id?: string | undefined;
38
+ name?: string | undefined;
39
+ status?: string | undefined;
40
+ }
41
+
42
+ const columns: Columns<Row> = [
43
+ { title: "Name", type: FieldType.Text, key: "name" },
44
+ { title: "Status", type: FieldType.Text, key: "status", hideOnMobile: true },
45
+ ];
46
+
47
+ const data: Array<Row> = [
48
+ { _id: "1", name: "Alpha", status: "Up" },
49
+ { _id: "2", name: "Beta", status: "Down" },
50
+ ];
51
+
52
+ const bulkActions: BulkActionProps<Row> = {
53
+ buttons: [
54
+ {
55
+ title: "Archive",
56
+ buttonStyleType: ButtonStyleType.NORMAL,
57
+ onClick: (): Promise<void> => {
58
+ return Promise.resolve();
59
+ },
60
+ },
61
+ ],
62
+ };
63
+
64
+ interface RenderTableOptions {
65
+ data?: Array<Row> | undefined;
66
+ isLoading?: boolean | undefined;
67
+ error?: string | undefined;
68
+ itemsOnPage?: number | undefined;
69
+ enableDragAndDrop?: boolean | undefined;
70
+ withBulkActions?: boolean | undefined;
71
+ noItemsMessage?: string | undefined;
72
+ }
73
+
74
+ type RenderTableFunction = (options: RenderTableOptions) => void;
75
+
76
+ const renderTable: RenderTableFunction = (
77
+ options: RenderTableOptions,
78
+ ): void => {
79
+ render(
80
+ <Table<Row>
81
+ id="test-table"
82
+ data={options.data ?? data}
83
+ columns={columns}
84
+ currentPageNumber={1}
85
+ totalItemsCount={(options.data ?? data).length}
86
+ itemsOnPage={options.itemsOnPage ?? 10}
87
+ error={options.error ?? ""}
88
+ isLoading={options.isLoading ?? false}
89
+ singularLabel="Monitor"
90
+ pluralLabel="Monitors"
91
+ sortOrder={SortOrder.Ascending}
92
+ sortBy={null}
93
+ onSortChanged={() => {}}
94
+ onNavigateToPage={() => {}}
95
+ noItemsMessage={options.noItemsMessage}
96
+ enableDragAndDrop={options.enableDragAndDrop}
97
+ matchBulkSelectedItemByField={options.withBulkActions ? "_id" : undefined}
98
+ bulkActions={options.withBulkActions ? bulkActions : undefined}
99
+ bulkSelectedItems={options.withBulkActions ? [] : undefined}
100
+ />,
101
+ );
102
+ };
103
+
104
+ /*
105
+ * The table decides mobile vs desktop from window.innerWidth at mount
106
+ * (< 768 = mobile). jsdom defaults to 1024, so desktop is the baseline and
107
+ * mobile tests must set the width BEFORE rendering.
108
+ */
109
+ const setViewportWidth: (width: number) => void = (width: number): void => {
110
+ Object.defineProperty(window, "innerWidth", {
111
+ configurable: true,
112
+ writable: true,
113
+ value: width,
114
+ });
115
+ };
116
+
117
+ afterEach(() => {
118
+ cleanup();
119
+ setViewportWidth(1024);
120
+ });
121
+
122
+ describe("first load (nothing to show yet)", () => {
123
+ test("renders skeleton rows instead of a spinner", () => {
124
+ renderTable({ isLoading: true, data: [] });
125
+
126
+ expect(screen.getByTestId("table-skeleton-loader")).toBeInTheDocument();
127
+ expect(screen.queryByTestId("component-loader")).toBeNull();
128
+ });
129
+
130
+ test("announces itself as a polite status with a Loading label", () => {
131
+ renderTable({ isLoading: true, data: [] });
132
+
133
+ const loader: HTMLElement = screen.getByTestId("table-skeleton-loader");
134
+ expect(loader).toHaveAttribute("role", "status");
135
+ expect(loader).toHaveAttribute("aria-live", "polite");
136
+ expect(loader).toHaveTextContent("Loading");
137
+ });
138
+
139
+ test("renders one skeleton row per item on the page", () => {
140
+ renderTable({ isLoading: true, data: [], itemsOnPage: 5 });
141
+
142
+ const loader: HTMLElement = screen.getByTestId("table-skeleton-loader");
143
+ expect(loader.querySelectorAll("tr")).toHaveLength(5);
144
+ });
145
+
146
+ test("caps skeleton rows at 10 even for a 25-row page", () => {
147
+ renderTable({ isLoading: true, data: [], itemsOnPage: 25 });
148
+
149
+ const loader: HTMLElement = screen.getByTestId("table-skeleton-loader");
150
+ expect(loader.querySelectorAll("tr")).toHaveLength(10);
151
+ });
152
+
153
+ test("a plain table gets one skeleton cell per column", () => {
154
+ renderTable({ isLoading: true, data: [] });
155
+
156
+ const loader: HTMLElement = screen.getByTestId("table-skeleton-loader");
157
+ const firstRow: Element = loader.querySelectorAll("tr")[0] as Element;
158
+ expect(firstRow.querySelectorAll("td")).toHaveLength(columns.length);
159
+ });
160
+
161
+ /*
162
+ * The header renders extra leading cells for the drag handle and the bulk
163
+ * checkbox. Skeleton rows must mirror them or every placeholder sits one
164
+ * or two columns to the left of the header it is standing in for.
165
+ */
166
+ test("drag and bulk each add their leading skeleton cell", () => {
167
+ renderTable({
168
+ isLoading: true,
169
+ data: [],
170
+ enableDragAndDrop: true,
171
+ withBulkActions: true,
172
+ });
173
+
174
+ const loader: HTMLElement = screen.getByTestId("table-skeleton-loader");
175
+ const firstRow: Element = loader.querySelectorAll("tr")[0] as Element;
176
+ expect(firstRow.querySelectorAll("td")).toHaveLength(columns.length + 2);
177
+ });
178
+
179
+ test("skeleton cells carry the real rows' padding classes", () => {
180
+ renderTable({ isLoading: true, data: [] });
181
+
182
+ const loader: HTMLElement = screen.getByTestId("table-skeleton-loader");
183
+ const cells: NodeListOf<Element> = loader
184
+ .querySelectorAll("tr")[0]!
185
+ .querySelectorAll("td");
186
+
187
+ // Same classes as TableRow's cells: last cell gets pr-6, the rest pr-3.
188
+ expect(cells[0]).toHaveClass("py-4", "pl-4", "pr-3", "whitespace-nowrap");
189
+ expect(cells[cells.length - 1]).toHaveClass("pr-6");
190
+ });
191
+ });
192
+
193
+ describe("mobile first load", () => {
194
+ test("renders a card-shaped stack, not table markup inside a div", () => {
195
+ setViewportWidth(500);
196
+ renderTable({ isLoading: true, data: [], itemsOnPage: 5 });
197
+
198
+ const loader: HTMLElement = screen.getByTestId("table-skeleton-loader");
199
+ expect(loader.tagName).toBe("DIV");
200
+ /*
201
+ * The old loader emitted a <tbody> into the mobile <div> wrapper -
202
+ * invalid HTML that React happened to tolerate. Nothing table-shaped
203
+ * may remain on mobile.
204
+ */
205
+ expect(document.querySelector("tbody")).toBeNull();
206
+ expect(loader.querySelectorAll(":scope > div")).toHaveLength(5);
207
+ });
208
+
209
+ test("hideOnMobile columns get no placeholder line", () => {
210
+ setViewportWidth(500);
211
+ renderTable({ isLoading: true, data: [], itemsOnPage: 3 });
212
+
213
+ const loader: HTMLElement = screen.getByTestId("table-skeleton-loader");
214
+ const firstCard: Element = loader.querySelectorAll(
215
+ ":scope > div",
216
+ )[0] as Element;
217
+
218
+ // One label/value group per VISIBLE column: Status is hideOnMobile.
219
+ expect(firstCard.querySelectorAll(".flex.flex-col")).toHaveLength(1);
220
+ });
221
+ });
222
+
223
+ describe("refetch with rows already on screen", () => {
224
+ test("keeps the real rows and shows no skeletons", () => {
225
+ renderTable({ isLoading: true });
226
+
227
+ expect(screen.getByText("Alpha")).toBeInTheDocument();
228
+ expect(screen.getByText("Beta")).toBeInTheDocument();
229
+ expect(screen.queryByTestId("table-skeleton-loader")).toBeNull();
230
+ });
231
+
232
+ test("dims the stale rows and blocks pointer events on them", () => {
233
+ renderTable({ isLoading: true });
234
+
235
+ const content: HTMLElement = screen.getByTestId("table-content");
236
+ expect(content).toHaveClass("opacity-60");
237
+ expect(content).toHaveClass("pointer-events-none");
238
+ });
239
+
240
+ test("an idle table is neither dimmed nor blocked", () => {
241
+ renderTable({});
242
+
243
+ const content: HTMLElement = screen.getByTestId("table-content");
244
+ expect(content).not.toHaveClass("opacity-60");
245
+ expect(content).not.toHaveClass("pointer-events-none");
246
+ });
247
+
248
+ /*
249
+ * Loading outranks error: mid-refetch the error prop can only be stale
250
+ * (the parent clears it before every fetch), so surfacing it over live
251
+ * rows would report a failure that is not happening.
252
+ */
253
+ test("a stale error does not interrupt the dimmed rows", () => {
254
+ renderTable({ isLoading: true, error: "stale failure" });
255
+
256
+ expect(screen.getByText("Alpha")).toBeInTheDocument();
257
+ expect(screen.queryByText("stale failure")).toBeNull();
258
+ });
259
+ });
260
+
261
+ describe("after loading finishes", () => {
262
+ test("an error replaces the rows", () => {
263
+ renderTable({ isLoading: false, error: "Something went wrong" });
264
+
265
+ expect(screen.getByText("Something went wrong")).toBeInTheDocument();
266
+ expect(screen.queryByTestId("table-skeleton-loader")).toBeNull();
267
+ });
268
+
269
+ test("an empty result shows the no-items message, not skeletons", () => {
270
+ renderTable({
271
+ isLoading: false,
272
+ data: [],
273
+ noItemsMessage: "No monitors here",
274
+ });
275
+
276
+ expect(screen.getByText("No monitors here")).toBeInTheDocument();
277
+ expect(screen.queryByTestId("table-skeleton-loader")).toBeNull();
278
+ });
279
+ });
@@ -69,6 +69,30 @@ describe("Tabs", () => {
69
69
  expect(getByTestId("tab-tab2")).toHaveClass(activeClass);
70
70
  });
71
71
 
72
+ test("tabs ease colour changes and carry a focus-visible ring", () => {
73
+ const onTabChange: MockFunction = getJestMockFunction();
74
+
75
+ const { getByTestId } = render(
76
+ <Tabs tabs={tabs} onTabChange={onTabChange} />,
77
+ );
78
+
79
+ /*
80
+ * Selected and unselected tabs share the base classes, so asserting one
81
+ * of each covers the whole strip. The ring is focus-visible (not focus:)
82
+ * so mouse clicks do not flash it.
83
+ */
84
+ for (const tabTestId of ["tab-tab1", "tab-tab2"]) {
85
+ expect(getByTestId(tabTestId)).toHaveClass(
86
+ "transition-colors",
87
+ "duration-150",
88
+ "focus:outline-none",
89
+ "focus-visible:ring-2",
90
+ "focus-visible:ring-indigo-500",
91
+ "focus-visible:ring-offset-2",
92
+ );
93
+ }
94
+ });
95
+
72
96
  test("it should handle empty tabs array gracefully", () => {
73
97
  const tabs: Array<Tab> = [];
74
98
  const onTabChange: MockFunction = getJestMockFunction();
@@ -1,19 +1,77 @@
1
1
  import Toast, { ToastType } from "../../../UI/Components/Toast/Toast";
2
2
  import "@testing-library/jest-dom";
3
- import { render, screen } from "@testing-library/react";
3
+ import {
4
+ act,
5
+ fireEvent,
6
+ render,
7
+ screen,
8
+ waitFor,
9
+ } from "@testing-library/react";
4
10
  import userEvent from "@testing-library/user-event";
5
11
  import { UserEvent } from "@testing-library/user-event/dist/types/setup/setup";
6
12
  import * as React from "react";
7
- import { describe, expect, test } from "@jest/globals";
13
+ import { describe, expect, jest, test } from "@jest/globals";
14
+
15
+ /*
16
+ * The toast mounts closed (faded, nudged down) and opens on the next
17
+ * animation frame, so anything asserting the settled look has to let that
18
+ * frame run first — the same choreography Modal.test.tsx flushes.
19
+ */
20
+ type FlushEntranceFrameFunction = () => Promise<void>;
21
+
22
+ const flushEntranceFrame: FlushEntranceFrameFunction =
23
+ async (): Promise<void> => {
24
+ await act(async () => {
25
+ await new Promise<void>((resolve: () => void) => {
26
+ requestAnimationFrame(() => {
27
+ resolve();
28
+ });
29
+ });
30
+ });
31
+ };
8
32
 
9
33
  describe("Test for Toast.tsx", () => {
10
- test("should render the component", () => {
34
+ test("should render the toast card", () => {
11
35
  render(
12
36
  <Toast type={ToastType.SUCCESS} title="Spread" description="Love" />,
13
37
  );
14
- expect(screen.getByTestId("toast")).toHaveClass(
15
- "pointer-events-none fixed z-40 top-0 left-0 right-0 flex items-end px-4 py-6 sm:items-start sm:p-6",
38
+
39
+ /*
40
+ * The card itself, not a fixed strip: positioning now belongs to the ONE
41
+ * region container ToastInit renders (see ToastStacking.test.tsx), so
42
+ * stacked toasts no longer paint on top of each other at top-0.
43
+ */
44
+ const toast: HTMLElement = screen.getByTestId("toast");
45
+
46
+ expect(toast).toHaveClass(
47
+ "pointer-events-auto",
48
+ "w-full",
49
+ "max-w-sm",
50
+ "rounded-lg",
51
+ "bg-white",
52
+ "shadow-lg",
16
53
  );
54
+ expect(toast).not.toHaveClass("fixed");
55
+ });
56
+
57
+ test("enters by fading and lifting in from a closed state", async () => {
58
+ render(
59
+ <Toast type={ToastType.SUCCESS} title="Spread" description="Love" />,
60
+ );
61
+
62
+ const toast: HTMLElement = screen.getByTestId("toast");
63
+
64
+ expect(toast).toHaveClass("opacity-0", "translate-y-2");
65
+
66
+ await flushEntranceFrame();
67
+
68
+ expect(toast).toHaveClass("opacity-100");
69
+ /*
70
+ * A settled toast carries no transform class — a lingering translate
71
+ * would make the card the containing block for position:fixed
72
+ * descendants.
73
+ */
74
+ expect(toast.className).not.toMatch(/(^|\s)(scale|translate)-/);
17
75
  });
18
76
 
19
77
  test("should have close button", () => {
@@ -59,16 +117,81 @@ describe("Test for Toast.tsx", () => {
59
117
  render(<Toast type={ToastType.NORMAL} title="Spread" description="Love" />);
60
118
  expect(screen.getByTestId("toast-icon")).toHaveClass("text-gray-400");
61
119
  });
62
- test("simulates button click and sets the state to flase, closing the toast", async () => {
120
+
121
+ test("clicking close fades the toast out and only then unmounts it", async () => {
63
122
  const user: UserEvent = userEvent.setup();
64
123
 
65
124
  render(
66
125
  <Toast type={ToastType.SUCCESS} title="Spread" description="Love" />,
67
126
  );
68
127
 
128
+ await flushEntranceFrame();
129
+
69
130
  const closeButton: HTMLButtonElement = screen.getByTestId("close-button");
70
131
  await user.click(closeButton);
71
132
 
72
- expect(screen.queryByTestId("toast")).toBeFalsy();
133
+ /*
134
+ * The exit runs as a fade with a delayed unmount, so immediately after
135
+ * the click the card is still mounted, at zero opacity.
136
+ */
137
+ expect(screen.getByTestId("toast")).toHaveClass("opacity-0");
138
+
139
+ await waitFor(() => {
140
+ expect(screen.queryByTestId("toast")).toBeFalsy();
141
+ });
142
+ });
143
+
144
+ /*
145
+ * Owners (ToastInit) may hand the toast a NEW onClose identity on any
146
+ * re-render. The exit timer must not restart because of that — it keys off
147
+ * isLeaving alone and reads the latest onClose through a ref — otherwise a
148
+ * re-render mid-fade would leave an invisible pointer-events-auto card
149
+ * intercepting clicks past the original deadline.
150
+ */
151
+ test("a re-render with a new onClose mid-exit neither restarts the timer nor calls a stale handler", () => {
152
+ jest.useFakeTimers();
153
+
154
+ try {
155
+ const staleOnClose: () => void = jest.fn();
156
+ const latestOnClose: () => void = jest.fn();
157
+
158
+ const { rerender } = render(
159
+ <Toast
160
+ type={ToastType.SUCCESS}
161
+ title="Spread"
162
+ description="Love"
163
+ onClose={staleOnClose}
164
+ />,
165
+ );
166
+
167
+ fireEvent.click(screen.getByTestId("close-button"));
168
+
169
+ // 100ms into the 150ms exit fade the owner re-renders the toast…
170
+ act(() => {
171
+ jest.advanceTimersByTime(100);
172
+ });
173
+ rerender(
174
+ <Toast
175
+ type={ToastType.SUCCESS}
176
+ title="Spread"
177
+ description="Love"
178
+ onClose={latestOnClose}
179
+ />,
180
+ );
181
+
182
+ // …the card is still mid-fade…
183
+ expect(screen.getByTestId("toast")).toHaveClass("opacity-0");
184
+
185
+ // …and the ORIGINAL 150ms deadline still unmounts it 50ms later.
186
+ act(() => {
187
+ jest.advanceTimersByTime(50);
188
+ });
189
+
190
+ expect(screen.queryByTestId("toast")).toBeFalsy();
191
+ expect(latestOnClose).toHaveBeenCalledTimes(1);
192
+ expect(staleOnClose).not.toHaveBeenCalled();
193
+ } finally {
194
+ jest.useRealTimers();
195
+ }
73
196
  });
74
197
  });
@@ -0,0 +1,198 @@
1
+ import ToastLayout, {
2
+ ShowToastNotification,
3
+ } from "../../../UI/Components/Toast/ToastInit";
4
+ import { ToastType } from "../../../UI/Components/Toast/Toast";
5
+ import "@testing-library/jest-dom";
6
+ import {
7
+ act,
8
+ cleanup,
9
+ fireEvent,
10
+ render,
11
+ screen,
12
+ waitFor,
13
+ } from "@testing-library/react";
14
+ import React from "react";
15
+ import { afterEach, describe, expect, jest, test } from "@jest/globals";
16
+
17
+ /*
18
+ * ToastInit used to render every toast as its own fixed strip at top-0 (the
19
+ * index prop that was supposed to offset them was never passed), so stacked
20
+ * toasts painted on top of each other and only the last one was readable.
21
+ * The contract pinned here: ONE fixed region, cards stacked in a flex column
22
+ * in insertion order.
23
+ */
24
+
25
+ type ShowToastFunction = (title: string, onClose?: () => void) => void;
26
+
27
+ const showToast: ShowToastFunction = (
28
+ title: string,
29
+ onClose?: () => void,
30
+ ): void => {
31
+ act(() => {
32
+ ShowToastNotification({
33
+ title,
34
+ description: `${title} description`,
35
+ type: ToastType.SUCCESS,
36
+ onClose,
37
+ });
38
+ });
39
+ };
40
+
41
+ describe("Toast stacking", () => {
42
+ afterEach(() => {
43
+ cleanup();
44
+ jest.clearAllMocks();
45
+ });
46
+
47
+ test("renders one fixed region that keeps its slot in the z-index ladder", () => {
48
+ render(<ToastLayout />);
49
+
50
+ const region: HTMLElement = screen.getByTestId("toast-region");
51
+
52
+ /*
53
+ * z-40 is deliberate: above SideOver (z-30), below Modal (z-50). The
54
+ * region itself takes no pointer events; only the cards inside do.
55
+ */
56
+ expect(region).toHaveClass(
57
+ "pointer-events-none",
58
+ "fixed",
59
+ "z-40",
60
+ "top-0",
61
+ "left-0",
62
+ "right-0",
63
+ );
64
+ expect(region).toHaveClass("sm:items-start", "sm:p-6");
65
+ expect(region).toHaveAttribute("aria-live", "assertive");
66
+ });
67
+
68
+ test("stacks multiple toasts in one region, in insertion order", () => {
69
+ render(<ToastLayout />);
70
+
71
+ showToast("First");
72
+ showToast("Second");
73
+ showToast("Third");
74
+
75
+ const toasts: Array<HTMLElement> = screen.getAllByTestId("toast");
76
+
77
+ expect(toasts).toHaveLength(3);
78
+
79
+ // All three cards live inside the ONE region, not three fixed strips.
80
+ const region: HTMLElement = screen.getByTestId("toast-region");
81
+
82
+ for (const toast of toasts) {
83
+ expect(region).toContainElement(toast);
84
+ expect(toast).not.toHaveClass("fixed");
85
+ }
86
+
87
+ // The stack is a flex column with a gap, so cards never overlap.
88
+ const stack: HTMLElement = toasts[0]?.parentElement as HTMLElement;
89
+
90
+ expect(stack).toHaveClass("flex", "flex-col", "gap-4");
91
+ expect(toasts[1]?.parentElement).toBe(stack);
92
+ expect(toasts[2]?.parentElement).toBe(stack);
93
+
94
+ const titles: Array<string | null> = screen
95
+ .getAllByTestId("title")
96
+ .map((title: HTMLElement) => {
97
+ return title.textContent;
98
+ });
99
+
100
+ expect(titles).toEqual(["First", "Second", "Third"]);
101
+ });
102
+
103
+ test("dismissing one toast removes only that toast and calls its onClose", async () => {
104
+ const onCloseFirst: () => void = jest.fn();
105
+
106
+ render(<ToastLayout />);
107
+
108
+ showToast("First", onCloseFirst);
109
+ showToast("Second");
110
+
111
+ const closeButtons: Array<HTMLElement> =
112
+ screen.getAllByTestId("close-button");
113
+
114
+ expect(closeButtons).toHaveLength(2);
115
+
116
+ fireEvent.click(closeButtons[0] as HTMLElement);
117
+
118
+ /*
119
+ * The exit fade delays the unmount, so removal is awaited rather than
120
+ * asserted synchronously.
121
+ */
122
+ await waitFor(() => {
123
+ expect(screen.getAllByTestId("toast")).toHaveLength(1);
124
+ });
125
+
126
+ expect(onCloseFirst).toHaveBeenCalledTimes(1);
127
+ expect(screen.getByTestId("title")).toHaveTextContent("Second");
128
+ });
129
+
130
+ test("a toast dispatched after a dismissal still stacks below the survivors", async () => {
131
+ render(<ToastLayout />);
132
+
133
+ showToast("First");
134
+ showToast("Second");
135
+
136
+ fireEvent.click(screen.getAllByTestId("close-button")[0] as HTMLElement);
137
+
138
+ await waitFor(() => {
139
+ expect(screen.getAllByTestId("toast")).toHaveLength(1);
140
+ });
141
+
142
+ showToast("Third");
143
+
144
+ const titles: Array<string | null> = screen
145
+ .getAllByTestId("title")
146
+ .map((title: HTMLElement) => {
147
+ return title.textContent;
148
+ });
149
+
150
+ expect(titles).toEqual(["Second", "Third"]);
151
+ });
152
+
153
+ /*
154
+ * Regression: dispatching a toast re-renders the whole stack, and the
155
+ * layout used to hand every card a freshly created onClose on each render.
156
+ * A dismissed card mid-exit-fade saw the new prop, cleared its unmount
157
+ * timer, and re-armed it — so a burst of toasts could keep an invisible
158
+ * pointer-events-auto card alive indefinitely, intercepting clicks.
159
+ */
160
+ test("a toast dispatched during another toast's exit fade does not restart its unmount timer", () => {
161
+ jest.useFakeTimers();
162
+
163
+ try {
164
+ const onCloseFirst: () => void = jest.fn();
165
+
166
+ render(<ToastLayout />);
167
+
168
+ showToast("First", onCloseFirst);
169
+
170
+ fireEvent.click(screen.getByTestId("close-button"));
171
+
172
+ // 100ms into the 150ms exit fade a second toast re-renders the stack.
173
+ act(() => {
174
+ jest.advanceTimersByTime(100);
175
+ });
176
+ showToast("Second");
177
+
178
+ expect(screen.getAllByTestId("toast")).toHaveLength(2);
179
+
180
+ // The ORIGINAL 150ms deadline holds: 50ms later the first card is gone.
181
+ act(() => {
182
+ jest.advanceTimersByTime(50);
183
+ });
184
+
185
+ expect(onCloseFirst).toHaveBeenCalledTimes(1);
186
+
187
+ const titles: Array<string | null> = screen
188
+ .getAllByTestId("title")
189
+ .map((title: HTMLElement) => {
190
+ return title.textContent;
191
+ });
192
+
193
+ expect(titles).toEqual(["Second"]);
194
+ } finally {
195
+ jest.useRealTimers();
196
+ }
197
+ });
198
+ });