@oneuptime/common 12.0.9 → 12.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/Models/DatabaseModels/NetworkDevice.ts +44 -0
  2. package/Models/DatabaseModels/NetworkDeviceLink.ts +116 -0
  3. package/Models/DatabaseModels/RunnerJob.ts +17 -2
  4. package/Server/API/LlmProviderAPI.ts +8 -0
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.ts +52 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  7. package/Server/Services/NetworkDeviceLabelRuleEngineService.ts +240 -0
  8. package/Server/Services/NetworkDeviceLinkService.ts +338 -2
  9. package/Server/Services/NetworkDeviceService.ts +265 -12
  10. package/Server/Services/RunnerJobService.ts +24 -1
  11. package/Server/Types/Billing/MeteredPlan/AllMeteredPlans.ts +19 -5
  12. package/Server/Types/Database/JSONColumnQuery.ts +42 -4
  13. package/Server/Utils/LLM/LLMService.ts +108 -19
  14. package/Server/Utils/Monitor/MonitorAlert.ts +22 -115
  15. package/Server/Utils/Monitor/MonitorIncident.ts +7 -337
  16. package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +27 -2
  17. package/Server/Utils/Monitor/SeriesResourceLabels.ts +32 -0
  18. package/Server/Utils/Monitor/SeriesResourceLinker.ts +469 -0
  19. package/Server/Utils/SSRFProtection.ts +5 -4
  20. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +186 -38
  21. package/Tests/App/AdminDashboard/AdminUsersBulkAddToProject.test.tsx +955 -0
  22. package/Tests/App/AdminDashboard/BulkAddUsersToProject.test.ts +778 -0
  23. package/Tests/App/AdminDashboard/BulkAddUsersToProjectModal.test.tsx +1051 -0
  24. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +420 -112
  25. package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +81 -6
  26. package/Tests/App/Dashboard/PayAsYouGoConsentGate.test.tsx +543 -0
  27. package/Tests/App/Dashboard/PayAsYouGoFreePlan.test.ts +125 -0
  28. package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +612 -0
  29. package/Tests/App/Dashboard/SloWidgetChartRendering.test.tsx +536 -0
  30. package/Tests/App/Dashboard/UserElementEmail.test.tsx +447 -0
  31. package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +284 -0
  32. package/Tests/Models/AnalyticsModels/CommonModel.test.ts +312 -0
  33. package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +250 -0
  34. package/Tests/Server/Infrastructure/Postgres/EpisodeMemberNotifyIndexesMigration.test.ts +46 -18
  35. package/Tests/Server/Services/NetworkDeviceLabelRuleRun.test.ts +618 -0
  36. package/Tests/Server/Services/NetworkDeviceLinkParentValidation.test.ts +1043 -0
  37. package/Tests/Server/Services/NetworkSiteAssignmentRuleRun.test.ts +706 -0
  38. package/Tests/Server/Services/RunnerJobEnqueue.test.ts +412 -0
  39. package/Tests/Server/Types/Billing/PayAsYouGoMeteredPricing.test.ts +125 -0
  40. package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +508 -0
  41. package/Tests/Server/Utils/AI/LLMServiceRequestPolicy.test.ts +2 -2
  42. package/Tests/Server/Utils/AI/LLMServiceRetryPolicy.test.ts +450 -0
  43. package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +556 -0
  44. package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +547 -0
  45. package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +30 -0
  46. package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +699 -0
  47. package/Tests/Server/Utils/SeriesResourceLabels.test.ts +55 -0
  48. package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +668 -0
  49. package/Tests/Types/API/URLMalformedTolerance.test.ts +301 -0
  50. package/Tests/Types/API/URLTelScheme.test.ts +261 -0
  51. package/Tests/Types/API/URLUnsupportedScheme.test.ts +148 -0
  52. package/Tests/Types/Billing/PayAsYouGoPricing.test.ts +64 -0
  53. package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +107 -0
  54. package/Tests/Types/Monitor/MonitorType.test.ts +62 -0
  55. package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +184 -0
  56. package/Tests/Types/Runbook/RunbookStepType.test.ts +124 -0
  57. package/Tests/Types/Workflow/BaseModelComponents.test.ts +17 -0
  58. package/Tests/UI/Components/Button.test.tsx +279 -0
  59. package/Tests/UI/Components/Charts/ChartXAxisDataKey.test.tsx +256 -0
  60. package/Tests/UI/Components/Charts/DataPointUtil.test.ts +323 -0
  61. package/Tests/UI/Components/DictionaryValuePlaceholderAndSuggestions.test.tsx +595 -0
  62. package/Tests/UI/Components/Icon.test.tsx +57 -0
  63. package/Tests/UI/Components/List.test.tsx +20 -3
  64. package/Tests/UI/Components/LogsSavedViewsDropdown.test.tsx +304 -0
  65. package/Tests/UI/Components/LogsViewerClearSavedView.test.tsx +179 -0
  66. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +349 -1
  67. package/Tests/UI/Components/Pagination.test.tsx +721 -146
  68. package/Tests/UI/Components/PaginationIntegration.test.tsx +386 -0
  69. package/Tests/UI/Components/PaginationUtil.test.ts +417 -0
  70. package/Tests/UI/Components/TelemetrySavedViewsDropdown.test.tsx +343 -0
  71. package/Tests/UI/Components/Workflow/ColumnEditor/ColumnControl.test.ts +289 -0
  72. package/Tests/UI/Components/Workflow/ColumnEditor/ColumnOperators.test.ts +271 -0
  73. package/Tests/UI/Components/Workflow/ColumnEditor/ColumnRow.test.ts +119 -0
  74. package/Tests/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.test.ts +968 -0
  75. package/Tests/UI/Components/Workflow/ModelColumnEditor.render.test.tsx +766 -0
  76. package/Tests/UI/Utils/RunNetworkRule.test.ts +320 -0
  77. package/Tests/Utils/APIRetryPolicy.test.ts +823 -0
  78. package/Tests/Utils/Monitor/NetworkDeviceRoleUtil.test.ts +502 -0
  79. package/Tests/Utils/Monitor/NetworkTopologyDeclaredHierarchy.test.ts +759 -0
  80. package/Tests/Utils/NetworkAutomation/RuleRunSummary.test.ts +286 -0
  81. package/Tests/Utils/Schema/BaseSchema.test.ts +651 -0
  82. package/Types/API/Protocol.ts +9 -0
  83. package/Types/API/URL.ts +286 -20
  84. package/Types/Billing/PayAsYouGoPricing.ts +47 -0
  85. package/Types/CustomField/CustomFieldType.ts +28 -0
  86. package/Types/Monitor/MonitorType.ts +11 -0
  87. package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
  88. package/Types/NetworkAutomation/RuleRunResult.ts +121 -0
  89. package/Types/Runbook/RunbookStepType.ts +23 -0
  90. package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +7 -0
  91. package/UI/Components/Button/Button.tsx +14 -22
  92. package/UI/Components/Charts/Area/AreaChart.tsx +4 -2
  93. package/UI/Components/Charts/Bar/BarChart.tsx +4 -2
  94. package/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.ts +13 -0
  95. package/UI/Components/Charts/Line/LineChart.tsx +4 -2
  96. package/UI/Components/Charts/Types/XAxis/XAxis.ts +6 -0
  97. package/UI/Components/Charts/Utils/DataPoint.ts +15 -12
  98. package/UI/Components/Dictionary/Dictionary.tsx +4 -39
  99. package/UI/Components/Icon/Icon.tsx +1 -1
  100. package/UI/Components/LogsViewer/LogsViewer.tsx +5 -1
  101. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +8 -0
  102. package/UI/Components/LogsViewer/components/LogsPagination.tsx +29 -91
  103. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +2 -0
  104. package/UI/Components/LogsViewer/components/SavedViewsDropdown.tsx +66 -1
  105. package/UI/Components/ModelTable/CustomFieldColumns.tsx +55 -0
  106. package/UI/Components/Pagination/Pagination.tsx +373 -328
  107. package/UI/Components/Pagination/PaginationUtil.ts +263 -0
  108. package/UI/Components/Pagination/Screenshots/README.md +15 -0
  109. package/UI/Components/Pagination/Screenshots/pagination-before-after.png +0 -0
  110. package/UI/Components/Pagination/Screenshots/pagination-go-to-page-modal.png +0 -0
  111. package/UI/Components/Pagination/Screenshots/pagination-long-list.png +0 -0
  112. package/UI/Components/Pagination/Screenshots/pagination-mobile.png +0 -0
  113. package/UI/Components/Pagination/Screenshots/pagination-telemetry.png +0 -0
  114. package/UI/Components/TelemetryViewer/components/SavedViewsDropdown.tsx +66 -1
  115. package/UI/Components/TelemetryViewer/components/TelemetryPagination.tsx +38 -92
  116. package/UI/Components/TextArea/TextArea.tsx +3 -0
  117. package/UI/Components/Workflow/ArgumentsForm.tsx +18 -1
  118. package/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.tsx +177 -0
  119. package/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.tsx +243 -0
  120. package/UI/Components/Workflow/ColumnEditor/ColumnControl.ts +255 -0
  121. package/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.tsx +178 -0
  122. package/UI/Components/Workflow/ColumnEditor/ColumnOperators.ts +206 -0
  123. package/UI/Components/Workflow/ColumnEditor/ColumnRow.ts +157 -0
  124. package/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.ts +517 -0
  125. package/UI/Components/Workflow/ColumnEditor/ColumnValueInput.tsx +482 -0
  126. package/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.tsx +131 -0
  127. package/UI/Components/Workflow/ColumnEditor/ModelRecordForm.tsx +163 -0
  128. package/UI/Components/Workflow/ModelColumnEditor.tsx +284 -100
  129. package/UI/Utils/NetworkAutomation/RunNetworkRule.ts +122 -0
  130. package/Utils/API.ts +243 -36
  131. package/Utils/Monitor/NetworkDeviceRoleUtil.ts +45 -0
  132. package/Utils/Monitor/NetworkTopologyUtil.ts +42 -2
  133. package/Utils/NetworkAutomation/RuleRunSummary.ts +147 -0
  134. package/build/dist/Models/DatabaseModels/NetworkDevice.js +45 -0
  135. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  136. package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js +115 -0
  137. package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js.map +1 -1
  138. package/build/dist/Models/DatabaseModels/RunnerJob.js +17 -2
  139. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  140. package/build/dist/Server/API/LlmProviderAPI.js +9 -1
  141. package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
  142. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.js +33 -0
  143. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.js.map +1 -0
  144. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  145. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  146. package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js +202 -0
  147. package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js.map +1 -1
  148. package/build/dist/Server/Services/NetworkDeviceLinkService.js +253 -1
  149. package/build/dist/Server/Services/NetworkDeviceLinkService.js.map +1 -1
  150. package/build/dist/Server/Services/NetworkDeviceService.js +223 -12
  151. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  152. package/build/dist/Server/Services/RunnerJobService.js +20 -2
  153. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  154. package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js +12 -5
  155. package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js.map +1 -1
  156. package/build/dist/Server/Types/Database/JSONColumnQuery.js +40 -4
  157. package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -1
  158. package/build/dist/Server/Utils/LLM/LLMService.js +78 -22
  159. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  160. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +22 -98
  161. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  162. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +7 -254
  163. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  164. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +14 -2
  165. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
  166. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +27 -0
  167. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
  168. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +285 -0
  169. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -0
  170. package/build/dist/Server/Utils/SSRFProtection.js +5 -4
  171. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  172. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +128 -35
  173. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
  174. package/build/dist/Types/API/Protocol.js +9 -0
  175. package/build/dist/Types/API/Protocol.js.map +1 -1
  176. package/build/dist/Types/API/URL.js +241 -18
  177. package/build/dist/Types/API/URL.js.map +1 -1
  178. package/build/dist/Types/Billing/PayAsYouGoPricing.js +41 -0
  179. package/build/dist/Types/Billing/PayAsYouGoPricing.js.map +1 -0
  180. package/build/dist/Types/CustomField/CustomFieldType.js +26 -0
  181. package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
  182. package/build/dist/Types/Monitor/MonitorType.js +10 -0
  183. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  184. package/build/dist/Types/NetworkAutomation/RuleRunResult.js +39 -0
  185. package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -0
  186. package/build/dist/Types/Runbook/RunbookStepType.js +21 -0
  187. package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
  188. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +1 -1
  189. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
  190. package/build/dist/UI/Components/Button/Button.js +10 -16
  191. package/build/dist/UI/Components/Button/Button.js.map +1 -1
  192. package/build/dist/UI/Components/Charts/Area/AreaChart.js +2 -1
  193. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  194. package/build/dist/UI/Components/Charts/Bar/BarChart.js +2 -1
  195. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  196. package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js +12 -0
  197. package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js.map +1 -1
  198. package/build/dist/UI/Components/Charts/Line/LineChart.js +2 -1
  199. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  200. package/build/dist/UI/Components/Charts/Utils/DataPoint.js +17 -10
  201. package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
  202. package/build/dist/UI/Components/Dictionary/Dictionary.js +4 -18
  203. package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
  204. package/build/dist/UI/Components/Icon/Icon.js +1 -1
  205. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  206. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +3 -3
  207. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  208. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +5 -1
  209. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  210. package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js +14 -44
  211. package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js.map +1 -1
  212. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
  213. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  214. package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js +20 -2
  215. package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js.map +1 -1
  216. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +42 -0
  217. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
  218. package/build/dist/UI/Components/Pagination/Pagination.js +174 -199
  219. package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
  220. package/build/dist/UI/Components/Pagination/PaginationUtil.js +160 -0
  221. package/build/dist/UI/Components/Pagination/PaginationUtil.js.map +1 -0
  222. package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js +20 -2
  223. package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js.map +1 -1
  224. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js +23 -47
  225. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js.map +1 -1
  226. package/build/dist/UI/Components/TextArea/TextArea.js +1 -1
  227. package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
  228. package/build/dist/UI/Components/Workflow/ArgumentsForm.js +14 -2
  229. package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
  230. package/build/dist/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.js +86 -0
  231. package/build/dist/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.js.map +1 -0
  232. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.js +119 -0
  233. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.js.map +1 -0
  234. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnControl.js +206 -0
  235. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnControl.js.map +1 -0
  236. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.js +57 -0
  237. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.js.map +1 -0
  238. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnOperators.js +150 -0
  239. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnOperators.js.map +1 -0
  240. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRow.js +67 -0
  241. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRow.js.map +1 -0
  242. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.js +309 -0
  243. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.js.map +1 -0
  244. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnValueInput.js +204 -0
  245. package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnValueInput.js.map +1 -0
  246. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js +66 -0
  247. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js.map +1 -0
  248. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelRecordForm.js +83 -0
  249. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelRecordForm.js.map +1 -0
  250. package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +187 -68
  251. package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -1
  252. package/build/dist/UI/Utils/NetworkAutomation/RunNetworkRule.js +75 -0
  253. package/build/dist/UI/Utils/NetworkAutomation/RunNetworkRule.js.map +1 -0
  254. package/build/dist/Utils/API.js +175 -34
  255. package/build/dist/Utils/API.js.map +1 -1
  256. package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +37 -0
  257. package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -1
  258. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +25 -3
  259. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  260. package/build/dist/Utils/NetworkAutomation/RuleRunSummary.js +78 -0
  261. package/build/dist/Utils/NetworkAutomation/RuleRunSummary.js.map +1 -0
  262. package/package.json +1 -1
