@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,267 @@
1
+ import FieldType from "../../../../../UI/Components/Types/FieldType";
2
+ import FormFieldSchemaType from "../../../../../UI/Components/Forms/Types/FormFieldSchemaType";
3
+ import FormFieldSchemaTypeUtil from "../../../../../UI/Components/Forms/Utils/FormFieldSchemaTypeUtil";
4
+ import { describe, expect, test } from "@jest/globals";
5
+
6
+ /*
7
+ * FormFieldSchemaTypeUtil.toFieldType maps a form's high-level schema type onto
8
+ * the lower-level FieldType the renderer understands. The implementation is a
9
+ * pure switch, so every case is a fixed input -> fixed output with no time,
10
+ * network, database or randomness involved — all assertions below are
11
+ * deterministic.
12
+ */
13
+
14
+ interface SchemaToFieldExpectation {
15
+ schemaType: FormFieldSchemaType;
16
+ expected: FieldType;
17
+ }
18
+
19
+ /*
20
+ * The full, hand-checked mapping table — one row per FormFieldSchemaType member.
21
+ * This is the backbone of the suite: it pins down the exact FieldType every
22
+ * schema type collapses to, including the several many-to-one and renamed
23
+ * cases (for example Time -> DateTime, Domain -> Text, Query -> Element).
24
+ */
25
+ const expectations: Array<SchemaToFieldExpectation> = [
26
+ { schemaType: FormFieldSchemaType.ObjectID, expected: FieldType.ObjectID },
27
+ { schemaType: FormFieldSchemaType.Name, expected: FieldType.Name },
28
+ { schemaType: FormFieldSchemaType.Hostname, expected: FieldType.Hostname },
29
+ { schemaType: FormFieldSchemaType.ImageFile, expected: FieldType.ImageFile },
30
+ { schemaType: FormFieldSchemaType.URL, expected: FieldType.URL },
31
+ { schemaType: FormFieldSchemaType.Route, expected: FieldType.Route },
32
+ { schemaType: FormFieldSchemaType.Number, expected: FieldType.Number },
33
+ { schemaType: FormFieldSchemaType.Password, expected: FieldType.Password },
34
+ { schemaType: FormFieldSchemaType.Text, expected: FieldType.Text },
35
+ { schemaType: FormFieldSchemaType.Time, expected: FieldType.DateTime },
36
+ { schemaType: FormFieldSchemaType.Email, expected: FieldType.Email },
37
+ {
38
+ schemaType: FormFieldSchemaType.PositiveNumber,
39
+ expected: FieldType.Number,
40
+ },
41
+ { schemaType: FormFieldSchemaType.Date, expected: FieldType.Date },
42
+ { schemaType: FormFieldSchemaType.Phone, expected: FieldType.Phone },
43
+ { schemaType: FormFieldSchemaType.DateTime, expected: FieldType.DateTime },
44
+ { schemaType: FormFieldSchemaType.Domain, expected: FieldType.Text },
45
+ { schemaType: FormFieldSchemaType.LongText, expected: FieldType.LongText },
46
+ { schemaType: FormFieldSchemaType.Color, expected: FieldType.Color },
47
+ { schemaType: FormFieldSchemaType.Dropdown, expected: FieldType.Dropdown },
48
+ { schemaType: FormFieldSchemaType.File, expected: FieldType.File },
49
+ {
50
+ schemaType: FormFieldSchemaType.MultiSelectDropdown,
51
+ expected: FieldType.MultiSelectDropdown,
52
+ },
53
+ {
54
+ schemaType: FormFieldSchemaType.OptionChooserButton,
55
+ expected: FieldType.Element,
56
+ },
57
+ { schemaType: FormFieldSchemaType.Toggle, expected: FieldType.Boolean },
58
+ { schemaType: FormFieldSchemaType.Port, expected: FieldType.Port },
59
+ {
60
+ schemaType: FormFieldSchemaType.EncryptedText,
61
+ expected: FieldType.HiddenText,
62
+ },
63
+ { schemaType: FormFieldSchemaType.Markdown, expected: FieldType.Markdown },
64
+ {
65
+ schemaType: FormFieldSchemaType.JavaScript,
66
+ expected: FieldType.JavaScript,
67
+ },
68
+ { schemaType: FormFieldSchemaType.CSS, expected: FieldType.CSS },
69
+ { schemaType: FormFieldSchemaType.HTML, expected: FieldType.HTML },
70
+ { schemaType: FormFieldSchemaType.RadioButton, expected: FieldType.Text },
71
+ { schemaType: FormFieldSchemaType.JSON, expected: FieldType.JSON },
72
+ { schemaType: FormFieldSchemaType.Query, expected: FieldType.Element },
73
+ {
74
+ schemaType: FormFieldSchemaType.CustomComponent,
75
+ expected: FieldType.Element,
76
+ },
77
+ { schemaType: FormFieldSchemaType.Checkbox, expected: FieldType.Boolean },
78
+ {
79
+ schemaType: FormFieldSchemaType.CategoryCheckbox,
80
+ expected: FieldType.Boolean,
81
+ },
82
+ { schemaType: FormFieldSchemaType.Icon, expected: FieldType.Icon },
83
+ /*
84
+ * These three schema types have no explicit case in the switch and therefore
85
+ * fall through to the default arm, which returns Text. They are listed here
86
+ * so the exhaustiveness guard below sees them and so their default-path
87
+ * behaviour is asserted alongside every other member.
88
+ */
89
+ { schemaType: FormFieldSchemaType.MultipleFiles, expected: FieldType.Text },
90
+ { schemaType: FormFieldSchemaType.Dictionary, expected: FieldType.Text },
91
+ { schemaType: FormFieldSchemaType.CardSelect, expected: FieldType.Text },
92
+ ];
93
+
94
+ describe("FormFieldSchemaTypeUtil.toFieldType — full mapping table", () => {
95
+ test.each(expectations)(
96
+ "maps schema type $schemaType to field type $expected",
97
+ (expectation: SchemaToFieldExpectation): void => {
98
+ const actual: FieldType = FormFieldSchemaTypeUtil.toFieldType(
99
+ expectation.schemaType,
100
+ );
101
+
102
+ expect(actual).toBe(expectation.expected);
103
+ },
104
+ );
105
+ });
106
+
107
+ describe("FormFieldSchemaTypeUtil.toFieldType — many-to-one and renamed cases", () => {
108
+ /*
109
+ * Several schema types deliberately do NOT map to a like-named FieldType.
110
+ * These are the cases most likely to break silently under a careless edit, so
111
+ * each is spelled out on its own to document the intended collapse.
112
+ */
113
+ test("Time renders as a DateTime field", () => {
114
+ expect(FormFieldSchemaTypeUtil.toFieldType(FormFieldSchemaType.Time)).toBe(
115
+ FieldType.DateTime,
116
+ );
117
+ });
118
+
119
+ test("PositiveNumber renders as a plain Number field", () => {
120
+ expect(
121
+ FormFieldSchemaTypeUtil.toFieldType(FormFieldSchemaType.PositiveNumber),
122
+ ).toBe(FieldType.Number);
123
+ });
124
+
125
+ test("Domain and RadioButton both render as Text fields", () => {
126
+ expect(
127
+ FormFieldSchemaTypeUtil.toFieldType(FormFieldSchemaType.Domain),
128
+ ).toBe(FieldType.Text);
129
+ expect(
130
+ FormFieldSchemaTypeUtil.toFieldType(FormFieldSchemaType.RadioButton),
131
+ ).toBe(FieldType.Text);
132
+ });
133
+
134
+ test("EncryptedText renders as a HiddenText field", () => {
135
+ expect(
136
+ FormFieldSchemaTypeUtil.toFieldType(FormFieldSchemaType.EncryptedText),
137
+ ).toBe(FieldType.HiddenText);
138
+ });
139
+
140
+ test("OptionChooserButton, Query and CustomComponent all render as Element fields", () => {
141
+ const elementSchemaTypes: Array<FormFieldSchemaType> = [
142
+ FormFieldSchemaType.OptionChooserButton,
143
+ FormFieldSchemaType.Query,
144
+ FormFieldSchemaType.CustomComponent,
145
+ ];
146
+
147
+ for (const schemaType of elementSchemaTypes) {
148
+ expect(FormFieldSchemaTypeUtil.toFieldType(schemaType)).toBe(
149
+ FieldType.Element,
150
+ );
151
+ }
152
+ });
153
+
154
+ test("Toggle, Checkbox and CategoryCheckbox all render as Boolean fields", () => {
155
+ const booleanSchemaTypes: Array<FormFieldSchemaType> = [
156
+ FormFieldSchemaType.Toggle,
157
+ FormFieldSchemaType.Checkbox,
158
+ FormFieldSchemaType.CategoryCheckbox,
159
+ ];
160
+
161
+ for (const schemaType of booleanSchemaTypes) {
162
+ expect(FormFieldSchemaTypeUtil.toFieldType(schemaType)).toBe(
163
+ FieldType.Boolean,
164
+ );
165
+ }
166
+ });
167
+ });
168
+
169
+ describe("FormFieldSchemaTypeUtil.toFieldType — default fallback", () => {
170
+ /*
171
+ * The switch matches on the enum's string VALUE. An input whose value is not a
172
+ * known schema type must land on the default arm and come back as Text rather
173
+ * than throwing or returning undefined.
174
+ */
175
+ test("an unrecognised value falls back to Text", () => {
176
+ expect(
177
+ FormFieldSchemaTypeUtil.toFieldType(
178
+ "definitely-not-a-schema-type" as FormFieldSchemaType,
179
+ ),
180
+ ).toBe(FieldType.Text);
181
+ });
182
+
183
+ test("an empty string falls back to Text", () => {
184
+ expect(FormFieldSchemaTypeUtil.toFieldType("" as FormFieldSchemaType)).toBe(
185
+ FieldType.Text,
186
+ );
187
+ });
188
+
189
+ test("the enum members with no explicit case (MultipleFiles, Dictionary, CardSelect) fall back to Text", () => {
190
+ expect(
191
+ FormFieldSchemaTypeUtil.toFieldType(FormFieldSchemaType.MultipleFiles),
192
+ ).toBe(FieldType.Text);
193
+ expect(
194
+ FormFieldSchemaTypeUtil.toFieldType(FormFieldSchemaType.Dictionary),
195
+ ).toBe(FieldType.Text);
196
+ expect(
197
+ FormFieldSchemaTypeUtil.toFieldType(FormFieldSchemaType.CardSelect),
198
+ ).toBe(FieldType.Text);
199
+ });
200
+ });
201
+
202
+ describe("FormFieldSchemaTypeUtil.toFieldType — the Toggle value quirk", () => {
203
+ /*
204
+ * FormFieldSchemaType.Toggle is unusual: its member name is Toggle but its
205
+ * string value is "Boolean". Because the switch compares the underlying
206
+ * string value, feeding the raw literal "Boolean" must resolve to the Toggle
207
+ * case (Boolean), not to the default arm.
208
+ */
209
+ test("the Toggle member's underlying string value is 'Boolean'", () => {
210
+ expect(FormFieldSchemaType.Toggle).toBe("Boolean");
211
+ });
212
+
213
+ test("the raw literal 'Boolean' resolves to the Toggle case", () => {
214
+ expect(
215
+ FormFieldSchemaTypeUtil.toFieldType("Boolean" as FormFieldSchemaType),
216
+ ).toBe(FieldType.Boolean);
217
+ });
218
+ });
219
+
220
+ describe("FormFieldSchemaTypeUtil.toFieldType — structural guarantees", () => {
221
+ test("covers every FormFieldSchemaType member (guards against a new, unmapped type)", () => {
222
+ const coveredValues: Set<string> = new Set(
223
+ expectations.map((expectation: SchemaToFieldExpectation): string => {
224
+ return expectation.schemaType;
225
+ }),
226
+ );
227
+ const allSchemaValues: Array<string> = Object.values(FormFieldSchemaType);
228
+
229
+ for (const schemaValue of allSchemaValues) {
230
+ expect(coveredValues.has(schemaValue)).toBe(true);
231
+ }
232
+
233
+ /*
234
+ * Every schema value is unique, so the covered set and the enum must be the
235
+ * same size. If a member is added to the enum this fails, flagging that the
236
+ * mapping (and this table) needs updating.
237
+ */
238
+ expect(coveredValues.size).toBe(allSchemaValues.length);
239
+ });
240
+
241
+ test("always returns a defined value that is a real FieldType member", () => {
242
+ const validFieldTypes: Set<string> = new Set(Object.values(FieldType));
243
+
244
+ for (const schemaValue of Object.values(FormFieldSchemaType)) {
245
+ const result: FieldType = FormFieldSchemaTypeUtil.toFieldType(
246
+ schemaValue as FormFieldSchemaType,
247
+ );
248
+
249
+ expect(result).toBeDefined();
250
+ expect(validFieldTypes.has(result)).toBe(true);
251
+ }
252
+ });
253
+
254
+ test("is pure — the same input yields the same output every time", () => {
255
+ for (const expectation of expectations) {
256
+ const first: FieldType = FormFieldSchemaTypeUtil.toFieldType(
257
+ expectation.schemaType,
258
+ );
259
+ const second: FieldType = FormFieldSchemaTypeUtil.toFieldType(
260
+ expectation.schemaType,
261
+ );
262
+
263
+ expect(first).toBe(second);
264
+ expect(first).toBe(expectation.expected);
265
+ }
266
+ });
267
+ });
@@ -63,10 +63,17 @@ describe("List", () => {
63
63
  expect(screen.getByTestId("list-pagination")).toBeInTheDocument();
64
64
  });