@@ -0,0 +1,595 @@
1
+ import DictionaryForm, {
2
+ ComponentProps,
3
+ ValueType,
4
+ } from "../../../UI/Components/Dictionary/Dictionary";
5
+ import { DictionaryEntryValue } from "../../../UI/Components/Dictionary/DictionaryFilterOperator";
6
+ import Includes from "../../../Types/BaseDatabase/Includes";
7
+ import Dictionary from "../../../Types/Dictionary";
8
+ /*
9
+ * The main entry, not "/extend-expect": the latter no longer ships type
10
+ * declarations, so every jest-dom matcher in this file fails to typecheck and
11
+ * the whole suite is skipped before a single assertion runs.
12
+ */
13
+ import "@testing-library/jest-dom";
14
+ import { cleanup, fireEvent, render, screen } from "@testing-library/react";
15
+ import React from "react";
16
+ import { afterEach, describe, expect, test } from "@jest/globals";
17
+
18
+ /*
19
+ * What a DictionaryForm row offers the user in its value box comes from exactly
20
+ * two props: `valuePlaceholder` (one hint for the whole form) and
21
+ * `valueSuggestions` (a list per key). There used to be two more —
22
+ * `valuePlaceholders`, a per-key placeholder overriding the form-wide one, and
23
+ * `defaultValueSuggestions`, a list offered on any row whose key had no list of
24
+ * its own. Both existed only for the workflow record editor, which now renders
25
+ * its own schema-driven editor and never mounts this component, so both were
26
+ * removed along with the merging logic behind them.
27
+ *
28
+ * These tests pin what survives that removal, and they are written so that
29
+ * re-introducing either fallback fails them:
30
+ *
31
+ * - the placeholder is form-wide, so two rows with different keys show the
32
+ * same hint, and a row shows it whether or not its key is known;
33
+ * - suggestions are strictly per key, so a key with no list — or an empty
34
+ * one, or no key typed yet — offers nothing at all rather than falling back
35
+ * to some other list.
36
+ *
37
+ * Everything else here is the ordinary behaviour of the value box those two
38
+ * props drive: filtering, picking, switching key, the multi-select an
39
+ * "is any of" operator turns the box into, the loading row, and the
40
+ * `onValueSearch` callback that lets a parent refine the list server-side.
41
+ */
42
+
43
+ const VALUE_PLACEHOLDER: string = "Attribute value";
44
+ const KEY_PLACEHOLDER: string = "Attribute key";
45
+ const ADD_BUTTON_TITLE: string = "Add Attribute";
46
+
47
+ const VALUE_SUGGESTIONS: Record<string, Array<string>> = {
48
+ env: ["production", "staging", "development"],
49
+ region: ["us-east-1", "eu-west-2"],
50
+ // A key that is known but has no values to offer yet.
51
+ team: [],
52
+ };
53
+
54
+ interface RenderResult {
55
+ emitted: Array<Dictionary<DictionaryEntryValue>>;
56
+ searches: Array<{ key: string; searchText: string }>;
57
+ lastEmitted: () => Dictionary<DictionaryEntryValue> | undefined;
58
+ }
59
+
60
+ type RenderDictionaryFunction = (
61
+ overrides?: Partial<ComponentProps>,
62
+ ) => RenderResult;
63
+
64
+ const renderDictionary: RenderDictionaryFunction = (
65
+ overrides: Partial<ComponentProps> = {},
66
+ ): RenderResult => {
67
+ const emitted: Array<Dictionary<DictionaryEntryValue>> = [];
68
+ const searches: Array<{ key: string; searchText: string }> = [];
69
+
70
+ render(
71
+ <DictionaryForm
72
+ addButtonSuffix="Attribute"
73
+ keyPlaceholder={KEY_PLACEHOLDER}
74
+ valuePlaceholder={VALUE_PLACEHOLDER}
75
+ valueSuggestions={VALUE_SUGGESTIONS}
76
+ onChange={(value: Dictionary<DictionaryEntryValue>) => {
77
+ emitted.push(value);
78
+ }}
79
+ onValueSearch={(key: string, searchText: string) => {
80
+ searches.push({ key: key, searchText: searchText });
81
+ }}
82
+ {...overrides}
83
+ />,
84
+ );
85
+
86
+ return {
87
+ emitted: emitted,
88
+ searches: searches,
89
+ lastEmitted: () => {
90
+ return emitted.length > 0 ? emitted[emitted.length - 1] : undefined;
91
+ },
92
+ };
93
+ };
94
+
95
+ /*
96
+ * Rows are the only `items-start` flex containers the form renders, so they are
97
+ * the one stable handle on "this row's inputs" — indexing a flat list of inputs
98
+ * breaks as soon as a row changes shape (a Number row has one autocomplete, a
99
+ * Text row two).
100
+ */
101
+ type RowsFunction = () => Array<HTMLElement>;
102
+
103
+ const rows: RowsFunction = (): Array<HTMLElement> => {
104
+ return Array.from(
105
+ document.querySelectorAll<HTMLElement>("div.flex.items-start"),
106
+ );
107
+ };
108
+
109
+ /*
110
+ * AutocompleteTextInput and react-select both report role="combobox", so the
111
+ * only stable way to single out the hand-rolled ones is the listbox they own.
112
+ */
113
+ type AutocompletesInRowFunction = (rowIndex: number) => Array<HTMLInputElement>;
114
+
115
+ const autocompletesInRow: AutocompletesInRowFunction = (
116
+ rowIndex: number,
117
+ ): Array<HTMLInputElement> => {
118
+ return Array.from(
119
+ rows()[rowIndex]!.querySelectorAll<HTMLInputElement>(
120
+ 'input[aria-controls^="autocomplete-suggestions-"]',
121
+ ),
122
+ );
123
+ };
124
+
125
+ type RowInputFunction = (rowIndex?: number) => HTMLInputElement;
126
+
127
+ const keyInput: RowInputFunction = (rowIndex: number = 0): HTMLInputElement => {
128
+ return autocompletesInRow(rowIndex)[0]!;
129
+ };
130
+
131
+ const valueInput: RowInputFunction = (
132
+ rowIndex: number = 0,
133
+ ): HTMLInputElement => {
134
+ return autocompletesInRow(rowIndex)[1]!;
135
+ };
136
+
137
+ type NumberInputFunction = () => HTMLInputElement;
138
+
139
+ const numberInput: NumberInputFunction = (): HTMLInputElement => {
140
+ return document.querySelector<HTMLInputElement>('input[type="number"]')!;
141
+ };
142
+
143
+ /*
144
+ * Column order inside a row: key autocomplete, then the operator select when
145
+ * operators are enabled, then the value control. The value control is always
146
+ * the last combobox in the row, whichever shape it currently has.
147
+ */
148
+ type RowComboboxesFunction = (rowIndex: number) => Array<HTMLElement>;
149
+
150
+ const rowComboboxes: RowComboboxesFunction = (
151
+ rowIndex: number,
152
+ ): Array<HTMLElement> => {
153
+ return Array.from(
154
+ rows()[rowIndex]!.querySelectorAll<HTMLElement>('[role="combobox"]'),
155
+ );
156
+ };
157
+
158
+ type RowComboboxFunction = (rowIndex?: number) => HTMLElement;
159
+
160
+ const operatorSelect: RowComboboxFunction = (
161
+ rowIndex: number = 0,
162
+ ): HTMLElement => {
163
+ return rowComboboxes(rowIndex)[1]!;
164
+ };
165
+
166
+ const valueSelect: RowComboboxFunction = (
167
+ rowIndex: number = 0,
168
+ ): HTMLElement => {
169
+ const comboboxes: Array<HTMLElement> = rowComboboxes(rowIndex);
170
+ return comboboxes[comboboxes.length - 1]!;
171
+ };
172
+
173
+ /*
174
+ * react-select opens on ArrowDown and its options are portalled to
175
+ * document.body, so they are reachable by role from `screen` but never from
176
+ * inside the form's own subtree.
177
+ */
178
+ type SelectOptionFunction = (combobox: HTMLElement, optionText: string) => void;
179
+
180
+ const selectOption: SelectOptionFunction = (
181
+ combobox: HTMLElement,
182
+ optionText: string,
183
+ ): void => {
184
+ fireEvent.keyDown(combobox, { key: "ArrowDown" });
185
+ const option: HTMLElement = screen.getByText(optionText);
186
+ fireEvent.mouseDown(option);
187
+ fireEvent.click(option);
188
+ };
189
+
190
+ /*
191
+ * Both the autocomplete menu and react-select's menu report role="option", and
192
+ * only one of them is ever open at a time in these tests.
193
+ */
194
+ type OpenOptionsFunction = () => Array<string>;
195
+
196
+ const openOptions: OpenOptionsFunction = (): Array<string> => {
197
+ return screen.queryAllByRole("option").map((option: HTMLElement) => {
198
+ return option.textContent || "";
199
+ });
200
+ };
201
+
202
+ type AddRowFunction = () => void;
203
+
204
+ const addRow: AddRowFunction = (): void => {
205
+ fireEvent.click(screen.getByText(ADD_BUTTON_TITLE));
206
+ };
207
+
208
+ type TypeKeyFunction = (key: string, rowIndex?: number) => void;
209
+
210
+ const typeKey: TypeKeyFunction = (key: string, rowIndex: number = 0): void => {
211
+ fireEvent.change(keyInput(rowIndex), { target: { value: key } });
212
+ };
213
+
214
+ type AddRowWithKeyFunction = (key: string) => void;
215
+
216
+ const addRowWithKey: AddRowWithKeyFunction = (key: string): void => {
217
+ addRow();
218
+ typeKey(key, rows().length - 1);
219
+ };
220
+
221
+ describe("Dictionary — the value box", () => {
222
+ afterEach(() => {
223
+ cleanup();
224
+ });
225
+
226
+ describe("the value placeholder", () => {
227
+ test("shows the form-wide placeholder on a row with a known key", () => {
228
+ renderDictionary();
229
+ addRowWithKey("env");
230
+
231
+ expect(valueInput().getAttribute("placeholder")).toBe(VALUE_PLACEHOLDER);
232
+ });
233
+
234
+ test("shows it on a row whose key is still blank", () => {
235
+ renderDictionary();
236
+ addRow();
237
+
238
+ expect(valueInput().getAttribute("placeholder")).toBe(VALUE_PLACEHOLDER);
239
+ });
240
+
241
+ test("shows it on a row whose key has no suggestions of its own", () => {
242
+ renderDictionary();
243
+ addRowWithKey("some.unknown.attribute");
244
+
245
+ expect(valueInput().getAttribute("placeholder")).toBe(VALUE_PLACEHOLDER);
246
+ });
247
+
248
+ /*
249
+ * The pin for the removed per-key `valuePlaceholders`: the hint is a
250
+ * property of the form, not of the column, so two rows sitting on different
251
+ * keys read exactly the same.
252
+ */
253
+ test("is the same on every row, whatever each row's key is", () => {
254
+ renderDictionary();
255
+ addRowWithKey("env");
256
+ addRowWithKey("region");
257
+
258
+ expect(valueInput(0).getAttribute("placeholder")).toBe(VALUE_PLACEHOLDER);
259
+ expect(valueInput(1).getAttribute("placeholder")).toBe(VALUE_PLACEHOLDER);
260
+ });
261
+
262
+ test("does not leak into the key input, which keeps its own", () => {
263
+ renderDictionary();
264
+ addRowWithKey("env");
265
+
266
+ expect(keyInput().getAttribute("placeholder")).toBe(KEY_PLACEHOLDER);
267
+ });
268
+
269
+ test("a Number row uses it too", () => {
270
+ renderDictionary({
271
+ valueTypes: [ValueType.Text, ValueType.Number],
272
+ initialValue: { latency: 200 },
273
+ });
274
+
275
+ expect(numberInput().getAttribute("placeholder")).toBe(VALUE_PLACEHOLDER);
276
+ });
277
+
278
+ test("an 'is any of' row uses it as the multi-select's placeholder", () => {
279
+ renderDictionary({ enableOperators: true });
280
+ addRowWithKey("env");
281
+
282
+ selectOption(operatorSelect(), "is any of");
283
+
284
+ expect(screen.getByText(VALUE_PLACEHOLDER)).toBeInTheDocument();
285
+ });
286
+
287
+ test("a numeric operator overrides it with 'Number'", () => {
288
+ renderDictionary({ enableOperators: true });
289
+ addRowWithKey("env");
290
+
291
+ selectOption(operatorSelect(), ">");
292
+
293
+ expect(valueInput().getAttribute("placeholder")).toBe("Number");
294
+ });
295
+
296
+ test("a value-less operator labels the inert box with the operator", () => {
297
+ renderDictionary({ enableOperators: true });
298
+ addRowWithKey("env");
299
+
300
+ selectOption(operatorSelect(), "is empty");
301
+
302
+ const inertInput: HTMLInputElement = screen.getByPlaceholderText(
303
+ "is empty",
304
+ ) as HTMLInputElement;
305
+ expect(inertInput.readOnly).toBe(true);
306
+ expect(screen.queryByPlaceholderText(VALUE_PLACEHOLDER)).toBeNull();
307
+ });
308
+
309
+ test("with no placeholder prop the value input simply has none", () => {
310
+ /*
311
+ * Rendered directly rather than through renderDictionary: the prop is
312
+ * declared `valuePlaceholder?: string`, and under
313
+ * exactOptionalPropertyTypes passing an explicit `undefined` is not the
314
+ * same as leaving it off.
315
+ */
316
+ render(
317
+ <DictionaryForm
318
+ addButtonSuffix="Attribute"
319
+ keyPlaceholder={KEY_PLACEHOLDER}
320
+ valueSuggestions={VALUE_SUGGESTIONS}
321
+ />,
322
+ );
323
+ addRowWithKey("env");
324
+
325
+ expect(valueInput().getAttribute("placeholder")).toBeNull();
326
+ });
327
+ });
328
+
329
+ describe("per-key value suggestions", () => {
330
+ test("focusing the value box offers that key's values", () => {
331
+ renderDictionary();
332
+ addRowWithKey("env");
333
+
334
+ fireEvent.focus(valueInput());
335
+
336
+ expect(openOptions()).toEqual(["production", "staging", "development"]);
337
+ });
338
+
339
+ test("a different key offers a different set", () => {
340
+ renderDictionary();
341
+ addRowWithKey("region");
342
+
343
+ fireEvent.focus(valueInput());
344
+
345
+ expect(openOptions()).toEqual(["us-east-1", "eu-west-2"]);
346
+ });
347
+
348
+ /*
349
+ * The pin for the removed `defaultValueSuggestions`: a key nobody has
350
+ * values for offers nothing, rather than falling back to a form-wide list.
351
+ */
352
+ test("a key with no list of its own offers nothing", () => {
353
+ renderDictionary();
354
+ addRowWithKey("some.unknown.attribute");
355
+
356
+ fireEvent.focus(valueInput());
357
+
358
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
359
+ expect(openOptions()).toEqual([]);
360
+ });
361
+
362
+ test("a key whose list is empty offers nothing", () => {
363
+ renderDictionary();
364
+ addRowWithKey("team");
365
+
366
+ fireEvent.focus(valueInput());
367
+
368
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
369
+ expect(openOptions()).toEqual([]);
370
+ });
371
+
372
+ test("a row with no key yet offers nothing", () => {
373
+ renderDictionary();
374
+ addRow();
375
+
376
+ fireEvent.focus(valueInput());
377
+
378
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
379
+ expect(openOptions()).toEqual([]);
380
+ });
381
+
382
+ test("with no valueSuggestions prop at all, nothing is offered", () => {
383
+ renderDictionary({ valueSuggestions: undefined });
384
+ addRowWithKey("env");
385
+
386
+ fireEvent.focus(valueInput());
387
+
388
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
389
+ expect(openOptions()).toEqual([]);
390
+ });
391
+
392
+ test("typing narrows the list to matching values", () => {
393
+ renderDictionary();
394
+ addRowWithKey("env");
395
+
396
+ fireEvent.change(valueInput(), { target: { value: "st" } });
397
+
398
+ expect(openOptions()).toEqual(["staging"]);
399
+ });
400
+
401
+ test("picking a value fills the box and emits it", () => {
402
+ const rendered: RenderResult = renderDictionary();
403
+ addRowWithKey("env");
404
+
405
+ fireEvent.focus(valueInput());
406
+ fireEvent.click(screen.getByText("staging"));
407
+
408
+ expect(valueInput().value).toBe("staging");
409
+ expect(rendered.lastEmitted()).toEqual({ env: "staging" });
410
+ });
411
+
412
+ test("changing the key swaps the suggestion set", () => {
413
+ renderDictionary();
414
+ addRowWithKey("env");
415
+
416
+ fireEvent.focus(valueInput());
417
+ expect(openOptions()).toContain("production");
418
+
419
+ typeKey("region");
420
+ fireEvent.focus(valueInput());
421
+
422
+ expect(openOptions()).toEqual(["us-east-1", "eu-west-2"]);
423
+ });
424
+
425
+ test("changing to a key with no list closes the offer", () => {
426
+ renderDictionary();
427
+ addRowWithKey("env");
428
+
429
+ fireEvent.focus(valueInput());
430
+ expect(openOptions()).toContain("production");
431
+
432
+ typeKey("some.unknown.attribute");
433
+
434
+ expect(openOptions()).toEqual([]);
435
+ });
436
+
437
+ test("each row is offered its own key's values", () => {
438
+ renderDictionary();
439
+ addRowWithKey("env");
440
+ addRowWithKey("region");
441
+
442
+ fireEvent.focus(valueInput(0));
443
+ expect(openOptions()).toEqual(["production", "staging", "development"]);
444
+
445
+ fireEvent.blur(valueInput(0));
446
+ fireEvent.keyDown(valueInput(0), { key: "Escape" });
447
+
448
+ fireEvent.focus(valueInput(1));
449
+ expect(openOptions()).toContain("us-east-1");
450
+ expect(openOptions()).not.toContain("production");
451
+ });
452
+
453
+ test("suggestions do not stop the user typing a value of their own", () => {
454
+ const rendered: RenderResult = renderDictionary();
455
+ addRowWithKey("env");
456
+
457
+ fireEvent.change(valueInput(), { target: { value: "canary" } });
458
+
459
+ expect(valueInput().value).toBe("canary");
460
+ expect(rendered.lastEmitted()).toEqual({ env: "canary" });
461
+ });
462
+
463
+ test("the key input keeps offering keys, not values", () => {
464
+ renderDictionary({ keys: ["env", "region"] });
465
+ addRow();
466
+
467
+ fireEvent.focus(keyInput());
468
+
469
+ expect(openOptions()).toEqual(["env", "region"]);
470
+ });
471
+ });
472
+
473
+ describe("the multi-select an 'is any of' row turns the value box into", () => {
474
+ test("offers the key's values as its options", () => {
475
+ renderDictionary({ enableOperators: true });
476
+ addRowWithKey("env");
477
+
478
+ selectOption(operatorSelect(), "is any of");
479
+ fireEvent.keyDown(valueSelect(), { key: "ArrowDown" });
480
+
481
+ expect(openOptions()).toEqual(["production", "staging", "development"]);
482
+ });
483
+
484
+ test("offers nothing for a key with no list of its own", () => {
485
+ renderDictionary({ enableOperators: true });
486
+ addRowWithKey("some.unknown.attribute");
487
+
488
+ selectOption(operatorSelect(), "is any of");
489
+ fireEvent.keyDown(valueSelect(), { key: "ArrowDown" });
490
+
491
+ expect(openOptions()).toEqual([]);
492
+ });
493
+
494
+ test("emits the picked values as an Includes", () => {
495
+ const rendered: RenderResult = renderDictionary({
496
+ enableOperators: true,
497
+ });
498
+ addRowWithKey("env");
499
+
500
+ selectOption(operatorSelect(), "is any of");
501
+ selectOption(valueSelect(), "production");
502
+
503
+ const emittedValue: DictionaryEntryValue =
504
+ rendered.lastEmitted()!["env"]!;
505
+ expect(emittedValue).toBeInstanceOf(Includes);
506
+ expect((emittedValue as Includes).values).toEqual(["production"]);
507
+ });
508
+
509
+ test("a picked value becomes a chip and leaves the rest on offer", () => {
510
+ renderDictionary({ enableOperators: true });
511
+ addRowWithKey("env");
512
+
513
+ selectOption(operatorSelect(), "is any of");
514
+ selectOption(valueSelect(), "production");
515
+
516
+ // The picked value is now a chip, so the menu offers only what is left.
517
+ expect(screen.getByText("production")).toBeInTheDocument();
518
+
519
+ fireEvent.keyDown(valueSelect(), { key: "ArrowDown" });
520
+
521
+ expect(openOptions()).toEqual(["staging", "development"]);
522
+ });
523
+
524
+ test("a stored value that is no longer suggested is still shown", () => {
525
+ /*
526
+ * Saved filters outlive the suggestion list they were picked from — the
527
+ * options are the union of the key's suggestions and whatever the row
528
+ * already holds, so a value the server no longer returns still renders
529
+ * rather than silently vanishing from the user's filter.
530
+ */
531
+ renderDictionary({
532
+ enableOperators: true,
533
+ initialValue: { env: new Includes(["retired-environment"]) },
534
+ });
535
+
536
+ expect(screen.getByText("retired-environment")).toBeInTheDocument();
537
+ });
538
+ });
539
+
540
+ describe("loading values from the server", () => {
541
+ test("a key being loaded shows the loading row", () => {
542
+ renderDictionary({
543
+ valueSuggestions: {},
544
+ loadingValueKeys: ["env"],
545
+ });
546
+ addRowWithKey("env");
547
+
548
+ fireEvent.focus(valueInput());
549
+
550
+ expect(screen.getByText("Loading values...")).toBeInTheDocument();
551
+ });
552
+
553
+ test("only the row whose key is loading shows it", () => {
554
+ renderDictionary({
555
+ valueSuggestions: {},
556
+ loadingValueKeys: ["env"],
557
+ });
558
+ addRowWithKey("region");
559
+
560
+ fireEvent.focus(valueInput());
561
+
562
+ expect(screen.queryByText("Loading values...")).not.toBeInTheDocument();
563
+ });
564
+
565
+ test("typing a value asks the parent to refine the list", () => {
566
+ const rendered: RenderResult = renderDictionary();
567
+ addRowWithKey("env");
568
+
569
+ fireEvent.change(valueInput(), { target: { value: "prod" } });
570
+
571
+ expect(rendered.searches).toEqual([{ key: "env", searchText: "prod" }]);
572
+ });
573
+
574
+ test("a row with no key does not ask", () => {
575
+ const rendered: RenderResult = renderDictionary();
576
+ addRow();
577
+
578
+ fireEvent.change(valueInput(), { target: { value: "prod" } });
579
+
580
+ expect(rendered.searches).toEqual([]);
581
+ });
582
+
583
+ test("a numeric operator does not ask — it has no values to narrow", () => {
584
+ const rendered: RenderResult = renderDictionary({
585
+ enableOperators: true,
586
+ });
587
+ addRowWithKey("env");
588
+
589
+ selectOption(operatorSelect(), ">");
590
+ fireEvent.change(valueInput(), { target: { value: "500" } });
591
+
592
+ expect(rendered.searches).toEqual([]);
593
+ });
594
+ });
595
+ });
@@ -0,0 +1,57 @@
1
+ import Icon from "../../../UI/Components/Icon/Icon";
2
+ import IconProp from "../../../Types/Icon/IconProp";
3
+ import { describe, expect, it } from "@jest/globals";
4
+ import "@testing-library/jest-dom";
5
+ import { render } from "@testing-library/react";
6
+ import React from "react";
7
+
8
+ type GetIconPathFunction = (icon: IconProp) => string;
9
+
10
+ const getIconPath: GetIconPathFunction = (icon: IconProp): string => {
11
+ const { container } = render(<Icon icon={icon} />);
12
+ return container.querySelector("path")?.getAttribute("d") || "";
13
+ };
14
+
15
+ /*
16
+ * The chevrons are the whole vocabulary of the pagination arrows and of every
17
+ * "back" button in the dashboard, and they are picked out of one long
18
+ * if/else chain of hand-written path data - which is exactly how ChevronLeft
19
+ * came to be drawing the bulleted-list glyph.
20
+ */
21
+ describe("Icon chevrons", () => {
22
+ it("draws a left-pointing chevron for ChevronLeft", () => {
23
+ expect(getIconPath(IconProp.ChevronLeft)).toBe(
24
+ "M15.75 19.5L8.25 12l7.5-7.5",
25
+ );
26
+ });
27
+
28
+ it("draws a right-pointing chevron for ChevronRight", () => {
29
+ expect(getIconPath(IconProp.ChevronRight)).toBe(
30
+ "M8.25 4.5l7.5 7.5-7.5 7.5",
31
+ );
32
+ });
33
+
34
+ it("draws a down-pointing chevron for ChevronDown", () => {
35
+ expect(getIconPath(IconProp.ChevronDown)).toBe(
36
+ "M19.5 8.25l-7.5 7.5-7.5-7.5",
37
+ );
38
+ });
39
+
40
+ it("does not draw a list where a chevron was asked for", () => {
41
+ const listPath: string = getIconPath(IconProp.List);
42
+
43
+ expect(getIconPath(IconProp.ChevronLeft)).not.toBe(listPath);
44
+ expect(getIconPath(IconProp.ChevronRight)).not.toBe(listPath);
45
+ expect(getIconPath(IconProp.ChevronDown)).not.toBe(listPath);
46
+ });
47
+
48
+ it("gives each chevron its own direction", () => {
49
+ const paths: Array<string> = [
50
+ getIconPath(IconProp.ChevronLeft),
51
+ getIconPath(IconProp.ChevronRight),
52
+ getIconPath(IconProp.ChevronDown),
53
+ ];
54
+
55
+ expect(new Set(paths).size).toBe(paths.length);
56
+ });
57
+ });
@@ -92,10 +92,27 @@ describe("List", () => {
92
92
 
93
93
  it("handles onNavigateToPage callback", () => {
94
94
  render(<List {...defaultProps} />);
95
- // There are multiple "Next" elements (mobile and desktop), get the first one
96
- const nextButtons: HTMLElement[] = screen.getAllByText("Next");
97
- fireEvent.click(nextButtons[0]!);
95
+
96
+ fireEvent.click(screen.getByTestId("pagination-next-button"));
97
+
98
+ expect(defaultProps.onNavigateToPage).toHaveBeenCalledWith(2, 5);
99
+ });
100
+
101
+ it("jumps straight to a page from the list footer", () => {
102
+ render(<List {...defaultProps} />);
103
+
104
+ fireEvent.click(screen.getByTestId("pagination-page-2"));
98
105
 
99
106
  expect(defaultProps.onNavigateToPage).toHaveBeenCalledWith(2, 5);
100
107
  });
108
+
109
+ it("changes the page size from the list footer", () => {
110
+ render(<List {...defaultProps} />);
111
+
112
+ fireEvent.change(screen.getByTestId("pagination-items-on-page-select"), {
113
+ target: { value: "25" },
114
+ });
115
+
116
+ expect(defaultProps.onNavigateToPage).toHaveBeenCalledWith(1, 25);
117
+ });
101
118
  });