65
65
 
66
- it("renders loading state", () => {
66
+ it("renders skeleton cards while loading with nothing to show yet", () => {
67
+ render(<List {...defaultProps} isLoading={true} data={[]} />);
68
+
69
+ expect(screen.getByTestId("list-skeleton-loader")).toBeInTheDocument();
70
+ });
71
+
72
+ it("keeps existing cards visible, dimmed, while refetching", () => {
67
73
  render(<List {...defaultProps} isLoading={true} />);
68
74
 
69
- expect(screen.getByTestId("component-loader")).toBeInTheDocument();
75
+ expect(screen.queryByTestId("list-skeleton-loader")).toBeNull();
76
+ expect(screen.getByTestId("list-content")).toHaveClass("opacity-60");
70
77
  });
71
78
 
72
79
  it("renders error state", () => {
@@ -0,0 +1,173 @@
1
+ import List, { ComponentProps } from "../../../UI/Components/List/List";
2
+ import FieldType from "../../../UI/Components/Types/FieldType";
3
+ import "@testing-library/jest-dom";
4
+ import { describe, expect, it, jest } from "@jest/globals";
5
+ import { render, screen } from "@testing-library/react";
6
+ import React from "react";
7
+
8
+ /*
9
+ * The list's loading contract, mirroring the table's: a first load (nothing
10
+ * fetched yet) renders card-shaped skeletons in ListBody's own wrapper so
11
+ * nothing shifts when data lands; a refetch keeps the stale cards visible,
12
+ * dimmed and unclickable; error and empty only speak once loading finishes.
13
+ */
14
+
15
+ describe("List loading states", () => {
16
+ interface ListData {
17
+ id: string;
18
+ name: string;
19
+ description: string;
20
+ }
21
+
22
+ const fields: ComponentProps<ListData>["fields"] = [
23
+ {
24
+ title: "ID",
25
+ key: "id",
26
+ fieldType: FieldType.Text,
27
+ colSpan: 1,
28
+ },
29
+ {
30
+ title: "Name",
31
+ key: "name",
32
+ fieldType: FieldType.Text,
33
+ colSpan: 2,
34
+ },
35
+ {
36
+ title: "Description",
37
+ key: "description",
38
+ fieldType: FieldType.Text,
39
+ colSpan: 2,
40
+ },
41
+ ];
42
+
43
+ const defaultProps: ComponentProps<ListData> = {
44
+ data: [
45
+ { id: "1", name: "Item 1", description: "Description 1" },
46
+ { id: "2", name: "Item 2", description: "Description 2" },
47
+ ],
48
+ id: "test-list",
49
+ fields: fields,
50
+ onNavigateToPage: jest.fn(),
51
+ currentPageNumber: 1,
52
+ totalItemsCount: 10,
53
+ itemsOnPage: 10,
54
+ error: "",
55
+ isLoading: false,
56
+ singularLabel: "Item",
57
+ pluralLabel: "Items",
58
+ };
59
+
60
+ describe("first load (nothing to show yet)", () => {
61
+ it("renders skeleton cards instead of a spinner", () => {
62
+ render(<List {...defaultProps} isLoading={true} data={[]} />);
63
+
64
+ expect(screen.getByTestId("list-skeleton-loader")).toBeInTheDocument();
65
+ expect(screen.queryByTestId("component-loader")).toBeNull();
66
+ });
67
+
68
+ it("announces itself as a polite status with a Loading label", () => {
69
+ render(<List {...defaultProps} isLoading={true} data={[]} />);
70
+
71
+ const loader: HTMLElement = screen.getByTestId("list-skeleton-loader");
72
+ expect(loader).toHaveAttribute("role", "status");
73
+ expect(loader).toHaveAttribute("aria-live", "polite");
74
+ expect(loader).toHaveTextContent("Loading");
75
+ });
76
+
77
+ it("matches ListBody's wrapper so nothing shifts when data lands", () => {
78
+ render(<List {...defaultProps} isLoading={true} data={[]} />);
79
+
80
+ expect(screen.getByTestId("list-skeleton-loader")).toHaveClass(
81
+ "space-y-6",
82
+ "p-6",
83
+ "border-t",
84
+ );
85
+ });
86
+
87
+ it("renders one card per item on the page, capped at 6", () => {
88
+ render(
89
+ <List {...defaultProps} isLoading={true} data={[]} itemsOnPage={25} />,
90
+ );
91
+
92
+ const loader: HTMLElement = screen.getByTestId("list-skeleton-loader");
93
+ expect(loader.querySelectorAll(":scope > div")).toHaveLength(6);
94
+ });
95
+
96
+ it("renders fewer cards for a smaller page size", () => {
97
+ render(
98
+ <List {...defaultProps} isLoading={true} data={[]} itemsOnPage={3} />,
99
+ );
100
+
101
+ const loader: HTMLElement = screen.getByTestId("list-skeleton-loader");
102
+ expect(loader.querySelectorAll(":scope > div")).toHaveLength(3);
103
+ });
104
+
105
+ it("gives each card one placeholder line per field", () => {
106
+ render(<List {...defaultProps} isLoading={true} data={[]} />);
107
+
108
+ const loader: HTMLElement = screen.getByTestId("list-skeleton-loader");
109
+ const firstCard: Element = loader.querySelectorAll(
110
+ ":scope > div",
111
+ )[0] as Element;
112
+
113
+ expect(
114
+ firstCard.querySelectorAll('[data-testid="skeleton"]'),
115
+ ).toHaveLength(fields.length);
116
+ });
117
+ });
118
+
119
+ describe("refetch with cards already on screen", () => {
120
+ it("keeps the real cards and shows no skeletons", () => {
121
+ render(<List {...defaultProps} isLoading={true} />);
122
+
123
+ expect(screen.queryByTestId("list-skeleton-loader")).toBeNull();
124
+ expect(screen.getByText("Item 1")).toBeInTheDocument();
125
+ });
126
+
127
+ it("dims the stale cards and blocks pointer events on them", () => {
128
+ render(<List {...defaultProps} isLoading={true} />);
129
+
130
+ const content: HTMLElement = screen.getByTestId("list-content");
131
+ expect(content).toHaveClass("opacity-60");
132
+ expect(content).toHaveClass("pointer-events-none");
133
+ });
134
+
135
+ it("an idle list is neither dimmed nor blocked", () => {
136
+ render(<List {...defaultProps} />);
137
+
138
+ const content: HTMLElement = screen.getByTestId("list-content");
139
+ expect(content).not.toHaveClass("opacity-60");
140
+ expect(content).not.toHaveClass("pointer-events-none");
141
+ });
142
+
143
+ /*
144
+ * Loading outranks error: mid-refetch the error prop can only be stale
145
+ * (the parent clears it before every fetch), so surfacing it over live
146
+ * cards would report a failure that is not happening.
147
+ */
148
+ it("a stale error does not interrupt the dimmed cards", () => {
149
+ render(<List {...defaultProps} isLoading={true} error="stale failure" />);
150
+
151
+ expect(screen.getByText("Item 1")).toBeInTheDocument();
152
+ expect(screen.queryByText("stale failure")).toBeNull();
153
+ });
154
+ });
155
+
156
+ describe("after loading finishes", () => {
157
+ it("an error replaces the cards", () => {
158
+ render(<List {...defaultProps} error="Something went wrong" />);
159
+
160
+ expect(screen.getByText("Something went wrong")).toBeInTheDocument();
161
+ expect(screen.queryByTestId("list-skeleton-loader")).toBeNull();
162
+ });
163
+
164
+ it("an empty result shows the no-items message, not skeletons", () => {
165
+ render(
166
+ <List {...defaultProps} data={[]} noItemsMessage="Nothing here" />,
167
+ );
168
+
169
+ expect(screen.getByText("Nothing here")).toBeInTheDocument();
170
+ expect(screen.queryByTestId("list-skeleton-loader")).toBeNull();
171
+ });
172
+ });
173
+ });
@@ -0,0 +1,113 @@
1
+ /*
2
+ * Perf contract for MarkdownViewer's mermaid integration (linear-polish):
3
+ * mermaid's pre-bundled build is ~4.5MB — it used to be statically imported
4
+ * and initialized at module scope, which dragged it into the Dashboard's
5
+ * eager first-load graph through every markdown call site. The contract now
6
+ * is: importing MarkdownViewer and rendering plain markdown must NEVER load
7
+ * mermaid; only a rendered mermaid diagram may trigger the dynamic import,
8
+ * and theme initialization must happen after that load.
9
+ *
10
+ * The jest module registry is the probe: the "mermaid" factory below flips a
11
+ * sentinel when the module is actually required, which under ts-jest's
12
+ * CommonJS output happens exactly when the component executes its dynamic
13
+ * import(). (Common/jest.config.json maps "mermaid" to a stub via
14
+ * moduleNameMapper, but an explicit jest.mock factory takes precedence.)
15
+ */
16
+ import { afterEach, describe, expect, jest, test } from "@jest/globals";
17
+
18
+ interface MockMermaidState {
19
+ required: boolean;
20
+ initializeCalls: Array<unknown>;
21
+ renderCalls: Array<string>;
22
+ }
23
+
24
+ const mockMermaidState: MockMermaidState = {
25
+ required: false,
26
+ initializeCalls: [],
27
+ renderCalls: [],
28
+ };
29
+
30
+ jest.mock("mermaid", () => {
31
+ // Runs only when something actually requires mermaid — the sentinel.
32
+ mockMermaidState.required = true;
33
+ return {
34
+ __esModule: true,
35
+ default: {
36
+ initialize: (config: unknown): void => {
37
+ mockMermaidState.initializeCalls.push(config);
38
+ },
39
+ render: (_id: string, chart: string): Promise<{ svg: string }> => {
40
+ mockMermaidState.renderCalls.push(chart);
41
+ return Promise.resolve({
42
+ svg: '<svg><g data-diagram="true"></g></svg>',
43
+ });
44
+ },
45
+ },
46
+ };
47
+ });
48
+
49
+ import "@testing-library/jest-dom";
50
+ import { cleanup, render, screen, waitFor } from "@testing-library/react";
51
+ import React from "react";
52
+ import MarkdownViewer, {
53
+ MermaidDiagram,
54
+ } from "../../../UI/Components/Markdown.tsx/MarkdownViewer";
55
+
56
+ /*
57
+ * Captured immediately after the static import above: if MarkdownViewer ever
58
+ * regresses to a module-scope `import mermaid from "mermaid"` (or a
59
+ * module-scope initialize call), this flips to true before any test runs.
60
+ */
61
+ const mermaidRequiredAtModuleLoad: boolean = mockMermaidState.required;
62
+
63
+ afterEach(() => {
64
+ cleanup();
65
+ });
66
+
67
+ /*
68
+ * NOTE: test order inside this file is load-bearing. The sentinel is
69
+ * one-directional (a module stays required for the rest of the process), so
70
+ * the "never loads" assertions must run before the test that legitimately
71
+ * triggers the load.
72
+ */
73
+ describe("MarkdownViewer lazy mermaid contract", () => {
74
+ test("importing the MarkdownViewer module does not require mermaid", () => {
75
+ expect(mermaidRequiredAtModuleLoad).toBe(false);
76
+ });
77
+
78
+ test("rendering plain markdown does not require mermaid", () => {
79
+ render(
80
+ <MarkdownViewer
81
+ text={"# Hello\n\nJust **plain** markdown with `inline code`."}
82
+ />,
83
+ );
84
+
85
+ const rendered: HTMLElement = screen.getByTestId("react-markdown");
86
+ expect(rendered).toBeInTheDocument();
87
+ expect(mockMermaidState.required).toBe(false);
88
+ expect(mockMermaidState.initializeCalls).toHaveLength(0);
89
+ });
90
+
91
+ test("rendering a mermaid diagram dynamically loads mermaid, then initializes and renders", async () => {
92
+ const chart: string = "graph TD; A-->B";
93
+ const { container } = render(<MermaidDiagram chart={chart} />);
94
+
95
+ // The load + render pipeline is async (dynamic import then render()).
96
+ await waitFor((): void => {
97
+ expect(mockMermaidState.renderCalls).toContain(chart);
98
+ });
99
+
100
+ expect(mockMermaidState.required).toBe(true);
101
+
102
+ /*
103
+ * Theme setup must run post-load and before render: with no module-scope
104
+ * initialization left, the only way initialize() can have been called is
105
+ * from the diagram's own effect after the dynamic import resolved.
106
+ */
107
+ expect(mockMermaidState.initializeCalls.length).toBeGreaterThan(0);
108
+
109
+ await waitFor((): void => {
110
+ expect(container.querySelector("svg")).not.toBeNull();
111
+ });
112
+ });
113
+ });
@@ -0,0 +1,110 @@
1
+ /*
2
+ * Retry contract for MarkdownViewer's lazy mermaid load (linear-polish):
3
+ * loadMermaid() caches the dynamic-import promise module-wide so multiple
4
+ * diagrams share one load — but a REJECTED load (flaky network, a deploy
5
+ * rotating hashed chunk names) must not be cached forever. The contract is:
6
+ * the diagram that triggered the failed load shows its error state, and the
7
+ * next diagram mount retries the import from scratch.
8
+ *
9
+ * This lives in its own file rather than MarkdownLazy.test.tsx because that
10
+ * file's sentinel design is one-directional: once its legitimate-load test
11
+ * has run, jest's mock registry AND the component's module-scope cache both
12
+ * hold the successfully resolved module for the rest of the process, so a
13
+ * failure can no longer be staged there (jest.resetModules() is no escape —
14
+ * it would fork React's identity out from under @testing-library). Here the
15
+ * FIRST require of "mermaid" throws instead: jest memoizes a mock factory's
16
+ * result only after it returns, so a throwing factory is re-invoked on the
17
+ * next require — which is exactly the retry path under test.
18
+ */
19
+ import { afterEach, describe, expect, jest, test } from "@jest/globals";
20
+
21
+ interface MockMermaidState {
22
+ factoryAttempts: number;
23
+ failNextRequire: boolean;
24
+ renderCalls: Array<string>;
25
+ }
26
+
27
+ const mockMermaidState: MockMermaidState = {
28
+ factoryAttempts: 0,
29
+ failNextRequire: true,
30
+ renderCalls: [],
31
+ };
32
+
33
+ jest.mock("mermaid", () => {
34
+ // Runs on every require until it returns once — then jest memoizes it.
35
+ mockMermaidState.factoryAttempts++;
36
+ if (mockMermaidState.failNextRequire) {
37
+ mockMermaidState.failNextRequire = false;
38
+ throw new Error("simulated mermaid chunk load failure");
39
+ }
40
+ return {
41
+ __esModule: true,
42
+ default: {
43
+ initialize: (): void => {
44
+ // theme setup is exercised in MarkdownLazy.test.tsx — a no-op here.
45
+ },
46
+ render: (_id: string, chart: string): Promise<{ svg: string }> => {
47
+ mockMermaidState.renderCalls.push(chart);
48
+ return Promise.resolve({
49
+ svg: '<svg><g data-diagram="true"></g></svg>',
50
+ });
51
+ },
52
+ },
53
+ };
54
+ });
55
+
56
+ import "@testing-library/jest-dom";
57
+ import { cleanup, render, waitFor } from "@testing-library/react";
58
+ import React from "react";
59
+ import { MermaidDiagram } from "../../../UI/Components/Markdown.tsx/MarkdownViewer";
60
+
61
+ afterEach(() => {
62
+ cleanup();
63
+ });
64
+
65
+ describe("MarkdownViewer mermaid load retry contract", () => {
66
+ test("a failed chunk load surfaces as the diagram's error state, and the next mount retries and succeeds", async () => {
67
+ const chart: string = "graph TD; A-->B";
68
+
69
+ /*
70
+ * First mount: the dynamic import rejects (the mock factory throws on
71
+ * the first require). The diagram must fall into its error path, not
72
+ * hang or crash the tree.
73
+ */
74
+ const first: ReturnType<typeof render> = render(
75
+ <MermaidDiagram chart={chart} />,
76
+ );
77
+
78
+ await waitFor((): void => {
79
+ expect(
80
+ first.container.querySelector("pre.text-red-500")?.textContent,
81
+ ).toContain("Error rendering diagram");
82
+ });
83
+
84
+ expect(mockMermaidState.factoryAttempts).toBe(1);
85
+ expect(mockMermaidState.renderCalls).toHaveLength(0);
86
+
87
+ first.unmount();
88
+
89
+ /*
90
+ * Second mount: the rejected load must NOT have been cached — the
91
+ * component retries the import, which now succeeds, and the diagram
92
+ * renders. Before the fix, the module-scope promise cache pinned the
93
+ * first rejection forever and this mount showed the error again.
94
+ */
95
+ const second: ReturnType<typeof render> = render(
96
+ <MermaidDiagram chart={chart} />,
97
+ );
98
+
99
+ await waitFor((): void => {
100
+ expect(mockMermaidState.renderCalls).toContain(chart);
101
+ });
102
+
103
+ expect(mockMermaidState.factoryAttempts).toBe(2);
104
+
105
+ await waitFor((): void => {
106
+ expect(second.container.querySelector("svg")).not.toBeNull();
107
+ });
108
+ expect(second.container.querySelector("pre.text-red-500")).toBeNull();
109
+ });
110
+ });