@gen-epix/ui 1.15.0 → 1.15.2

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 (738) hide show
  1. package/dist/environment.d.ts +54 -0
  2. package/dist/index.d.ts +16078 -0
  3. package/dist/index.js.map +1 -0
  4. package/package.json +13 -16
  5. package/src/@types/environment.d.ts +0 -54
  6. package/src/api/api.ts +0 -43576
  7. package/src/api/base.ts +0 -111
  8. package/src/api/common.ts +0 -168
  9. package/src/api/configuration.ts +0 -118
  10. package/src/api/index.ts +0 -20
  11. package/src/assets/icons/CollectionIcon.svg +0 -1
  12. package/src/assets/icons/DnaIcon.svg +0 -1
  13. package/src/assets/icons/VirusIcon.svg +0 -1
  14. package/src/assets/logo/logo-rijksoverheid.svg +0 -42
  15. package/src/assets/logo/logo-rivm.svg +0 -6
  16. package/src/classes/Subject/Subject.ts +0 -26
  17. package/src/classes/Subject/index.ts +0 -1
  18. package/src/classes/TableEventBus/TableEventBus.ts +0 -14
  19. package/src/classes/TableEventBus/index.ts +0 -1
  20. package/src/classes/abstracts/EventBusAbstract/EventBusAbstract.ts +0 -37
  21. package/src/classes/abstracts/EventBusAbstract/index.ts +0 -1
  22. package/src/classes/abstracts/FilterAbstract/FilterAbstract.ts +0 -54
  23. package/src/classes/abstracts/FilterAbstract/index.ts +0 -1
  24. package/src/classes/abstracts/SubscribableAbstract/SubscribableAbstract.ts +0 -20
  25. package/src/classes/abstracts/SubscribableAbstract/index.ts +0 -1
  26. package/src/classes/errors/UploadError.ts +0 -11
  27. package/src/classes/errors/index.ts +0 -1
  28. package/src/classes/filters/BooleanFilter.ts +0 -31
  29. package/src/classes/filters/DateFilter.ts +0 -133
  30. package/src/classes/filters/GeoFilter.ts +0 -60
  31. package/src/classes/filters/MultiSelectFilter.ts +0 -70
  32. package/src/classes/filters/NumberRangeFilter.ts +0 -82
  33. package/src/classes/filters/SelectionFilter.ts +0 -25
  34. package/src/classes/filters/TextFilter.ts +0 -36
  35. package/src/classes/filters/TreeFilter.ts +0 -22
  36. package/src/classes/managers/AuthenticationManager/AuthenticationManager.ts +0 -94
  37. package/src/classes/managers/AuthenticationManager/index.ts +0 -1
  38. package/src/classes/managers/AuthorizationManager/AuthorizationManager.ts +0 -70
  39. package/src/classes/managers/AuthorizationManager/index.ts +0 -1
  40. package/src/classes/managers/BackendVersionManager/BackendVersionManager.ts +0 -23
  41. package/src/classes/managers/BackendVersionManager/index.ts +0 -1
  42. package/src/classes/managers/BreadcrumbManager/BreadcrumbManager.ts +0 -30
  43. package/src/classes/managers/BreadcrumbManager/index.ts +0 -1
  44. package/src/classes/managers/ConfigManager/ConfigManager.ts +0 -31
  45. package/src/classes/managers/ConfigManager/index.ts +0 -1
  46. package/src/classes/managers/DevicePixelRatioManager/DevicePixelRatioManager.ts +0 -27
  47. package/src/classes/managers/DevicePixelRatioManager/index.ts +0 -1
  48. package/src/classes/managers/EmotionCacheManager/EmotionCacheManager.ts +0 -27
  49. package/src/classes/managers/EmotionCacheManager/index.ts +0 -1
  50. package/src/classes/managers/EpiDataManager/EpiDataManager.ts +0 -196
  51. package/src/classes/managers/EpiDataManager/index.ts +0 -1
  52. package/src/classes/managers/EpiEventBusManager/EpiEventBusManager.ts +0 -61
  53. package/src/classes/managers/EpiEventBusManager/index.ts +0 -1
  54. package/src/classes/managers/EpiHighlightingManager/EpiHighlightingManager.ts +0 -32
  55. package/src/classes/managers/EpiHighlightingManager/index.ts +0 -1
  56. package/src/classes/managers/EpiLineListCaseSetMembersManager/EpiListsCaseSetMembersManager.ts +0 -118
  57. package/src/classes/managers/EpiLineListCaseSetMembersManager/index.ts +0 -1
  58. package/src/classes/managers/FeatureFlagsManager/FeatureFlagsManager.ts +0 -30
  59. package/src/classes/managers/FeatureFlagsManager/index.ts +0 -1
  60. package/src/classes/managers/I18nManager/I18nManager.ts +0 -105
  61. package/src/classes/managers/I18nManager/index.ts +0 -1
  62. package/src/classes/managers/InactivityManager/InactivityManager.ts +0 -119
  63. package/src/classes/managers/InactivityManager/index.ts +0 -1
  64. package/src/classes/managers/KeyboardShortcutManager/KeyboardShortcutManager.ts +0 -78
  65. package/src/classes/managers/KeyboardShortcutManager/index.ts +0 -1
  66. package/src/classes/managers/LogManager/LogManager.ts +0 -151
  67. package/src/classes/managers/LogManager/index.ts +0 -1
  68. package/src/classes/managers/NavigationHistoryManager/NavigationHistoryManager.ts +0 -16
  69. package/src/classes/managers/NavigationHistoryManager/index.ts +0 -1
  70. package/src/classes/managers/NotificationManager/NotificationManager.ts +0 -104
  71. package/src/classes/managers/NotificationManager/index.ts +0 -1
  72. package/src/classes/managers/PageEventBusManager/PageEventBusManager.ts +0 -116
  73. package/src/classes/managers/PageEventBusManager/index.ts +0 -1
  74. package/src/classes/managers/QueryClientManager/QueryClientManager.ts +0 -51
  75. package/src/classes/managers/QueryClientManager/index.ts +0 -1
  76. package/src/classes/managers/RouterManager/RouterManager.ts +0 -25
  77. package/src/classes/managers/RouterManager/index.ts +0 -1
  78. package/src/classes/managers/UserSettingsManager/UserSettingsManager.ts +0 -16
  79. package/src/classes/managers/UserSettingsManager/index.ts +0 -1
  80. package/src/classes/managers/WindowManager/WindowManager.test.ts +0 -23
  81. package/src/classes/managers/WindowManager/WindowManager.ts +0 -26
  82. package/src/classes/managers/WindowManager/index.ts +0 -1
  83. package/src/components/app/App/App.tsx +0 -58
  84. package/src/components/app/App/index.ts +0 -1
  85. package/src/components/app/ApplicationBootstrap/ApplicationBootstrap.tsx +0 -223
  86. package/src/components/app/ApplicationBootstrap/index.ts +0 -1
  87. package/src/components/app/RouterRoot/RouterRoot.tsx +0 -187
  88. package/src/components/app/RouterRoot/index.ts +0 -1
  89. package/src/components/epi/EpiAddCasesToEventDialog/EpiAddCasesToEventDialog.tsx +0 -347
  90. package/src/components/epi/EpiAddCasesToEventDialog/EpiAddCasesToEventDialogSuccessNotificationMessage.tsx +0 -38
  91. package/src/components/epi/EpiAddCasesToEventDialog/index.ts +0 -1
  92. package/src/components/epi/EpiBulkEditCaseDialog/EpiBulkEditCaseDialog.tsx +0 -61
  93. package/src/components/epi/EpiBulkEditCaseDialog/index.ts +0 -1
  94. package/src/components/epi/EpiCaseInfoDialog/EpiCaseCaseSetInfo.tsx +0 -137
  95. package/src/components/epi/EpiCaseInfoDialog/EpiCaseContent.tsx +0 -127
  96. package/src/components/epi/EpiCaseInfoDialog/EpiCaseForm.tsx +0 -111
  97. package/src/components/epi/EpiCaseInfoDialog/EpiCaseInfoDialog.tsx +0 -377
  98. package/src/components/epi/EpiCaseInfoDialog/EpiCaseSharingForm.tsx +0 -140
  99. package/src/components/epi/EpiCaseInfoDialog/EpiCaseSharingInfo.tsx +0 -23
  100. package/src/components/epi/EpiCaseInfoDialog/EpiReadOnlyCaseContent.tsx +0 -89
  101. package/src/components/epi/EpiCaseInfoDialog/index.ts +0 -1
  102. package/src/components/epi/EpiCaseSetInfoDialog/EpiCaseSetContent.tsx +0 -108
  103. package/src/components/epi/EpiCaseSetInfoDialog/EpiCaseSetDescription.tsx +0 -34
  104. package/src/components/epi/EpiCaseSetInfoDialog/EpiCaseSetForm.tsx +0 -151
  105. package/src/components/epi/EpiCaseSetInfoDialog/EpiCaseSetInfoDialog.tsx +0 -389
  106. package/src/components/epi/EpiCaseSetInfoDialog/EpiCaseSetSharingForm.tsx +0 -155
  107. package/src/components/epi/EpiCaseSetInfoDialog/EpiCaseSetSharingInfo.tsx +0 -23
  108. package/src/components/epi/EpiCaseSetInfoDialog/index.ts +0 -1
  109. package/src/components/epi/EpiCaseSummary/EpiCaseSummary.tsx +0 -141
  110. package/src/components/epi/EpiCaseSummary/index.ts +0 -1
  111. package/src/components/epi/EpiCaseTypeInfoDialog/EpiCaseTypeInfoAccessRights.tsx +0 -91
  112. package/src/components/epi/EpiCaseTypeInfoDialog/EpiCaseTypeInfoColAccessRights.tsx +0 -118
  113. package/src/components/epi/EpiCaseTypeInfoDialog/EpiCaseTypeInfoData.tsx +0 -38
  114. package/src/components/epi/EpiCaseTypeInfoDialog/EpiCaseTypeInfoDialog.tsx +0 -39
  115. package/src/components/epi/EpiCaseTypeInfoDialog/EpiCaseTypeInfoDialogContent.tsx +0 -169
  116. package/src/components/epi/EpiCaseTypeInfoDialog/EpiCaseTypeInfoDialogWithLoader.tsx +0 -42
  117. package/src/components/epi/EpiCaseTypeInfoDialog/EpiCaseTypeInfoRegions.tsx +0 -87
  118. package/src/components/epi/EpiCaseTypeInfoDialog/EpiCaseTypeInfoTrees.tsx +0 -73
  119. package/src/components/epi/EpiCaseTypeInfoDialog/EpiCaseTypeInfoValues.tsx +0 -61
  120. package/src/components/epi/EpiCaseTypeInfoDialog/EpiCaseTypeInfoVariableDetails.tsx +0 -151
  121. package/src/components/epi/EpiCaseTypeInfoDialog/index.ts +0 -10
  122. package/src/components/epi/EpiCasesAlreadyInCaseSetWarning/EpiCasesAlreadyInCaseSetWarning.tsx +0 -190
  123. package/src/components/epi/EpiCasesAlreadyInCaseSetWarning/EpiCasesAlreadyInCaseSetWarningCaseSetLink.tsx +0 -62
  124. package/src/components/epi/EpiCasesAlreadyInCaseSetWarning/index.ts +0 -1
  125. package/src/components/epi/EpiCompletCaseTypeLoader/EpiCompletCaseTypeLoader.tsx +0 -87
  126. package/src/components/epi/EpiCompletCaseTypeLoader/index.ts +0 -1
  127. package/src/components/epi/EpiContactDetailsDialog/EpiContactDetailsDialog.tsx +0 -151
  128. package/src/components/epi/EpiContactDetailsDialog/index.ts +0 -1
  129. package/src/components/epi/EpiContextMenu/EpiContextMenu.tsx +0 -155
  130. package/src/components/epi/EpiContextMenu/index.ts +0 -1
  131. package/src/components/epi/EpiCreateEventDialog/EpiCreateEventDialog.tsx +0 -386
  132. package/src/components/epi/EpiCreateEventDialog/EpiCreateEventDialogSuccessNotificationMessage.tsx +0 -30
  133. package/src/components/epi/EpiCreateEventDialog/index.ts +0 -1
  134. package/src/components/epi/EpiCurve/EpiCurve.tsx +0 -525
  135. package/src/components/epi/EpiCurve/index.ts +0 -1
  136. package/src/components/epi/EpiCustomTabPanel/EpiCustomTabPanel.tsx +0 -27
  137. package/src/components/epi/EpiCustomTabPanel/index.ts +0 -1
  138. package/src/components/epi/EpiDashboard/EpiDashboard.tsx +0 -390
  139. package/src/components/epi/EpiDashboard/EpiDashboardDownloadSidebarItem.tsx +0 -154
  140. package/src/components/epi/EpiDashboard/EpiDashboardGeneralSettingsForm.tsx +0 -89
  141. package/src/components/epi/EpiDashboard/EpiDashboardLayoutRenderer.tsx +0 -248
  142. package/src/components/epi/EpiDashboard/EpiDashboardLayoutSettingsForm.tsx +0 -160
  143. package/src/components/epi/EpiDashboard/EpiDashboardSettingsSidebarItem.tsx +0 -93
  144. package/src/components/epi/EpiDashboard/EpiDashboardTreeSettingsForm.tsx +0 -90
  145. package/src/components/epi/EpiDashboard/index.ts +0 -1
  146. package/src/components/epi/EpiDashboardStoreLoader/EpiDashboardStoreLoader.tsx +0 -34
  147. package/src/components/epi/EpiDashboardStoreLoader/EpiDashboardStoreLoaderContent.tsx +0 -73
  148. package/src/components/epi/EpiDashboardStoreLoader/index.ts +0 -2
  149. package/src/components/epi/EpiDashboardStoreLoader/withEpiDashboardStore.tsx +0 -19
  150. package/src/components/epi/EpiDataCollectionAccessInfo/EpiDataCollectionAccessInfo.tsx +0 -88
  151. package/src/components/epi/EpiDataCollectionAccessInfo/index.ts +0 -1
  152. package/src/components/epi/EpiFindSimilarCasesDialog/EpiFindSimilarCasesDialog.tsx +0 -302
  153. package/src/components/epi/EpiFindSimilarCasesDialog/index.ts +0 -1
  154. package/src/components/epi/EpiLegendaItem/EpiLegendaItem.tsx +0 -106
  155. package/src/components/epi/EpiLegendaItem/index.ts +0 -1
  156. package/src/components/epi/EpiLineList/EpiLineList.tsx +0 -492
  157. package/src/components/epi/EpiLineList/EpiLineListPrimaryMenu.tsx +0 -189
  158. package/src/components/epi/EpiLineList/EpiLineListSecondaryMenu.tsx +0 -56
  159. package/src/components/epi/EpiLineList/EpiLineListTitle.tsx +0 -28
  160. package/src/components/epi/EpiLineList/index.ts +0 -1
  161. package/src/components/epi/EpiLineList/useEpiLineListEmitDownloadOptions.tsx +0 -102
  162. package/src/components/epi/EpiMap/EpiMap.tsx +0 -547
  163. package/src/components/epi/EpiMap/index.ts +0 -1
  164. package/src/components/epi/EpiRemoveCasesFromEventDialog/EpiRemoveCasesFromEventDialog.tsx +0 -169
  165. package/src/components/epi/EpiRemoveCasesFromEventDialog/index.ts +0 -1
  166. package/src/components/epi/EpiRemoveFindSimilarCasesResultDialog/EpiRemoveFindSimilarCasesResultDialog.tsx +0 -216
  167. package/src/components/epi/EpiSequenceDownloadDialog/EpiSequenceDownloadDialog.tsx +0 -151
  168. package/src/components/epi/EpiSequenceDownloadDialog/index.ts +0 -1
  169. package/src/components/epi/EpiStratification/EpiStratification.tsx +0 -241
  170. package/src/components/epi/EpiStratification/index.ts +0 -1
  171. package/src/components/epi/EpiTree/EpiTree.tsx +0 -906
  172. package/src/components/epi/EpiTree/index.ts +0 -1
  173. package/src/components/epi/EpiTreeDescription/EpiTreeDescription.tsx +0 -71
  174. package/src/components/epi/EpiTreeDescription/index.ts +0 -1
  175. package/src/components/epi/EpiUpload/EpiUpload.tsx +0 -107
  176. package/src/components/epi/EpiUpload/EpiUploadCaseResultTable.tsx +0 -383
  177. package/src/components/epi/EpiUpload/EpiUploadCreateCases.tsx +0 -266
  178. package/src/components/epi/EpiUpload/EpiUploadMapColumns.tsx +0 -261
  179. package/src/components/epi/EpiUpload/EpiUploadMapSequences.tsx +0 -332
  180. package/src/components/epi/EpiUpload/EpiUploadNavigation.tsx +0 -52
  181. package/src/components/epi/EpiUpload/EpiUploadSelectFile.tsx +0 -285
  182. package/src/components/epi/EpiUpload/EpiUploadSelectSequenceFiles.tsx +0 -375
  183. package/src/components/epi/EpiUpload/EpiUploadValidate.tsx +0 -199
  184. package/src/components/epi/EpiUpload/EpiUploadValidateNavigation.tsx +0 -34
  185. package/src/components/epi/EpiUpload/index.ts +0 -1
  186. package/src/components/epi/EpiUserRightsDialog/EpiUserRightsDialog.tsx +0 -101
  187. package/src/components/epi/EpiUserRightsDialog/EpiUserRightsDialogCaseAccessPolicy.tsx +0 -160
  188. package/src/components/epi/EpiUserRightsDialog/index.ts +0 -1
  189. package/src/components/epi/EpiWarning/EpiWarning.tsx +0 -39
  190. package/src/components/epi/EpiWarning/index.ts +0 -1
  191. package/src/components/epi/EpiWidget/EpiWidget.tsx +0 -255
  192. package/src/components/epi/EpiWidget/index.ts +0 -1
  193. package/src/components/epi/EpiWidgetHeaderIconButton/EpiWidgetHeaderIconButton.tsx +0 -40
  194. package/src/components/epi/EpiWidgetHeaderIconButton/index.ts +0 -1
  195. package/src/components/epi/EpiWidgetMenu/EpiWidgetMenu.tsx +0 -56
  196. package/src/components/epi/EpiWidgetMenu/index.ts +0 -1
  197. package/src/components/epi/EpiWidgetUnavailable/EpiWidgetUnavailable.tsx +0 -51
  198. package/src/components/epi/EpiWidgetUnavailable/index.ts +0 -1
  199. package/src/components/filters/BooleanFilterField/BooleanFilterField.tsx +0 -26
  200. package/src/components/filters/BooleanFilterField/index.ts +0 -1
  201. package/src/components/filters/DateFilterField/DateFilterField.tsx +0 -23
  202. package/src/components/filters/DateFilterField/index.ts +0 -1
  203. package/src/components/filters/GeoFilterField/GeoFilterField.tsx +0 -32
  204. package/src/components/filters/GeoFilterField/index.ts +0 -1
  205. package/src/components/filters/MultiSelectFilterField/MultiSelectFilterField.tsx +0 -31
  206. package/src/components/filters/MultiSelectFilterField/index.ts +0 -1
  207. package/src/components/filters/NumberRangeFilterField/NumberRangeFilterField.tsx +0 -20
  208. package/src/components/filters/NumberRangeFilterField/index.ts +0 -1
  209. package/src/components/filters/TextFilterField/TextFilterField.tsx +0 -18
  210. package/src/components/filters/TextFilterField/index.ts +0 -1
  211. package/src/components/form/fields/Autocomplete/Autocomplete.tsx +0 -270
  212. package/src/components/form/fields/Autocomplete/index.ts +0 -1
  213. package/src/components/form/fields/CheckboxGroup/CheckboxGroup.tsx +0 -192
  214. package/src/components/form/fields/CheckboxGroup/index.ts +0 -1
  215. package/src/components/form/fields/DatePicker/DatePicker.tsx +0 -167
  216. package/src/components/form/fields/DatePicker/index.ts +0 -1
  217. package/src/components/form/fields/DateRangePicker/DateRangePicker.tsx +0 -359
  218. package/src/components/form/fields/DateRangePicker/index.ts +0 -1
  219. package/src/components/form/fields/NumberField/NumberField.tsx +0 -277
  220. package/src/components/form/fields/NumberField/index.ts +0 -1
  221. package/src/components/form/fields/NumberRangeInput/NumberRangeInput.tsx +0 -297
  222. package/src/components/form/fields/NumberRangeInput/index.ts +0 -1
  223. package/src/components/form/fields/RadioGroup/RadioGroup.tsx +0 -130
  224. package/src/components/form/fields/RadioGroup/index.ts +0 -1
  225. package/src/components/form/fields/RichTextEditor/RichTextEditor.tsx +0 -321
  226. package/src/components/form/fields/RichTextEditor/RichTextEditorContent.tsx +0 -34
  227. package/src/components/form/fields/RichTextEditor/index.ts +0 -2
  228. package/src/components/form/fields/RichTextEditor/useRichTextEditorExtensions.ts +0 -174
  229. package/src/components/form/fields/Select/Select.tsx +0 -219
  230. package/src/components/form/fields/Select/index.ts +0 -1
  231. package/src/components/form/fields/Switch/Switch.tsx +0 -116
  232. package/src/components/form/fields/Switch/index.ts +0 -1
  233. package/src/components/form/fields/TextField/TextField.tsx +0 -170
  234. package/src/components/form/fields/TextField/index.ts +0 -1
  235. package/src/components/form/fields/ToggleButtonGroup/ToggleButtonGroup.tsx +0 -118
  236. package/src/components/form/fields/ToggleButtonGroup/index.ts +0 -1
  237. package/src/components/form/fields/TransferList/TransferList.tsx +0 -360
  238. package/src/components/form/fields/TransferList/index.ts +0 -1
  239. package/src/components/form/fields/UploadButton/UploadButton.tsx +0 -157
  240. package/src/components/form/helpers/FormFieldHelperText/FormFieldHelperText.tsx +0 -68
  241. package/src/components/form/helpers/FormFieldHelperText/index.ts +0 -1
  242. package/src/components/form/helpers/FormFieldLoadingIndicator/FormFieldLoadingIndicator.tsx +0 -22
  243. package/src/components/form/helpers/FormFieldLoadingIndicator/index.ts +0 -1
  244. package/src/components/form/helpers/GenericForm/GenericForm.tsx +0 -187
  245. package/src/components/form/helpers/GenericForm/index.ts +0 -1
  246. package/src/components/ui/ApplicationBar/ApplicationBar.tsx +0 -74
  247. package/src/components/ui/ApplicationBar/ApplicationBarActions.tsx +0 -124
  248. package/src/components/ui/ApplicationBar/ApplicationBarActionsFeedbackItem.tsx +0 -102
  249. package/src/components/ui/ApplicationBar/ApplicationBarActionsInfotem.tsx +0 -60
  250. package/src/components/ui/ApplicationBar/ApplicationBarActionsNotificationsItem.tsx +0 -82
  251. package/src/components/ui/ApplicationBar/ApplicationBarActionsOrganizationSwitcherItem.tsx +0 -58
  252. package/src/components/ui/ApplicationBar/ApplicationBarActionsOutagesItem.tsx +0 -75
  253. package/src/components/ui/ApplicationBar/ApplicationBarActionsUserItem.tsx +0 -60
  254. package/src/components/ui/ApplicationBar/ApplicationBarNavigationMenu.tsx +0 -159
  255. package/src/components/ui/ApplicationBar/InfoMenu.tsx +0 -120
  256. package/src/components/ui/ApplicationBar/UserMenu.tsx +0 -208
  257. package/src/components/ui/ApplicationBar/UserOrganizationAdminMenuItem.tsx +0 -69
  258. package/src/components/ui/ApplicationBar/UserOwnOrganizationMenuItem.tsx +0 -54
  259. package/src/components/ui/ApplicationBar/index.ts +0 -1
  260. package/src/components/ui/ApplicationFooter/ApplicationFooter.tsx +0 -121
  261. package/src/components/ui/ApplicationFooter/ApplicationFooterLink.tsx +0 -48
  262. package/src/components/ui/ApplicationFooter/ApplicationFooterLinkSection.tsx +0 -56
  263. package/src/components/ui/ApplicationFooter/index.ts +0 -1
  264. package/src/components/ui/AuthenticationWrapper/AuthenticationWrapper.tsx +0 -208
  265. package/src/components/ui/AuthenticationWrapper/index.ts +0 -1
  266. package/src/components/ui/AuthorizationWrapper/AuthorizationWrapper.tsx +0 -75
  267. package/src/components/ui/AuthorizationWrapper/index.ts +0 -1
  268. package/src/components/ui/Breadcrumbs/Breadcrumbs.tsx +0 -92
  269. package/src/components/ui/Breadcrumbs/index.ts +0 -1
  270. package/src/components/ui/Confirmation/Confirmation.tsx +0 -10
  271. package/src/components/ui/Confirmation/ConfirmationRender.tsx +0 -81
  272. package/src/components/ui/Confirmation/index.ts +0 -2
  273. package/src/components/ui/ConsentDialog/ConsentDialog.tsx +0 -57
  274. package/src/components/ui/ConsentDialog/index.ts +0 -1
  275. package/src/components/ui/CopyToClipboardButton/CopyToClipboardButton.tsx +0 -94
  276. package/src/components/ui/CopyToClipboardButton/index.ts +0 -1
  277. package/src/components/ui/Dialog/Dialog.tsx +0 -196
  278. package/src/components/ui/Dialog/index.ts +0 -1
  279. package/src/components/ui/FileSelector/FileSelector.tsx +0 -358
  280. package/src/components/ui/FileSelector/index.ts +0 -1
  281. package/src/components/ui/GenericErrorMessage/GenericErrorMessage.tsx +0 -168
  282. package/src/components/ui/GenericErrorMessage/index.ts +0 -1
  283. package/src/components/ui/HomePageTrends/HomePageTrendCard.tsx +0 -132
  284. package/src/components/ui/HomePageTrends/HomePageTrends.tsx +0 -312
  285. package/src/components/ui/HomePageTrends/index.ts +0 -1
  286. package/src/components/ui/LicensesDialog/LicensesDialog.tsx +0 -231
  287. package/src/components/ui/LicensesDialog/index.ts +0 -1
  288. package/src/components/ui/LinearProgressWithLabel/LinearProgressWithLabel.tsx +0 -31
  289. package/src/components/ui/LinearProgressWithLabel/index.ts +0 -1
  290. package/src/components/ui/LoadingIndicator/LoadingIndicator.tsx +0 -11
  291. package/src/components/ui/LoadingIndicator/index.ts +0 -1
  292. package/src/components/ui/MyPermissionsDialog/MyPermissionsDialog.tsx +0 -95
  293. package/src/components/ui/MyPermissionsDialog/index.ts +0 -1
  294. package/src/components/ui/NavLink/NavLink.tsx +0 -47
  295. package/src/components/ui/NavLink/index.ts +0 -1
  296. package/src/components/ui/NestedMenu/IconMenuItem.tsx +0 -64
  297. package/src/components/ui/NestedMenu/NestedDropdown.tsx +0 -133
  298. package/src/components/ui/NestedMenu/NestedMenuItem.tsx +0 -250
  299. package/src/components/ui/NestedMenu/index.ts +0 -4
  300. package/src/components/ui/NestedMenu/nestedMenuItemsFromObject.tsx +0 -98
  301. package/src/components/ui/Notifications/NotificationItem.tsx +0 -70
  302. package/src/components/ui/Notifications/NotificationsDrawer.tsx +0 -161
  303. package/src/components/ui/Notifications/NotificationsStack.tsx +0 -45
  304. package/src/components/ui/Notifications/index.ts +0 -2
  305. package/src/components/ui/OrganizationSwitcherDialog/OrganizationSwitcherDialog.tsx +0 -204
  306. package/src/components/ui/OrganizationSwitcherDialog/index.ts +0 -1
  307. package/src/components/ui/OutageList/OutageItem.tsx +0 -48
  308. package/src/components/ui/OutageList/OutageList.tsx +0 -54
  309. package/src/components/ui/OutageList/OutageSection.tsx +0 -39
  310. package/src/components/ui/OutageList/index.ts +0 -1
  311. package/src/components/ui/OutagesDialog/OutagesDialog.tsx +0 -71
  312. package/src/components/ui/OutagesDialog/index.ts +0 -1
  313. package/src/components/ui/PageContainer/PageContainer.tsx +0 -207
  314. package/src/components/ui/PageContainer/index.ts +0 -1
  315. package/src/components/ui/PanelSeparator/PanelResizeHandle.tsx +0 -67
  316. package/src/components/ui/PanelSeparator/index.ts +0 -1
  317. package/src/components/ui/ResponseHandler/ResponseHandler.tsx +0 -79
  318. package/src/components/ui/ResponseHandler/index.ts +0 -1
  319. package/src/components/ui/Sidebar/SidebarItem.tsx +0 -138
  320. package/src/components/ui/Sidebar/SidebarMenu.tsx +0 -33
  321. package/src/components/ui/Sidebar/SidebarMenuItem.tsx +0 -61
  322. package/src/components/ui/Sidebar/index.ts +0 -3
  323. package/src/components/ui/SortableList/SortableList.tsx +0 -172
  324. package/src/components/ui/SortableList/SortableListItem.tsx +0 -73
  325. package/src/components/ui/SortableList/SortableListItemDragHandle.tsx +0 -53
  326. package/src/components/ui/SortableList/SortableOverlay.tsx +0 -35
  327. package/src/components/ui/SortableList/context/SortableListItemContext.tsx +0 -17
  328. package/src/components/ui/SortableList/context/SortableListItemContextProvider.tsx +0 -19
  329. package/src/components/ui/SortableList/context/useSortableListItemContext.tsx +0 -5
  330. package/src/components/ui/Spinner/Spinner.tsx +0 -84
  331. package/src/components/ui/Spinner/index.ts +0 -1
  332. package/src/components/ui/Stepper/Stepper.tsx +0 -194
  333. package/src/components/ui/Stepper/index.ts +0 -2
  334. package/src/components/ui/Stepper/stepperModel.ts +0 -4
  335. package/src/components/ui/Table/Table.tsx +0 -812
  336. package/src/components/ui/Table/TableActionsCell.tsx +0 -81
  337. package/src/components/ui/Table/TableCaption.tsx +0 -33
  338. package/src/components/ui/Table/TableCell.tsx +0 -162
  339. package/src/components/ui/Table/TableCellAsyncContent.tsx +0 -28
  340. package/src/components/ui/Table/TableCheckboxCell.tsx +0 -65
  341. package/src/components/ui/Table/TableCheckboxHeader.tsx +0 -72
  342. package/src/components/ui/Table/TableColumnsEditorDialog.tsx +0 -241
  343. package/src/components/ui/Table/TableFilter.tsx +0 -31
  344. package/src/components/ui/Table/TableFiltersSidebarItem.tsx +0 -398
  345. package/src/components/ui/Table/TableHeader.tsx +0 -40
  346. package/src/components/ui/Table/TableHeaderCell.tsx +0 -434
  347. package/src/components/ui/Table/TableHeaderFilter.tsx +0 -104
  348. package/src/components/ui/Table/TableMenu.tsx +0 -26
  349. package/src/components/ui/Table/TableReadableIndexCell.tsx +0 -65
  350. package/src/components/ui/Table/TableSidebarMenu.tsx +0 -59
  351. package/src/components/ui/Table/classNames.ts +0 -7
  352. package/src/components/ui/Table/index.ts +0 -12
  353. package/src/components/ui/UserFeedbackDialog/UserFeedbackDialog.tsx +0 -168
  354. package/src/components/ui/UserFeedbackDialog/index.ts +0 -1
  355. package/src/components/ui/UserInactivityConfirmation/UserInactivityConfirmation.tsx +0 -59
  356. package/src/components/ui/UserInactivityConfirmation/index.ts +0 -1
  357. package/src/components/ui/UsersEffectiveRightsDetailsDialog/UsersEffectiveRightsDetailsDialog.tsx +0 -584
  358. package/src/components/ui/UsersEffectiveRightsDetailsDialog/index.ts +0 -1
  359. package/src/context/caseAbac/CaseAbacContext.tsx +0 -24
  360. package/src/context/caseAbac/CaseAbacContextProvider.tsx +0 -69
  361. package/src/context/caseAbac/index.ts +0 -3
  362. package/src/context/caseAbac/useCaseAbacContext.tsx +0 -6
  363. package/src/context/caseTypeAbac/CaseTypeAbacContext.tsx +0 -16
  364. package/src/context/caseTypeAbac/CaseTypeAbacContextProvider.tsx +0 -40
  365. package/src/context/caseTypeAbac/index.ts +0 -3
  366. package/src/context/caseTypeAbac/useCaseTypeAbacContext.tsx +0 -6
  367. package/src/data/date.ts +0 -8
  368. package/src/data/queryDependencies.ts +0 -166
  369. package/src/dataHooks/useAssemblyProtocolsQuery/index.ts +0 -1
  370. package/src/dataHooks/useAssemblyProtocolsQuery/useAssemblyProtocolsQuery.ts +0 -39
  371. package/src/dataHooks/useCaseRightsQuery/index.ts +0 -1
  372. package/src/dataHooks/useCaseRightsQuery/useCaseRightsQuery.ts +0 -21
  373. package/src/dataHooks/useCaseSetCategoriesQuery/index.ts +0 -1
  374. package/src/dataHooks/useCaseSetCategoriesQuery/useCaseSetCategoriesQuery.ts +0 -39
  375. package/src/dataHooks/useCaseSetRightsQuery/index.ts +0 -1
  376. package/src/dataHooks/useCaseSetRightsQuery/useCaseSetRightsQuery.ts +0 -17
  377. package/src/dataHooks/useCaseSetStatsQuery/index.ts +0 -1
  378. package/src/dataHooks/useCaseSetStatsQuery/useCaseSetStatsQuery.ts +0 -31
  379. package/src/dataHooks/useCaseSetStatusesQuery/index.ts +0 -1
  380. package/src/dataHooks/useCaseSetStatusesQuery/useCaseSetStatusesQuery.ts +0 -39
  381. package/src/dataHooks/useCaseSetsQuery/index.ts +0 -1
  382. package/src/dataHooks/useCaseSetsQuery/useCaseSetsQuery.ts +0 -41
  383. package/src/dataHooks/useCaseTypeSetCategoriesQuery/index.ts +0 -1
  384. package/src/dataHooks/useCaseTypeSetCategoriesQuery/useCaseTypeSetCategoriesQuery.ts +0 -41
  385. package/src/dataHooks/useCaseTypeSetMembersQuery/index.ts +0 -1
  386. package/src/dataHooks/useCaseTypeSetMembersQuery/useCaseTypeSetMembersQuery.ts +0 -17
  387. package/src/dataHooks/useCaseTypeSetsQuery/index.ts +0 -1
  388. package/src/dataHooks/useCaseTypeSetsQuery/useCaseTypeSetsQuery.ts +0 -58
  389. package/src/dataHooks/useCaseTypeStatsQuery/index.ts +0 -1
  390. package/src/dataHooks/useCaseTypeStatsQuery/useCaseTypeStatsQuery.ts +0 -20
  391. package/src/dataHooks/useCaseTypesQuery/index.ts +0 -1
  392. package/src/dataHooks/useCaseTypesQuery/useCaseTypesQuery.ts +0 -43
  393. package/src/dataHooks/useColSetMembersQuery/index.ts +0 -1
  394. package/src/dataHooks/useColSetMembersQuery/useColSetMembersQuery.ts +0 -17
  395. package/src/dataHooks/useColSetsQuery/index.ts +0 -1
  396. package/src/dataHooks/useColSetsQuery/useColSetsQuery.ts +0 -39
  397. package/src/dataHooks/useColTypesQuery/index.ts +0 -1
  398. package/src/dataHooks/useColTypesQuery/useColTypesQuery.ts +0 -51
  399. package/src/dataHooks/useColsQuery/index.ts +0 -1
  400. package/src/dataHooks/useColsQuery/useColsQuery.ts +0 -60
  401. package/src/dataHooks/useConceptQuery/index.ts +0 -1
  402. package/src/dataHooks/useConceptQuery/useConceptQuery.ts +0 -52
  403. package/src/dataHooks/useConceptRelationTypeQuery/index.ts +0 -1
  404. package/src/dataHooks/useConceptRelationTypeQuery/useConceptRelationTypeQuery.ts +0 -21
  405. package/src/dataHooks/useConceptSetTypeQuery/index.ts +0 -1
  406. package/src/dataHooks/useConceptSetTypeQuery/useConceptSetTypeQuery.ts +0 -27
  407. package/src/dataHooks/useConceptSetsQuery/index.ts +0 -1
  408. package/src/dataHooks/useConceptSetsQuery/useConceptSetsQuery.ts +0 -38
  409. package/src/dataHooks/useDataCollectionSetMembersQuery/index.ts +0 -1
  410. package/src/dataHooks/useDataCollectionSetMembersQuery/useDataCollectionSetMembersQuery.ts +0 -17
  411. package/src/dataHooks/useDataCollectionsQuery/index.ts +0 -1
  412. package/src/dataHooks/useDataCollectionsQuery/useDataCollectionsQuery.ts +0 -45
  413. package/src/dataHooks/useDimTypesQuery/index.ts +0 -1
  414. package/src/dataHooks/useDimTypesQuery/useDimTypesQuery.ts +0 -26
  415. package/src/dataHooks/useDimsQuery/index.ts +0 -1
  416. package/src/dataHooks/useDimsQuery/useDimsQuery.ts +0 -55
  417. package/src/dataHooks/useDiseasesQuery/index.ts +0 -1
  418. package/src/dataHooks/useDiseasesQuery/useDiseasesQuery.ts +0 -39
  419. package/src/dataHooks/useEtiologicalAgentsQuery/index.ts +0 -1
  420. package/src/dataHooks/useEtiologicalAgentsQuery/useEtiologicalAgentsQuery.ts +0 -39
  421. package/src/dataHooks/useGeneticDistanceProtocolsQuery/index.ts +0 -1
  422. package/src/dataHooks/useGeneticDistanceProtocolsQuery/useGeneticDistanceProtocolsQuery.ts +0 -29
  423. package/src/dataHooks/useIdentifierIssuerOwnOrganizationQuery/index.ts +0 -1
  424. package/src/dataHooks/useIdentifierIssuerOwnOrganizationQuery/useIdentifierIssuerOwnOrganizationQuery.ts +0 -34
  425. package/src/dataHooks/useIdentifierIssuerQuery/index.ts +0 -1
  426. package/src/dataHooks/useIdentifierIssuerQuery/useIdentifierIssuerQuery.ts +0 -42
  427. package/src/dataHooks/useInviteUserConstraintsQuery/index.ts +0 -1
  428. package/src/dataHooks/useInviteUserConstraintsQuery/useInviteUserConstraintsQuery.ts +0 -32
  429. package/src/dataHooks/useOrganizationAccessCasePoliciesQuery/index.ts +0 -1
  430. package/src/dataHooks/useOrganizationAccessCasePoliciesQuery/useOrganizationAccessCasePoliciesQuery.ts +0 -20
  431. package/src/dataHooks/useOrganizationAdminPoliciesQuery/index.ts +0 -1
  432. package/src/dataHooks/useOrganizationAdminPoliciesQuery/useOrganizationAdminPoliciesQuery.ts +0 -60
  433. package/src/dataHooks/useOrganizationIdentifierIssuerLinksQuery/index.ts +0 -1
  434. package/src/dataHooks/useOrganizationIdentifierIssuerLinksQuery/useOrganizationIdentifierIssuerLinksQuery.ts +0 -18
  435. package/src/dataHooks/useOrganizationShareCasePoliciesQuery/index.ts +0 -1
  436. package/src/dataHooks/useOrganizationShareCasePoliciesQuery/useOrganizationShareCasePoliciesQuery.ts +0 -18
  437. package/src/dataHooks/useOrganizationsQuery/index.ts +0 -1
  438. package/src/dataHooks/useOrganizationsQuery/useOrganizationsQuery.ts +0 -40
  439. package/src/dataHooks/useRefColsQuery/index.ts +0 -1
  440. package/src/dataHooks/useRefColsQuery/useRefColsQuery.ts +0 -39
  441. package/src/dataHooks/useRefColsValidationRulesQuery/index.ts +0 -1
  442. package/src/dataHooks/useRefColsValidationRulesQuery/useRefColsValidationRulesQuery.ts +0 -17
  443. package/src/dataHooks/useRefDimsQuery/index.ts +0 -1
  444. package/src/dataHooks/useRefDimsQuery/useRefDimsQuery.ts +0 -39
  445. package/src/dataHooks/useRegionQuery/index.ts +0 -1
  446. package/src/dataHooks/useRegionQuery/useRegionQuery.ts +0 -52
  447. package/src/dataHooks/useRegionRelationTypeQuery/index.ts +0 -1
  448. package/src/dataHooks/useRegionRelationTypeQuery/useRegionRelationTypeQuery.ts +0 -24
  449. package/src/dataHooks/useRegionSetsQuery/index.ts +0 -1
  450. package/src/dataHooks/useRegionSetsQuery/useRegionSetsQuery.ts +0 -39
  451. package/src/dataHooks/useSequencingProtocolsQuery/index.ts +0 -1
  452. package/src/dataHooks/useSequencingProtocolsQuery/useSequencingProtocolsQuery.ts +0 -39
  453. package/src/dataHooks/useTreeAlgorithmCodesQuery/index.ts +0 -1
  454. package/src/dataHooks/useTreeAlgorithmCodesQuery/useTreeAlgorithmCodesQuery.ts +0 -42
  455. package/src/dataHooks/useUserAccessCasePoliciesQuery/index.ts +0 -1
  456. package/src/dataHooks/useUserAccessCasePoliciesQuery/useUserAccessCasePoliciesQuery.ts +0 -18
  457. package/src/dataHooks/useUserEffectiveRightsQuery/index.ts +0 -1
  458. package/src/dataHooks/useUserEffectiveRightsQuery/useUserEffectiveRightsQuery.ts +0 -125
  459. package/src/dataHooks/useUserShareCasePoliciesQuery/index.ts +0 -1
  460. package/src/dataHooks/useUserShareCasePoliciesQuery/useUserShareCasePoliciesQuery.ts +0 -18
  461. package/src/dataHooks/useUsersQuery/index.ts +0 -1
  462. package/src/dataHooks/useUsersQuery/useUsersQuery.ts +0 -43
  463. package/src/hoc/withDialog/index.ts +0 -1
  464. package/src/hoc/withDialog/withDialog.tsx +0 -132
  465. package/src/hoc/withPermissions/index.ts +0 -1
  466. package/src/hoc/withPermissions/withPermissions.tsx +0 -34
  467. package/src/hooks/useArray/index.ts +0 -1
  468. package/src/hooks/useArray/useArray.ts +0 -5
  469. package/src/hooks/useColumnsMenu/index.ts +0 -1
  470. package/src/hooks/useColumnsMenu/useColumnsMenu.tsx +0 -152
  471. package/src/hooks/useCreateMutation/index.ts +0 -1
  472. package/src/hooks/useCreateMutation/useCreateMutation.ts +0 -103
  473. package/src/hooks/useDeleteMutation/index.ts +0 -1
  474. package/src/hooks/useDeleteMutation/useDeleteMutation.ts +0 -92
  475. package/src/hooks/useDimensions/index.ts +0 -1
  476. package/src/hooks/useDimensions/useDimensions.ts +0 -82
  477. package/src/hooks/useEditMutation/index.ts +0 -1
  478. package/src/hooks/useEditMutation/useEditMutation.ts +0 -112
  479. package/src/hooks/useInitializeTableStore/index.ts +0 -1
  480. package/src/hooks/useInitializeTableStore/useInitializeTableStore.ts +0 -41
  481. package/src/hooks/useIsFormFieldRequiredFromSchema/index.ts +0 -1
  482. package/src/hooks/useIsFormFieldRequiredFromSchema/useIsFormFieldRequiredFromSchema.ts +0 -32
  483. package/src/hooks/useItemQuery/index.ts +0 -1
  484. package/src/hooks/useItemQuery/useItemQuery.ts +0 -82
  485. package/src/hooks/useOrganizationCasePolicyNameFactory/index.ts +0 -1
  486. package/src/hooks/useOrganizationCasePolicyNameFactory/useOrganizationCasePolicyNameFactory.ts +0 -31
  487. package/src/hooks/useQueryMemo/index.ts +0 -1
  488. package/src/hooks/useQueryMemo/useQueryMemo.ts +0 -24
  489. package/src/hooks/useScrollbarSize/index.ts +0 -1
  490. package/src/hooks/useScrollbarSize/useScrollbarSize.ts +0 -23
  491. package/src/hooks/useSubscribable/index.ts +0 -1
  492. package/src/hooks/useSubscribable/useSubscribable.ts +0 -30
  493. package/src/hooks/useUpdateBreadcrumb/index.ts +0 -1
  494. package/src/hooks/useUpdateBreadcrumb/useUpdateBreadcrumb.ts +0 -21
  495. package/src/hooks/useUpdateDocumentTitle/index.ts +0 -1
  496. package/src/hooks/useUpdateDocumentTitle/useUpdateDocumentTitle.ts +0 -10
  497. package/src/hooks/useUserCasePolicyNameFactory/index.ts +0 -1
  498. package/src/hooks/useUserCasePolicyNameFactory/useUserCasePolicyNameFactory.ts +0 -37
  499. package/src/index.ts +0 -301
  500. package/src/models/admin.ts +0 -7
  501. package/src/models/auth.ts +0 -14
  502. package/src/models/caseAccess.ts +0 -21
  503. package/src/models/config.ts +0 -153
  504. package/src/models/data.ts +0 -11
  505. package/src/models/dataHooks.ts +0 -18
  506. package/src/models/environment.ts +0 -10
  507. package/src/models/epi.ts +0 -188
  508. package/src/models/filter.ts +0 -39
  509. package/src/models/form.ts +0 -74
  510. package/src/models/generic.ts +0 -4
  511. package/src/models/nestedMenu.ts +0 -21
  512. package/src/models/notification.ts +0 -12
  513. package/src/models/outage.ts +0 -7
  514. package/src/models/query.ts +0 -75
  515. package/src/models/reactRouter.ts +0 -36
  516. package/src/models/table.ts +0 -191
  517. package/src/models/testId.ts +0 -1
  518. package/src/models/tree.ts +0 -32
  519. package/src/pages/AcceptInvitationPage/AcceptInvitationPage.tsx +0 -96
  520. package/src/pages/AcceptInvitationPage/index.ts +0 -1
  521. package/src/pages/AdminPage/AdminPage.tsx +0 -164
  522. package/src/pages/AdminPage/index.ts +0 -1
  523. package/src/pages/CaseSetStatusAdminPage/CaseSetStatusAdminPage.tsx +0 -98
  524. package/src/pages/CaseSetStatusAdminPage/index.ts +0 -1
  525. package/src/pages/CaseTypeSetCategoriesAdminPage/CaseTypeSetCategoriesAdminPage.tsx +0 -108
  526. package/src/pages/CaseTypeSetCategoriesAdminPage/index.ts +0 -1
  527. package/src/pages/CaseTypeSetsAdminPage/CaseTypeSetsAdminPage.tsx +0 -200
  528. package/src/pages/CaseTypeSetsAdminPage/index.ts +0 -1
  529. package/src/pages/CaseTypesAdminPage/CaseTypesAdminPage.tsx +0 -204
  530. package/src/pages/CaseTypesAdminPage/index.ts +0 -1
  531. package/src/pages/CasesDetailPage/CasesDetailPage.tsx +0 -57
  532. package/src/pages/CasesDetailPage/index.ts +0 -1
  533. package/src/pages/CasesPage/CasesPage.tsx +0 -338
  534. package/src/pages/CasesPage/index.ts +0 -1
  535. package/src/pages/ChooseIdentityProviderPage/ChooseIdentityProviderPage.tsx +0 -166
  536. package/src/pages/ChooseIdentityProviderPage/index.ts +0 -1
  537. package/src/pages/ColSetsAdminPage/ColSetsAdminPage.tsx +0 -175
  538. package/src/pages/ColSetsAdminPage/index.ts +0 -1
  539. package/src/pages/ColsAdminPage/ColsAdminPage.tsx +0 -400
  540. package/src/pages/ColsAdminPage/index.ts +0 -1
  541. package/src/pages/ConceptRelationsAdminPage/ConceptRelationsAdminPage.tsx +0 -134
  542. package/src/pages/ConceptRelationsAdminPage/index.ts +0 -1
  543. package/src/pages/ConceptSetsAdminPage/ConceptSetsAdminPage.tsx +0 -167
  544. package/src/pages/ConceptSetsAdminPage/index.ts +0 -1
  545. package/src/pages/ConceptsAdminPage/ConceptsAdminPage.tsx +0 -138
  546. package/src/pages/ConceptsAdminPage/index.ts +0 -1
  547. package/src/pages/CrudPage/CrudPage.tsx +0 -646
  548. package/src/pages/CrudPage/CrudPageDeleteDialog.tsx +0 -85
  549. package/src/pages/CrudPage/CrudPageEditDialog.tsx +0 -165
  550. package/src/pages/CrudPage/index.ts +0 -1
  551. package/src/pages/DataCollectionSetsAdminPage/DataCollectionSetsAdminPage.tsx +0 -156
  552. package/src/pages/DataCollectionSetsAdminPage/index.ts +0 -1
  553. package/src/pages/DataCollectionVisualizationPage/DataCollectionVisualizationPage.tsx +0 -238
  554. package/src/pages/DataCollectionVisualizationPage/index.ts +0 -1
  555. package/src/pages/DataCollectionsAdminPage/DataCollectionsAdminPage.tsx +0 -96
  556. package/src/pages/DataCollectionsAdminPage/index.ts +0 -1
  557. package/src/pages/DimsAdminPage/DimsAdminPage.tsx +0 -208
  558. package/src/pages/DimsAdminPage/index.ts +0 -1
  559. package/src/pages/DiseasesAdminPage/DiseasesAdminPage.tsx +0 -97
  560. package/src/pages/DiseasesAdminPage/index.ts +0 -1
  561. package/src/pages/ErrorPage/ErrorPage.tsx +0 -25
  562. package/src/pages/ErrorPage/index.ts +0 -1
  563. package/src/pages/EtiologicalAgentsAdminPage/EtiologicalAgentsAdminPage.tsx +0 -96
  564. package/src/pages/EtiologicalAgentsAdminPage/index.ts +0 -1
  565. package/src/pages/EtiologiesAdminPage/EtiologiesAdminPage.tsx +0 -110
  566. package/src/pages/EtiologiesAdminPage/index.ts +0 -1
  567. package/src/pages/EventsDetailPage/EventsDetailPage.tsx +0 -67
  568. package/src/pages/EventsDetailPage/index.ts +0 -1
  569. package/src/pages/EventsPage/EventsPage.tsx +0 -262
  570. package/src/pages/EventsPage/index.ts +0 -1
  571. package/src/pages/HomePage/HomePage.tsx +0 -47
  572. package/src/pages/HomePage/index.ts +0 -1
  573. package/src/pages/IdentifierIssuersAdminPage/IdentifierIssuersAdminPage.tsx +0 -106
  574. package/src/pages/IdentifierIssuersAdminPage/index.ts +0 -1
  575. package/src/pages/OrganizationAccessCasePoliciesAdminPage/OrganizationAccessCasePoliciesAdminPage.tsx +0 -217
  576. package/src/pages/OrganizationAccessCasePoliciesAdminPage/index.ts +0 -1
  577. package/src/pages/OrganizationAdminPoliciesAdminPage/OrganizationAdminPoliciesAdminPage.tsx +0 -119
  578. package/src/pages/OrganizationAdminPoliciesAdminPage/index.ts +0 -1
  579. package/src/pages/OrganizationContactsAdminPage/OrganizationContactsAdminPage.tsx +0 -127
  580. package/src/pages/OrganizationContactsAdminPage/index.ts +0 -1
  581. package/src/pages/OrganizationShareCasePoliciesAdminPage/OrganizationShareCasePoliciesAdminPage.tsx +0 -183
  582. package/src/pages/OrganizationShareCasePoliciesAdminPage/index.ts +0 -1
  583. package/src/pages/OrganizationSitesAdminPage/OrganizationSitesAdminPage.tsx +0 -132
  584. package/src/pages/OrganizationSitesAdminPage/index.ts +0 -1
  585. package/src/pages/OrganizationsAdminPage/OrganizationsAdminPage.tsx +0 -188
  586. package/src/pages/OrganizationsAdminPage/index.ts +0 -1
  587. package/src/pages/OutagesAdminPage/OutagesAdminPage.tsx +0 -158
  588. package/src/pages/OutagesAdminPage/index.ts +0 -1
  589. package/src/pages/PostLoginPage/PostLoginPage.tsx +0 -39
  590. package/src/pages/PostLoginPage/index.ts +0 -1
  591. package/src/pages/PostLogoutPage/PostLogoutPage.tsx +0 -44
  592. package/src/pages/PostLogoutPage/index.ts +0 -1
  593. package/src/pages/RefColsAdminPage/RefColsAdminPage.tsx +0 -286
  594. package/src/pages/RefColsAdminPage/index.ts +0 -1
  595. package/src/pages/RefDimsAdminPage/RefDimsAdminPage.tsx +0 -152
  596. package/src/pages/RefDimsAdminPage/index.ts +0 -1
  597. package/src/pages/RegionRelationsAdminPage/RegionRelationsAdminPage.tsx +0 -132
  598. package/src/pages/RegionRelationsAdminPage/index.ts +0 -1
  599. package/src/pages/RegionSetShapesAdminPage/RegionSetShapesAdminPage.tsx +0 -132
  600. package/src/pages/RegionSetShapesAdminPage/index.ts +0 -1
  601. package/src/pages/RegionSetsAdminPage/RegionSetsAdminPage.tsx +0 -147
  602. package/src/pages/RegionSetsAdminPage/index.ts +0 -1
  603. package/src/pages/RegionsAdminPage/RegionsAdminPage.tsx +0 -150
  604. package/src/pages/RegionsAdminPage/index.ts +0 -1
  605. package/src/pages/RouterErrorPage/RouterErrorPage.tsx +0 -23
  606. package/src/pages/RouterErrorPage/index.ts +0 -1
  607. package/src/pages/TrendsPage/TrendsPage.tsx +0 -17
  608. package/src/pages/TrendsPage/index.ts +0 -1
  609. package/src/pages/UploadPage/UploadPage.tsx +0 -40
  610. package/src/pages/UploadPage/index.ts +0 -1
  611. package/src/pages/UserAccessCasePoliciesAdminPage/UserAccessCasePoliciesAdminPage.tsx +0 -209
  612. package/src/pages/UserAccessCasePoliciesAdminPage/index.ts +0 -1
  613. package/src/pages/UserEffectiveRightsAdminPage/UserEffectiveRightsAdminPage.tsx +0 -331
  614. package/src/pages/UserEffectiveRightsAdminPage/index.ts +0 -1
  615. package/src/pages/UserEffectiveRightsTesterAdminPage/UserEffectiveRightsTesterAdminPage.tsx +0 -283
  616. package/src/pages/UserEffectiveRightsTesterAdminPage/index.ts +0 -1
  617. package/src/pages/UserInvitationsAdminPage/UserInvitationConsumeDialog.tsx +0 -143
  618. package/src/pages/UserInvitationsAdminPage/UserInvitationShareDialog.tsx +0 -100
  619. package/src/pages/UserInvitationsAdminPage/UserInvitationsAdminPage.tsx +0 -218
  620. package/src/pages/UserInvitationsAdminPage/index.ts +0 -1
  621. package/src/pages/UserShareCasePoliciesAdminPage/UserShareCasePoliciesAdminPage.tsx +0 -183
  622. package/src/pages/UserShareCasePoliciesAdminPage/index.ts +0 -1
  623. package/src/pages/UsersAdminPage/UsersAdminPage.tsx +0 -240
  624. package/src/pages/UsersAdminPage/index.ts +0 -1
  625. package/src/routes/adminRoutes.tsx +0 -801
  626. package/src/routes/index.ts +0 -2
  627. package/src/routes/routes.tsx +0 -235
  628. package/src/setup/index.ts +0 -1
  629. package/src/setup/setup.ts +0 -5
  630. package/src/setup/yup.ts +0 -203
  631. package/src/stores/epiDashboardStore/epiDashboardStore.ts +0 -750
  632. package/src/stores/epiDashboardStore/epiDashboardStoreContext.tsx +0 -6
  633. package/src/stores/epiDashboardStore/index.ts +0 -2
  634. package/src/stores/epiUploadStore/epiUploadStore.ts +0 -351
  635. package/src/stores/epiUploadStore/epiUploadStoreContext.tsx +0 -6
  636. package/src/stores/epiUploadStore/index.ts +0 -2
  637. package/src/stores/oidcStore/index.ts +0 -1
  638. package/src/stores/oidcStore/oidcStore.ts +0 -43
  639. package/src/stores/outagesStore/index.ts +0 -1
  640. package/src/stores/outagesStore/outagesStore.ts +0 -31
  641. package/src/stores/tableStore/TableStoreContext.tsx +0 -6
  642. package/src/stores/tableStore/TableStoreContextProvider.tsx +0 -20
  643. package/src/stores/tableStore/index.ts +0 -4
  644. package/src/stores/tableStore/tableStore.ts +0 -547
  645. package/src/stores/tableStore/useTableStoreContext.tsx +0 -7
  646. package/src/stores/userProfileStore/index.ts +0 -1
  647. package/src/stores/userProfileStore/userProfileStore.ts +0 -112
  648. package/src/test/integration/lib/render.tsx +0 -33
  649. package/src/test/integration/tests/integration-example/HelloWorld.test.tsx +0 -14
  650. package/src/test/integration/tests/integration-example/HelloWorld.tsx +0 -9
  651. package/src/test/setup-browser.ts +0 -3
  652. package/src/test/setup-jsdom.ts +0 -6
  653. package/src/test/setup.ts +0 -9
  654. package/src/utils/AbacUtil/AbacUtil.ts +0 -21
  655. package/src/utils/AbacUtil/index.ts +0 -1
  656. package/src/utils/AxiosUtil/AxiosUtil.test.ts +0 -66
  657. package/src/utils/AxiosUtil/AxiosUtil.ts +0 -47
  658. package/src/utils/AxiosUtil/index.ts +0 -1
  659. package/src/utils/CaseSelectionUtil/CaseSelectionUtil.ts +0 -32
  660. package/src/utils/CaseSelectionUtil/index.ts +0 -1
  661. package/src/utils/CaseSetUtil/CaseSetUtil.ts +0 -13
  662. package/src/utils/CaseSetUtil/index.ts +0 -1
  663. package/src/utils/CaseTypeUtil/CaseTypeUtil.ts +0 -178
  664. package/src/utils/CaseTypeUtil/index.ts +0 -1
  665. package/src/utils/CaseUtil/CaseUtil.ts +0 -401
  666. package/src/utils/CaseUtil/index.ts +0 -1
  667. package/src/utils/DashboardUtil/DashboardUtil.ts +0 -46
  668. package/src/utils/DashboardUtil/index.ts +0 -1
  669. package/src/utils/DataHookUtil/DataHookUtil.ts +0 -137
  670. package/src/utils/DataHookUtil/index.ts +0 -1
  671. package/src/utils/DataSetUtil/DataSetUtil.ts +0 -70
  672. package/src/utils/DataSetUtil/index.ts +0 -1
  673. package/src/utils/DataUtil/DataUtil.ts +0 -84
  674. package/src/utils/DataUtil/index.ts +0 -1
  675. package/src/utils/DownloadUtil/DownloadUtil.ts +0 -237
  676. package/src/utils/DownloadUtil/index.ts +0 -1
  677. package/src/utils/EffectiveRightsUtil/EffectiveRightsUtil.ts +0 -142
  678. package/src/utils/EffectiveRightsUtil/index.ts +0 -1
  679. package/src/utils/EpiCurveUtil/EpiCurveUtil.ts +0 -180
  680. package/src/utils/EpiCurveUtil/index.ts +0 -1
  681. package/src/utils/EpiFilterUtil/EpiFilterUtil.ts +0 -232
  682. package/src/utils/EpiFilterUtil/index.ts +0 -1
  683. package/src/utils/EpiLineListUtil/EpiLineListUtil.ts +0 -15
  684. package/src/utils/EpiLineListUtil/index.ts +0 -1
  685. package/src/utils/EpiMapUtil/EpiMapUtil.test.ts +0 -54
  686. package/src/utils/EpiMapUtil/EpiMapUtil.ts +0 -80
  687. package/src/utils/EpiMapUtil/index.ts +0 -1
  688. package/src/utils/EpiTreeUtil/EpiTreeUtil.test.ts +0 -2467
  689. package/src/utils/EpiTreeUtil/EpiTreeUtil.ts +0 -1055
  690. package/src/utils/EpiTreeUtil/index.ts +0 -1
  691. package/src/utils/EpiUploadUtil/EpiUploadUtil.ts +0 -857
  692. package/src/utils/EpiUploadUtil/index.ts +0 -1
  693. package/src/utils/FileUtil/FileUtil.ts +0 -22
  694. package/src/utils/FileUtil/index.ts +0 -1
  695. package/src/utils/FormUtil/FormUtil.test.ts +0 -36
  696. package/src/utils/FormUtil/FormUtil.ts +0 -63
  697. package/src/utils/FormUtil/index.ts +0 -1
  698. package/src/utils/MenuDataUtil/MenuDataUtil.ts +0 -54
  699. package/src/utils/MenuDataUtil/index.ts +0 -1
  700. package/src/utils/NewickUtil/NewickUtil.test.ts +0 -187
  701. package/src/utils/NewickUtil/NewickUtil.ts +0 -69
  702. package/src/utils/NewickUtil/index.ts +0 -1
  703. package/src/utils/NotificationUtil/NotificationUtil.ts +0 -28
  704. package/src/utils/NotificationUtil/index.ts +0 -1
  705. package/src/utils/NumberUtil/NumberUtil.test.ts +0 -113
  706. package/src/utils/NumberUtil/NumberUtil.ts +0 -67
  707. package/src/utils/NumberUtil/index.ts +0 -1
  708. package/src/utils/ObjectUtil/ObjectUtil.test.ts +0 -43
  709. package/src/utils/ObjectUtil/ObjectUtil.ts +0 -28
  710. package/src/utils/ObjectUtil/index.ts +0 -1
  711. package/src/utils/OutageUtil/OutageUtil.test.ts +0 -108
  712. package/src/utils/OutageUtil/OutageUtil.ts +0 -81
  713. package/src/utils/OutageUtil/index.ts +0 -1
  714. package/src/utils/QueryUtil/QueryUtil.test.ts +0 -42
  715. package/src/utils/QueryUtil/QueryUtil.ts +0 -105
  716. package/src/utils/QueryUtil/index.ts +0 -1
  717. package/src/utils/StringUtil/StringUtil.test.ts +0 -61
  718. package/src/utils/StringUtil/StringUtil.ts +0 -91
  719. package/src/utils/StringUtil/index.ts +0 -1
  720. package/src/utils/TableUtil/TableUtil.ts +0 -665
  721. package/src/utils/TableUtil/index.ts +0 -1
  722. package/src/utils/TestIdUtil/TestIdUtil.test.ts +0 -12
  723. package/src/utils/TestIdUtil/TestIdUtil.ts +0 -13
  724. package/src/utils/TestIdUtil/index.ts +0 -1
  725. package/src/utils/TimeUtil/TimeUtil.ts +0 -38
  726. package/src/utils/TimeUtil/index.ts +0 -1
  727. package/src/utils/UserManagerUtil/UserManagerUtil.test.ts +0 -41
  728. package/src/utils/UserManagerUtil/UserManagerUtil.ts +0 -39
  729. package/src/utils/UserManagerUtil/index.ts +0 -1
  730. package/src/utils/ValidationUtil/ValidationUtil.test.ts +0 -61
  731. package/src/utils/ValidationUtil/ValidationUtil.ts +0 -56
  732. package/src/utils/ValidationUtil/index.ts +0 -1
  733. /package/{src → dist}/locale/en.json +0 -0
  734. /package/{src → dist}/locale/nl.json +0 -0
  735. /package/{src/@types → dist}/mui.d.ts +0 -0
  736. /package/{src/@types → dist}/ui.d.ts +0 -0
  737. /package/{src/@types → dist}/vite-env.d.ts +0 -0
  738. /package/{src/@types → dist}/yup.d.ts +0 -0
@@ -1,2467 +0,0 @@
1
- import Decimal from 'decimal.js';
2
- import {
3
- afterAll,
4
- beforeAll,
5
- vi,
6
- } from 'vitest';
7
- import type { Theme } from '@mui/material/styles';
8
-
9
- import { ColType } from '../../api';
10
- import type { TreeAlgorithm } from '../../api';
11
- import { ConfigManager } from '../../classes/managers/ConfigManager';
12
- import { EpiDataManager } from '../../classes/managers/EpiDataManager';
13
- import type { ArgumentTypes } from '../../models/generic';
14
- import type { Config } from '../../models/config';
15
- import { STRATIFICATION_MODE } from '../../models/epi';
16
- import type {
17
- Stratification,
18
- TreeConfiguration,
19
- } from '../../models/epi';
20
- import type {
21
- TreeAssembly,
22
- TreeNode,
23
- TreePathProperties,
24
- } from '../../models/tree';
25
-
26
- import { EpiTreeUtil } from './EpiTreeUtil';
27
-
28
- // Path2D is a browser API not available in jsdom — provide a minimal stub
29
- if (typeof Path2D === 'undefined') {
30
- (global as unknown as Record<string, unknown>)['Path2D'] = class {
31
- // eslint-disable-next-line @typescript-eslint/no-empty-function
32
- public arc(): void { }
33
- // eslint-disable-next-line @typescript-eslint/no-empty-function
34
- public moveTo(): void { }
35
- // eslint-disable-next-line @typescript-eslint/no-empty-function
36
- public lineTo(): void { }
37
- // eslint-disable-next-line @typescript-eslint/no-empty-function
38
- public closePath(): void { }
39
- };
40
- }
41
-
42
- // Helpers to build TreeNode objects that mirror what NewickUtil.parse produces
43
- const makeLeaf = (name: string, branchLength = 1): TreeNode => ({
44
- name,
45
- branchLength: new Decimal(branchLength),
46
- maxBranchLength: new Decimal(branchLength),
47
- subTreeNames: [],
48
- subTreeLeaveNames: [name],
49
- size: 1,
50
- });
51
-
52
- const makeNode = (name: string, branchLength: number, children: TreeNode[]): TreeNode => ({
53
- name,
54
- branchLength: new Decimal(branchLength),
55
- children,
56
- // subTreeNames mirrors NewickUtil: for each child include child.name + child.subTreeNames
57
- subTreeNames: children.flatMap(c => [c.name, ...(c.subTreeNames ?? [])]).filter(Boolean),
58
- subTreeLeaveNames: children.flatMap(c => c.subTreeLeaveNames ?? []),
59
- size: children.reduce((s, c) => s + (c.size ?? 1), 0),
60
- });
61
-
62
- describe('EpiTreeUtil', () => {
63
- describe('getScrollPositionFromTreeVisibility', () => {
64
- const TABLE_ROW_HEIGHT = 30;
65
-
66
- it('returns 0 when scrolled to the top', () => {
67
- // 30 items total, canvas shows 10, no scroll
68
- expect(EpiTreeUtil.getScrollPositionFromTreeVisibility({
69
- treeCanvasHeight: 300,
70
- treeHeight: 900,
71
- treeSize: 30,
72
- verticalScrollPosition: 0,
73
- zoomLevel: 1,
74
- itemHeight: TABLE_ROW_HEIGHT,
75
- })).toBe(0);
76
- });
77
-
78
- it('returns the correct position when scrolled to the middle', () => {
79
- // scrolledByItems = 10, maxItemsInView = 10, average = 15
80
- // newScrollPosition = 15 * 30 - 150 = 300
81
- expect(EpiTreeUtil.getScrollPositionFromTreeVisibility({
82
- treeCanvasHeight: 300,
83
- treeHeight: 900,
84
- treeSize: 30,
85
- verticalScrollPosition: 300,
86
- zoomLevel: 1,
87
- itemHeight: TABLE_ROW_HEIGHT,
88
- })).toBe(300);
89
- });
90
-
91
- it('returns treeHeight - treeCanvasHeight when scrolled past the end', () => {
92
- // scrolledByItems = 30 (over-scrolled), lastItemInView clamped to treeSize (30)
93
- // average = 30, newScrollPosition clamped to 900 - 300 = 600
94
- expect(EpiTreeUtil.getScrollPositionFromTreeVisibility({
95
- treeCanvasHeight: 300,
96
- treeHeight: 900,
97
- treeSize: 30,
98
- verticalScrollPosition: 900,
99
- zoomLevel: 1,
100
- itemHeight: TABLE_ROW_HEIGHT,
101
- })).toBe(600);
102
- });
103
-
104
- it('accounts for zoom level when computing scroll position', () => {
105
- // zoomLevel = 2: scaledItemHeight = 15, maxItemsInView = 20
106
- // firstItemInView = 0, lastItemInView = min(20, 30) = 20, average = 10
107
- // newScrollPosition = 10 * 30 - 150 = 150
108
- expect(EpiTreeUtil.getScrollPositionFromTreeVisibility({
109
- treeCanvasHeight: 300,
110
- treeHeight: 900,
111
- treeSize: 30,
112
- verticalScrollPosition: 0,
113
- zoomLevel: 2,
114
- itemHeight: TABLE_ROW_HEIGHT,
115
- })).toBe(150);
116
- });
117
-
118
- it('clamps result to 0 when computed position is negative', () => {
119
- // Large canvas fits many items; averageItemInView * rowHeight < treeCanvasHeight/2
120
- // treeCanvasHeight = 600, scrollPosition = 0, treeSize = 10, treeHeight = 300
121
- // scaledItemHeight = 30, scrolledByItems = 0, maxItemsInView = 20
122
- // lastItemInView = min(20, 10) = 10, average = 5
123
- // candidate = 5 * 30 - 300 = -150 → clamped to 0
124
- expect(EpiTreeUtil.getScrollPositionFromTreeVisibility({
125
- treeCanvasHeight: 600,
126
- treeHeight: 300,
127
- treeSize: 10,
128
- verticalScrollPosition: 0,
129
- zoomLevel: 1,
130
- itemHeight: TABLE_ROW_HEIGHT,
131
- })).toBe(0);
132
- });
133
- });
134
-
135
- describe('getTickMarkScale', () => {
136
- beforeAll(() => {
137
- vi.spyOn(ConfigManager.instance, 'config', 'get').mockReturnValue({
138
- epiTree: {
139
- MIN_SCALE_WIDTH_PX: 48,
140
- MAX_SCALE_WIDTH_PX: 144,
141
- SCALE_INCREMENTS: [1, 2, 5, 10, 20, 50],
142
- },
143
- } as Config);
144
- });
145
-
146
- afterAll(() => {
147
- vi.restoreAllMocks();
148
- });
149
-
150
- it('returns [0,0,0] when treeWidthMinusPadding is 0', () => {
151
- expect(EpiTreeUtil.getTickMarkScale({
152
- geneticTreeWidth: new Decimal(80),
153
- minGeneticScaleUnit: 1,
154
- treeWidthMinusPadding: 0,
155
- zoomLevel: 1,
156
- })).toEqual([0, 0, 0]);
157
- });
158
-
159
- it('returns [0,0,0] when minGeneticScaleUnit is 0', () => {
160
- expect(EpiTreeUtil.getTickMarkScale({
161
- geneticTreeWidth: new Decimal(80),
162
- minGeneticScaleUnit: 0,
163
- treeWidthMinusPadding: 1200,
164
- zoomLevel: 1,
165
- })).toEqual([0, 0, 0]);
166
- });
167
-
168
- it('returns [2, minGeneticScaleUnit, minGeneticScaleUnit] when geneticTree fits only 2 lines (maxNumLines clamped to 2, minNumLines > maxNumLines)', () => {
169
- // geneticTreeWidth=0.8, minGeneticScaleUnit=1:
170
- // 0.8/1 + 1 = 1.8 < maxNumLines(26) → maxNumLines = max(ceil(0.8), 2) = 2
171
- // minNumLines(9) > 2 → minNumLines = 2 → maxNumLines === 2 → [2, 1, 1]
172
- expect(EpiTreeUtil.getTickMarkScale({
173
- geneticTreeWidth: new Decimal(0.8),
174
- minGeneticScaleUnit: 1,
175
- treeWidthMinusPadding: 1200,
176
- zoomLevel: 1,
177
- })).toEqual([2, 1, 1]);
178
- });
179
-
180
- it('clamps maxNumLines and sets minNumLines = maxNumLines when geneticTree has few divisions', () => {
181
- // geneticTreeWidth=4, minGeneticScaleUnit=1:
182
- // 4/1 + 1 = 5 < maxNumLines(26) → maxNumLines = max(ceil(4), 2) = 4
183
- // minNumLines(9) > 4 → minNumLines = 4, maxNumLines = 4
184
- // numLines=4, increment=1: product=4, leftover=0 → [5, 1, 1]
185
- expect(EpiTreeUtil.getTickMarkScale({
186
- geneticTreeWidth: new Decimal(4),
187
- minGeneticScaleUnit: 1,
188
- treeWidthMinusPadding: 1200,
189
- zoomLevel: 1,
190
- })).toEqual([5, 1, 1]);
191
- });
192
-
193
- it('skips increments smaller than minGeneticScaleUnit', () => {
194
- // minGeneticScaleUnit=5 causes increments [1, 2] to be skipped
195
- // numLines=16, increment=5: product=80, leftover=0 → [17, 5, 5]
196
- expect(EpiTreeUtil.getTickMarkScale({
197
- geneticTreeWidth: new Decimal(80),
198
- minGeneticScaleUnit: 5,
199
- treeWidthMinusPadding: 1200,
200
- zoomLevel: 1,
201
- })).toEqual([17, 5, 5]);
202
- });
203
-
204
- it('accounts for zoom level by reducing effective width', () => {
205
- // zoomLevel=2: width=600 → minNumLines=5, maxNumLines=14
206
- // numLines=8, increment=10: product=80, leftover=0 → [9, 10, 1]
207
- expect(EpiTreeUtil.getTickMarkScale({
208
- geneticTreeWidth: new Decimal(80),
209
- minGeneticScaleUnit: 1,
210
- treeWidthMinusPadding: 1200,
211
- zoomLevel: 2,
212
- })).toEqual([9, 10, 1]);
213
- });
214
-
215
- it('determines the tick mark scale for various tree widths', () => {
216
- const cases: Array<[ArgumentTypes<typeof EpiTreeUtil.getTickMarkScale>[0], [number, number, number]]> = [
217
- [{
218
- geneticTreeWidth: new Decimal(16),
219
- minGeneticScaleUnit: 1,
220
- treeWidthMinusPadding: 1200,
221
- zoomLevel: 1,
222
- }, [17, 1, 1]],
223
- [{
224
- geneticTreeWidth: new Decimal(80),
225
- minGeneticScaleUnit: 1,
226
- treeWidthMinusPadding: 1200,
227
- zoomLevel: 1,
228
- }, [17, 5, 1]],
229
- [{
230
- geneticTreeWidth: new Decimal(150),
231
- minGeneticScaleUnit: 1,
232
- treeWidthMinusPadding: 1200,
233
- zoomLevel: 1,
234
- }, [16, 10, 1]],
235
- ];
236
-
237
- cases.forEach(([input, expectedOutput]) => {
238
- expect(EpiTreeUtil.getTickMarkScale(input)).toEqual(expectedOutput);
239
- });
240
- });
241
- });
242
-
243
- describe('sanitizeTree', () => {
244
- it('returns the same root node reference', () => {
245
- const root = makeNode('Root', 0, [makeLeaf('A'), makeLeaf('B')]);
246
- expect(EpiTreeUtil.sanitizeTree(root)).toBe(root);
247
- });
248
-
249
- it('leaves a leaf node (no children) unchanged', () => {
250
- const leaf = makeLeaf('A');
251
- EpiTreeUtil.sanitizeTree(leaf);
252
- expect(leaf.children).toBeUndefined();
253
- expect(leaf.subTreeNames).toEqual([]);
254
- });
255
-
256
- it('does not modify children when all branch lengths are positive', () => {
257
- // Inner has branchLength=1: nodesToMove=[] → splice is a no-op
258
- const a = makeLeaf('A');
259
- const b = makeLeaf('B');
260
- const inner = makeNode('Inner', 1, [b, makeLeaf('C')]);
261
- const root = makeNode('Root', 0, [a, inner]);
262
-
263
- EpiTreeUtil.sanitizeTree(root);
264
-
265
- expect(root.children?.map(c => c.name)).toEqual(['A', 'Inner']);
266
- });
267
-
268
- it('collapses a zero-branch-length inner node by hoisting its children', () => {
269
- // Inner (bl=0) should be removed; its children B, C hoisted to Root
270
- const a = makeLeaf('A');
271
- const b = makeLeaf('B');
272
- const c = makeLeaf('C');
273
- const inner = makeNode('Inner', 0, [b, c]);
274
- const root = makeNode('Root', 0, [a, inner]);
275
-
276
- EpiTreeUtil.sanitizeTree(root);
277
-
278
- expect(root.children?.map(n => n.name)).toEqual(['A', 'B', 'C']);
279
- });
280
-
281
- it('removes the collapsed node name from subTreeNames', () => {
282
- // Root.subTreeNames starts as ['A', 'Inner', 'B', 'C']; 'Inner' should be removed
283
- const root = makeNode('Root', 0, [
284
- makeLeaf('A'),
285
- makeNode('Inner', 0, [makeLeaf('B'), makeLeaf('C')]),
286
- ]);
287
-
288
- EpiTreeUtil.sanitizeTree(root);
289
-
290
- expect(root.subTreeNames).toEqual(['A', 'B', 'C']);
291
- });
292
-
293
- it('does not collapse a non-zero-branch inner node and leaves subTreeNames intact', () => {
294
- const inner = makeNode('Inner', 1, [makeLeaf('B'), makeLeaf('C')]);
295
- const root = makeNode('Root', 0, [makeLeaf('A'), inner]);
296
- const originalSubTreeNames = [...root.subTreeNames];
297
-
298
- EpiTreeUtil.sanitizeTree(root);
299
-
300
- expect(root.children?.map(n => n.name)).toEqual(['A', 'Inner']);
301
- expect(root.subTreeNames).toEqual(originalSubTreeNames);
302
- });
303
-
304
- it('treats a node with undefined branchLength as zero-branch (uses ?? 0 fallback)', () => {
305
- // inner has no branchLength property → branchLength?.toNumber() ?? 0 === 0 → treated as zero-branch
306
- const inner = {
307
- name: 'Inner',
308
- subTreeLeaveNames: ['B', 'C'],
309
- subTreeNames: ['B', 'C'],
310
- size: 2,
311
- children: [makeLeaf('B'), makeLeaf('C')],
312
- } as TreeNode;
313
- const root = makeNode('Root', 0, [makeLeaf('A'), inner]);
314
-
315
- EpiTreeUtil.sanitizeTree(root);
316
-
317
- // inner treated as zero-branch → its children B,C hoisted to root
318
- expect(root.children?.map(n => n.name)).toEqual(['A', 'B', 'C']);
319
- });
320
-
321
- it('collapses multiple zero-branch inner nodes at the same level', () => {
322
- // Both Inner1 and Inner2 (bl=0) should be collapsed
323
- const root = makeNode('Root', 0, [
324
- makeNode('Inner1', 0, [makeLeaf('A'), makeLeaf('B')]),
325
- makeNode('Inner2', 0, [makeLeaf('C'), makeLeaf('D')]),
326
- ]);
327
-
328
- EpiTreeUtil.sanitizeTree(root);
329
-
330
- expect(root.children?.map(n => n.name)).toEqual(['A', 'B', 'C', 'D']);
331
- expect(root.subTreeNames).toEqual(['A', 'B', 'C', 'D']);
332
- });
333
-
334
- it('collapses only zero-branch inner nodes when mixed with non-zero ones', () => {
335
- // Inner1 (bl=0) collapsed; Inner2 (bl=1) kept
336
- const root = makeNode('Root', 0, [
337
- makeNode('Inner1', 0, [makeLeaf('A'), makeLeaf('B')]),
338
- makeNode('Inner2', 1, [makeLeaf('C'), makeLeaf('D')]),
339
- ]);
340
-
341
- EpiTreeUtil.sanitizeTree(root);
342
-
343
- expect(root.children?.map(n => n.name)).toEqual(['A', 'B', 'Inner2']);
344
- expect(root.subTreeNames).toEqual(['A', 'B', 'Inner2', 'C', 'D']);
345
- });
346
-
347
- it('collapses deeply nested zero-branch nodes level by level', () => {
348
- // Outer(bl=0) -> [Inner(bl=0) -> [B, C], D]
349
- // Inner first collapsed into Outer → Outer.children=[B,C,D]
350
- // Then Outer collapsed into Root → Root.children=[A,B,C,D]
351
- const b = makeLeaf('B');
352
- const c = makeLeaf('C');
353
- const inner = makeNode('Inner', 0, [b, c]);
354
- const d = makeLeaf('D');
355
- const outer = makeNode('Outer', 0, [inner, d]);
356
- const a = makeLeaf('A');
357
- const root = makeNode('Root', 0, [a, outer]);
358
-
359
- EpiTreeUtil.sanitizeTree(root);
360
-
361
- expect(root.children?.map(n => n.name)).toEqual(['A', 'B', 'C', 'D']);
362
- });
363
-
364
- it("retains orphaned ancestor names in ancestor's subTreeNames after deep collapse", () => {
365
- // Root.subTreeNames initially: ['A','Outer','Inner','B','C','D']
366
- // After collapse: 'Outer' is spliced out but 'Inner' (already listed before 'Outer') remains
367
- const root = makeNode('Root', 0, [
368
- makeLeaf('A'),
369
- makeNode('Outer', 0, [
370
- makeNode('Inner', 0, [makeLeaf('B'), makeLeaf('C')]),
371
- makeLeaf('D'),
372
- ]),
373
- ]);
374
-
375
- EpiTreeUtil.sanitizeTree(root);
376
-
377
- // 'Outer' is removed; 'Inner' stays because it was spliced from Outer (not Root) subTreeNames
378
- expect(root.subTreeNames).toEqual(['A', 'Inner', 'B', 'C', 'D']);
379
- });
380
- });
381
-
382
- describe('createTreeAddresses', () => {
383
- it('assigns an empty string address to a single root leaf', () => {
384
- const root = makeLeaf('root');
385
- expect(EpiTreeUtil.createTreeAddresses(root)).toEqual({ root: '' });
386
- });
387
-
388
- it('treats a child with undefined branchLength as zero-branch (uses ?? 0 fallback)', () => {
389
- // child has no branchLength → (child.branchLength?.toNumber() ?? 0) === 0 → assigned index 1
390
- const zeroBLChild = { name: 'z', subTreeLeaveNames: ['z'], subTreeNames: [], size: 1 } as TreeNode;
391
- const root = makeNode('root', 0, [zeroBLChild, makeLeaf('a', 1)]);
392
- const addresses = EpiTreeUtil.createTreeAddresses(root);
393
- expect(addresses['z']).toBe('1');
394
- expect(addresses['a']).toBe('2');
395
- });
396
-
397
- it('assigns "" to the root and "1","2" to two positive-branch children', () => {
398
- // No zero-branch children → index starts at 1
399
- const root = makeNode('root', 0, [makeLeaf('a'), makeLeaf('b')]);
400
- expect(EpiTreeUtil.createTreeAddresses(root)).toEqual({
401
- root: '',
402
- a: '1',
403
- b: '2',
404
- });
405
- });
406
-
407
- it('increments index for each additional positive-branch child', () => {
408
- // Three positive-branch children → "1", "2", "3"
409
- const root = makeNode('root', 0, [makeLeaf('a'), makeLeaf('b'), makeLeaf('c')]);
410
- expect(EpiTreeUtil.createTreeAddresses(root)).toEqual({
411
- root: '',
412
- a: '1',
413
- b: '2',
414
- c: '3',
415
- });
416
- });
417
-
418
- it('assigns address "1" to a single zero-branch child', () => {
419
- // hasZeroBranchLength=true → zero-branch children always get index 1
420
- const root = makeNode('root', 0, [makeLeaf('a', 0)]);
421
- expect(EpiTreeUtil.createTreeAddresses(root)).toEqual({
422
- root: '',
423
- a: '1',
424
- });
425
- });
426
-
427
- it('assigns "1" to zero-branch child and "2" to following positive-branch child', () => {
428
- // hasZeroBranchLength=true → index starts at 2 for non-zero; zero child gets 1
429
- const root = makeNode('root', 0, [makeLeaf('a', 0), makeLeaf('b', 1)]);
430
- expect(EpiTreeUtil.createTreeAddresses(root)).toEqual({
431
- root: '',
432
- a: '1',
433
- b: '2',
434
- });
435
- });
436
-
437
- it('assigns "2" to a positive-branch child that appears before a zero-branch child', () => {
438
- // hasZeroBranchLength=true → non-zero children start at index 2, zero gets 1
439
- const root = makeNode('root', 0, [makeLeaf('a', 1), makeLeaf('b', 0)]);
440
- expect(EpiTreeUtil.createTreeAddresses(root)).toEqual({
441
- root: '',
442
- a: '2',
443
- b: '1',
444
- });
445
- });
446
-
447
- it('assigns "1" to all zero-branch children when every child has zero branch length', () => {
448
- // All children route to the same index 1
449
- const root = makeNode('root', 0, [makeLeaf('a', 0), makeLeaf('b', 0), makeLeaf('c', 0)]);
450
- expect(EpiTreeUtil.createTreeAddresses(root)).toEqual({
451
- root: '',
452
- a: '1',
453
- b: '1',
454
- c: '1',
455
- });
456
- });
457
-
458
- it('builds dot-notation addresses recursively for a two-level tree', () => {
459
- // root → [left(bl=1) → [a(bl=1), b(bl=1)], right(bl=1)]
460
- // root: "", left: "1", a: "1.1", b: "1.2", right: "2"
461
- const left = makeNode('left', 1, [makeLeaf('a'), makeLeaf('b')]);
462
- const root = makeNode('root', 0, [left, makeLeaf('right')]);
463
- expect(EpiTreeUtil.createTreeAddresses(root)).toEqual({
464
- root: '',
465
- left: '1',
466
- a: '1.1',
467
- b: '1.2',
468
- right: '2',
469
- });
470
- });
471
-
472
- it('correctly addresses a zero-branch inner node and its children', () => {
473
- // root → [inner(bl=0) → [a(bl=1), b(bl=1)], c(bl=1)]
474
- // hasZero=true at root level → inner: "1", c: "2"
475
- // At inner level, no zero-branch → a: "1.1", b: "1.2"
476
- const inner = makeNode('inner', 0, [makeLeaf('a'), makeLeaf('b')]);
477
- const root = makeNode('root', 0, [inner, makeLeaf('c')]);
478
- expect(EpiTreeUtil.createTreeAddresses(root)).toEqual({
479
- root: '',
480
- inner: '1',
481
- a: '1.1',
482
- b: '1.2',
483
- c: '2',
484
- });
485
- });
486
-
487
- it('correctly addresses a three-level deep tree with mixed branch lengths', () => {
488
- // root → [x(bl=0) → [y(bl=1) → [p(bl=1), q(bl=1)]], z(bl=1)]
489
- // root level: hasZero=true → x: "1", z: "2"
490
- // x level: no zero-branch → y: "1.1"
491
- // y level: no zero-branch → p: "1.1.1", q: "1.1.2"
492
- const y = makeNode('y', 1, [makeLeaf('p'), makeLeaf('q')]);
493
- const x = makeNode('x', 0, [y]);
494
- const root = makeNode('root', 0, [x, makeLeaf('z')]);
495
- expect(EpiTreeUtil.createTreeAddresses(root)).toEqual({
496
- root: '',
497
- x: '1',
498
- y: '1.1',
499
- p: '1.1.1',
500
- q: '1.1.2',
501
- z: '2',
502
- });
503
- });
504
- });
505
-
506
- describe('findNewTreeRoot', () => {
507
- it("selector 'node': returns a new object — not the original node reference", () => {
508
- const target = { ...makeLeaf('target', 3), maxBranchLength: new Decimal(10) };
509
- const root = makeNode('root', 0, [target]);
510
- const result = EpiTreeUtil.findNewTreeRoot(root, 'target', 'node');
511
- expect(result).not.toBe(target);
512
- });
513
-
514
- it("selector 'node': sets branchLength to 0 on the matched node", () => {
515
- const target = { ...makeLeaf('target', 3), maxBranchLength: new Decimal(10) };
516
- const root = makeNode('root', 0, [target]);
517
- const result = EpiTreeUtil.findNewTreeRoot(root, 'target', 'node');
518
- expect(result.branchLength.toNumber()).toBe(0);
519
- });
520
-
521
- it("selector 'node': adjusts maxBranchLength by subtracting the original branchLength", () => {
522
- // maxBranchLength=10, branchLength=3 → result maxBranchLength = 7
523
- const target = { ...makeLeaf('target', 3), maxBranchLength: new Decimal(10) };
524
- const root = makeNode('root', 0, [target]);
525
- const result = EpiTreeUtil.findNewTreeRoot(root, 'target', 'node');
526
- expect(result.maxBranchLength.toNumber()).toBe(7);
527
- });
528
-
529
- it("selector 'node': preserves other properties of the matched node", () => {
530
- const target = { ...makeLeaf('target', 3), maxBranchLength: new Decimal(10) };
531
- const root = makeNode('root', 0, [target]);
532
- const result = EpiTreeUtil.findNewTreeRoot(root, 'target', 'node');
533
- expect(result.name).toBe('target');
534
- expect(result.subTreeLeaveNames).toEqual(['target']);
535
- });
536
-
537
- it("selector 'node': matches the root node itself when name equals nodeName", () => {
538
- const root: TreeNode = {
539
- name: 'root',
540
- branchLength: new Decimal(2),
541
- maxBranchLength: new Decimal(8),
542
- children: [makeLeaf('a')],
543
- subTreeNames: ['a'],
544
- subTreeLeaveNames: ['a'],
545
- size: 1,
546
- };
547
- const result = EpiTreeUtil.findNewTreeRoot(root, 'root', 'node');
548
- expect(result.name).toBe('root');
549
- expect(result.branchLength.toNumber()).toBe(0);
550
- expect(result.maxBranchLength.toNumber()).toBe(6); // 8 - 2
551
- });
552
-
553
- it("selector 'node': finds a direct child leaf", () => {
554
- const target = { ...makeLeaf('target', 4), maxBranchLength: new Decimal(12) };
555
- const root = makeNode('root', 0, [makeLeaf('other'), target]);
556
- const result = EpiTreeUtil.findNewTreeRoot(root, 'target', 'node');
557
- expect(result.name).toBe('target');
558
- expect(result.branchLength.toNumber()).toBe(0);
559
- expect(result.maxBranchLength.toNumber()).toBe(8); // 12 - 4
560
- });
561
-
562
- it("selector 'node': finds a deeply nested node", () => {
563
- const deepTarget = { ...makeLeaf('deepTarget', 5), maxBranchLength: new Decimal(15) };
564
- const inner = makeNode('inner', 2, [deepTarget, makeLeaf('sibling')]);
565
- const root = makeNode('root', 0, [makeLeaf('unrelated'), inner]);
566
- const result = EpiTreeUtil.findNewTreeRoot(root, 'deepTarget', 'node');
567
- expect(result.name).toBe('deepTarget');
568
- expect(result.branchLength.toNumber()).toBe(0);
569
- expect(result.maxBranchLength.toNumber()).toBe(10); // 15 - 5
570
- });
571
-
572
- it("selector 'parent': returns root when matched child is a direct child of root", () => {
573
- const root: TreeNode = {
574
- name: 'root',
575
- branchLength: new Decimal(0),
576
- maxBranchLength: new Decimal(10),
577
- children: [makeLeaf('a'), makeLeaf('b')],
578
- subTreeNames: ['a', 'b'],
579
- subTreeLeaveNames: ['a', 'b'],
580
- size: 2,
581
- };
582
- const result = EpiTreeUtil.findNewTreeRoot(root, 'a', 'parent');
583
- expect(result.name).toBe('root');
584
- expect(result.branchLength.toNumber()).toBe(0);
585
- expect(result.maxBranchLength.toNumber()).toBe(10); // 10 - 0
586
- });
587
-
588
- it("selector 'parent': returns the immediate parent of a second-level node", () => {
589
- const parent: TreeNode = {
590
- name: 'parent',
591
- branchLength: new Decimal(3),
592
- maxBranchLength: new Decimal(9),
593
- children: [makeLeaf('target'), makeLeaf('sibling')],
594
- subTreeNames: ['target', 'sibling'],
595
- subTreeLeaveNames: ['target', 'sibling'],
596
- size: 2,
597
- };
598
- const root = makeNode('root', 0, [makeLeaf('unrelated'), parent]);
599
- const result = EpiTreeUtil.findNewTreeRoot(root, 'target', 'parent');
600
- expect(result.name).toBe('parent');
601
- expect(result.branchLength.toNumber()).toBe(0);
602
- expect(result.maxBranchLength.toNumber()).toBe(6); // 9 - 3
603
- });
604
-
605
- it("selector 'parent': preserves the children and other properties of the matched parent", () => {
606
- const parent: TreeNode = {
607
- name: 'parent',
608
- branchLength: new Decimal(1),
609
- maxBranchLength: new Decimal(5),
610
- children: [makeLeaf('target'), makeLeaf('sibling')],
611
- subTreeNames: ['target', 'sibling'],
612
- subTreeLeaveNames: ['target', 'sibling'],
613
- size: 2,
614
- };
615
- const root = makeNode('root', 0, [parent]);
616
- const result = EpiTreeUtil.findNewTreeRoot(root, 'target', 'parent');
617
- expect(result.children).toBe(parent.children);
618
- expect(result.subTreeLeaveNames).toEqual(['target', 'sibling']);
619
- });
620
-
621
- it("selector 'node': when branchLength equals maxBranchLength the adjusted maxBranchLength is 0", () => {
622
- // branchLength=5, maxBranchLength=5 → maxBranchLength result = 0
623
- const target = { ...makeLeaf('target', 5), maxBranchLength: new Decimal(5) };
624
- const root = makeNode('root', 0, [target]);
625
- const result = EpiTreeUtil.findNewTreeRoot(root, 'target', 'node');
626
- expect(result.branchLength.toNumber()).toBe(0);
627
- expect(result.maxBranchLength.toNumber()).toBe(0);
628
- });
629
- });
630
-
631
- describe('getMinGeneticScaleUnit', () => {
632
- it('returns Infinity when passed a falsy value', () => {
633
- expect(EpiTreeUtil.getMinGeneticScaleUnit(null)).toBe(Infinity);
634
- });
635
-
636
- it('returns the branchLength of a single leaf node', () => {
637
- expect(EpiTreeUtil.getMinGeneticScaleUnit(makeLeaf('a', 3))).toBe(3);
638
- });
639
-
640
- it('returns Infinity for a leaf whose branchLength is 0 (zero is treated as missing)', () => {
641
- expect(EpiTreeUtil.getMinGeneticScaleUnit(makeLeaf('a', 0))).toBe(Infinity);
642
- });
643
-
644
- it('returns Infinity for a leaf with no branchLength set', () => {
645
- const leaf: TreeNode = { name: 'a', subTreeLeaveNames: ['a'], subTreeNames: [], size: 1 };
646
- expect(EpiTreeUtil.getMinGeneticScaleUnit(leaf)).toBe(Infinity);
647
- });
648
-
649
- it('returns the minimum among multiple leaf children', () => {
650
- // leaves: 3, 1, 7 → min = 1
651
- const root = makeNode('root', 0, [makeLeaf('a', 3), makeLeaf('b', 1), makeLeaf('c', 7)]);
652
- expect(EpiTreeUtil.getMinGeneticScaleUnit(root)).toBe(1);
653
- });
654
-
655
- it('ignores internal (non-leaf) node branch lengths when finding the minimum', () => {
656
- // inner branchLength=0.5 (very small) but it has children → should be ignored
657
- // leaf b branchLength=2 is the true minimum
658
- const inner = makeNode('inner', 0.5, [makeLeaf('b', 2), makeLeaf('c', 5)]);
659
- const root = makeNode('root', 0, [makeLeaf('a', 3), inner]);
660
- expect(EpiTreeUtil.getMinGeneticScaleUnit(root)).toBe(2);
661
- });
662
-
663
- it('skips zero-branch leaves and returns the minimum positive one', () => {
664
- // leaf a=0 (skipped), leaf b=4, leaf c=2 → min = 2
665
- const root = makeNode('root', 0, [makeLeaf('a', 0), makeLeaf('b', 4), makeLeaf('c', 2)]);
666
- expect(EpiTreeUtil.getMinGeneticScaleUnit(root)).toBe(2);
667
- });
668
-
669
- it('returns Infinity when all leaves have zero branchLength', () => {
670
- const root = makeNode('root', 0, [makeLeaf('a', 0), makeLeaf('b', 0)]);
671
- expect(EpiTreeUtil.getMinGeneticScaleUnit(root)).toBe(Infinity);
672
- });
673
-
674
- it('traverses deeply nested leaves correctly', () => {
675
- // root → inner → deep leaf with branchLength=0.1 (smallest)
676
- // other leaves: 5, 3
677
- const deep = makeLeaf('deep', 0.1);
678
- const inner = makeNode('inner', 2, [deep, makeLeaf('sibling', 3)]);
679
- const root = makeNode('root', 0, [makeLeaf('a', 5), inner]);
680
- expect(EpiTreeUtil.getMinGeneticScaleUnit(root)).toBe(0.1);
681
- });
682
-
683
- it('handles a tree where the root is itself a leaf (no children)', () => {
684
- const leaf = makeLeaf('solo', 7);
685
- expect(EpiTreeUtil.getMinGeneticScaleUnit(leaf)).toBe(7);
686
- });
687
-
688
- it('returns the sole positive leaf value when only one leaf has a positive branchLength', () => {
689
- const root = makeNode('root', 0, [makeLeaf('a', 0), makeLeaf('b', 0), makeLeaf('c', 6)]);
690
- expect(EpiTreeUtil.getMinGeneticScaleUnit(root)).toBe(6);
691
- });
692
- });
693
-
694
- describe('assembleTree', () => {
695
- const TABLE_ROW_HEIGHT = 30;
696
- const TREE_PADDING = 10;
697
- const LEAF_DOT_RADIUS = 4;
698
- const ANCESTOR_DOT_RADIUS = 3;
699
- const MINIMUM_DISTANCE_PERCENTAGE_TO_SHOW_LABEL = 5;
700
- const pixelToGeneticDistanceRatio = 100;
701
- const treeCanvasWidth = 800;
702
-
703
- beforeAll(() => {
704
- vi.spyOn(ConfigManager.instance, 'config', 'get').mockReturnValue({
705
- epiTree: {
706
- TREE_PADDING,
707
- LEAF_DOT_RADIUS,
708
- ANCESTOR_DOT_RADIUS,
709
- MINIMUM_DISTANCE_PERCENTAGE_TO_SHOW_LABEL,
710
- },
711
- } as Config);
712
- });
713
-
714
- afterAll(() => {
715
- vi.restoreAllMocks();
716
- });
717
-
718
- it('returns an assembly with empty arrays for a single leaf (no ancestors)', () => {
719
- // leaf a: distance=0, bl=2 → leafXPxEnd=210, leafYPx=15, leafXPxStart=10
720
- const tree = makeLeaf('a', 2);
721
- const asm = EpiTreeUtil.assembleTree({ rootNode: tree, treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
722
-
723
- expect(asm.leafNodes).toHaveLength(1);
724
- expect(asm.leafNodes[0].nodeName).toBe('a');
725
- expect(asm.leafTreeLines).toHaveLength(1);
726
- expect(asm.leafTreeLines[0].nodeName).toBe('a');
727
- expect(asm.ancestorNodes).toHaveLength(0);
728
- expect(asm.horizontalAncestorTreeLines).toHaveLength(0);
729
- expect(asm.verticalAncestorTreeLines).toHaveLength(0);
730
- expect(asm.nodePathPropertiesMap.size).toBe(1);
731
- expect(asm.horizontalLinePathPropertiesMap.size).toBe(1);
732
- expect(asm.verticalLinePathPropertiesMap.size).toBe(0);
733
- });
734
-
735
- it('produces correct support line coordinates for a single leaf', () => {
736
- // leafXPxEnd = (0+2)*100+10 = 210, leafYPx = 15
737
- const asm = EpiTreeUtil.assembleTree({ rootNode: makeLeaf('a', 2), treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
738
- expect(asm.supportLines).toEqual([{ nodeName: 'a', fromX: 210, toX: treeCanvasWidth, y: 15 }]);
739
- });
740
-
741
- it('produces one entry per leaf in leafNodes, leafTreeLines, and supportLines for a two-leaf tree', () => {
742
- // root(bl=0) → [a(bl=2), b(bl=3)]
743
- const tree = makeNode('root', 0, [makeLeaf('a', 2), makeLeaf('b', 3)]);
744
- const asm = EpiTreeUtil.assembleTree({ rootNode: tree, treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
745
-
746
- expect(asm.leafNodes).toHaveLength(2);
747
- expect(asm.leafTreeLines).toHaveLength(2);
748
- expect(asm.supportLines).toHaveLength(2);
749
- });
750
-
751
- it('DFS leaf ordering is reflected in support line Y positions', () => {
752
- // root(bl=0) → [a(bl=2), b(bl=3)]
753
- // a at leafIndex=0: y=15; b at leafIndex=1: y=45
754
- const tree = makeNode('root', 0, [makeLeaf('a', 2), makeLeaf('b', 3)]);
755
- const asm = EpiTreeUtil.assembleTree({ rootNode: tree, treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
756
-
757
- expect(asm.supportLines[0]).toEqual({ nodeName: 'a', fromX: 210, toX: treeCanvasWidth, y: 15 });
758
- expect(asm.supportLines[1]).toEqual({ nodeName: 'b', fromX: 310, toX: treeCanvasWidth, y: 45 });
759
- });
760
-
761
- it('produces one horizontalAncestorTreeLine and two verticalAncestorTreeLines for a two-leaf tree', () => {
762
- const tree = makeNode('root', 0, [makeLeaf('a', 2), makeLeaf('b', 3)]);
763
- const asm = EpiTreeUtil.assembleTree({ rootNode: tree, treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
764
-
765
- expect(asm.horizontalAncestorTreeLines).toHaveLength(1);
766
- expect(asm.verticalAncestorTreeLines).toHaveLength(2);
767
- });
768
-
769
- it("includes all descendant leaf names in the ancestor's horizontalAncestorTreeLine nodeNames", () => {
770
- // root horizontal line nodeNames = ['root', 'a', 'b']
771
- const tree = makeNode('root', 0, [makeLeaf('a', 2), makeLeaf('b', 3)]);
772
- const asm = EpiTreeUtil.assembleTree({ rootNode: tree, treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
773
-
774
- expect(asm.horizontalAncestorTreeLines[0].nodeNames).toEqual(['root', 'a', 'b']);
775
- });
776
-
777
- it('adds an ancestor dot when all children have positive branch lengths', () => {
778
- // both a and b have bl > 0 → ancestor dot
779
- const tree = makeNode('root', 0, [makeLeaf('a', 2), makeLeaf('b', 3)]);
780
- const asm = EpiTreeUtil.assembleTree({ rootNode: tree, treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
781
-
782
- expect(asm.ancestorNodes).toHaveLength(1);
783
- expect(asm.ancestorNodes[0].nodeNames).toContain('root');
784
- });
785
-
786
- it('does NOT add an ancestor dot when a child has zero branch length', () => {
787
- // leaf 'a' has bl=0 → root.children.every(bl > 0) is false → no dot
788
- const tree = makeNode('root', 0, [makeLeaf('a', 0), makeLeaf('b', 2)]);
789
- const asm = EpiTreeUtil.assembleTree({ rootNode: tree, treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
790
-
791
- expect(asm.ancestorNodes).toHaveLength(0);
792
- });
793
-
794
- it('correctly sizes all maps for a two-leaf tree', () => {
795
- // nodePathPropertiesMap: 2 leaf dots + 1 ancestor dot = 3
796
- // horizontalLinePathPropertiesMap: 2 leaf lines + 1 ancestor line = 3
797
- // verticalLinePathPropertiesMap: 2 vertical lines
798
- const tree = makeNode('root', 0, [makeLeaf('a', 2), makeLeaf('b', 3)]);
799
- const asm = EpiTreeUtil.assembleTree({ rootNode: tree, treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
800
-
801
- expect(asm.nodePathPropertiesMap.size).toBe(3);
802
- expect(asm.horizontalLinePathPropertiesMap.size).toBe(3);
803
- expect(asm.verticalLinePathPropertiesMap.size).toBe(2);
804
- });
805
-
806
- it('stores correct subTreeLeaveNames for a leaf in nodePathPropertiesMap', () => {
807
- const leafA = makeLeaf('a', 2);
808
- const asm = EpiTreeUtil.assembleTree({ rootNode: leafA, treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
809
-
810
- const values = [...asm.nodePathPropertiesMap.values()];
811
- expect(values[0].subTreeLeaveNames).toEqual(['a']);
812
- expect(values[0].treeNode).toBe(leafA);
813
- });
814
-
815
- it('stores correct subTreeLeaveNames for an ancestor in horizontalLinePathPropertiesMap', () => {
816
- // ancestor line subTreeLeaveNames = caseIds = ['a', 'b']
817
- const tree = makeNode('root', 0, [makeLeaf('a', 2), makeLeaf('b', 3)]);
818
- const asm = EpiTreeUtil.assembleTree({ rootNode: tree, treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
819
-
820
- // The ancestor's horizontal line is the last entry (added after the two leaf lines)
821
- const mapValues = [...asm.horizontalLinePathPropertiesMap.values()];
822
- const ancestorEntry = mapValues.find(v => v.subTreeLeaveNames.length === 2);
823
- expect(ancestorEntry?.subTreeLeaveNames).toEqual(['a', 'b']);
824
- });
825
-
826
- it('correctly assembles a three-leaf tree with two levels', () => {
827
- // root(bl=0) → [left(bl=1) → [a(bl=1), b(bl=1)], c(bl=2)]
828
- // leaf a: distance=1, y=15; leaf b: distance=1, y=45; leaf c: distance=0, y=75
829
- const tree = makeNode('root', 0, [
830
- makeNode('left', 1, [makeLeaf('a', 1), makeLeaf('b', 1)]),
831
- makeLeaf('c', 2),
832
- ]);
833
- const asm = EpiTreeUtil.assembleTree({ rootNode: tree, treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
834
-
835
- expect(asm.leafNodes).toHaveLength(3);
836
- expect(asm.leafNodes.map(n => n.nodeName)).toEqual(['a', 'b', 'c']);
837
- expect(asm.horizontalAncestorTreeLines).toHaveLength(2);
838
- expect(asm.ancestorNodes).toHaveLength(2); // both 'left' and 'root' have positive-branch children
839
- // 2 vertical lines for 'left' + 2 for 'root'
840
- expect(asm.verticalAncestorTreeLines).toHaveLength(4);
841
- });
842
-
843
- it('produces correct support lines for a three-leaf tree in DFS order', () => {
844
- // root(bl=0) → [left(bl=1) → [a(bl=1), b(bl=1)], c(bl=2)]
845
- // a: leafXPxEnd=(1+1)*100+10=210, y=15
846
- // b: leafXPxEnd=210, y=45
847
- // c: leafXPxEnd=(0+2)*100+10=210, y=75
848
- const tree = makeNode('root', 0, [
849
- makeNode('left', 1, [makeLeaf('a', 1), makeLeaf('b', 1)]),
850
- makeLeaf('c', 2),
851
- ]);
852
- const asm = EpiTreeUtil.assembleTree({ rootNode: tree, treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
853
-
854
- expect(asm.supportLines[0]).toEqual({ nodeName: 'a', fromX: 210, toX: treeCanvasWidth, y: 15 });
855
- expect(asm.supportLines[1]).toEqual({ nodeName: 'b', fromX: 210, toX: treeCanvasWidth, y: 45 });
856
- expect(asm.supportLines[2]).toEqual({ nodeName: 'c', fromX: 210, toX: treeCanvasWidth, y: 75 });
857
- });
858
-
859
- it('ancestor horizontal line in a three-leaf tree includes all descendant leaf names', () => {
860
- // root horizontal line nodeNames = ['root', 'a', 'b', 'c']
861
- const tree = makeNode('root', 0, [
862
- makeNode('left', 1, [makeLeaf('a', 1), makeLeaf('b', 1)]),
863
- makeLeaf('c', 2),
864
- ]);
865
- const asm = EpiTreeUtil.assembleTree({ rootNode: tree, treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
866
-
867
- const rootLine = asm.horizontalAncestorTreeLines.find(l => l.nodeNames[0] === 'root');
868
- expect(rootLine?.nodeNames).toEqual(['root', 'a', 'b', 'c']);
869
- });
870
-
871
- it('produces distance label texts when rootNode.maxBranchLength is set and branches are significant', () => {
872
- // root.maxBranchLength=10; a.bl=5 → 5/10*100=50% >= 5% → label; b.bl=10 → 100% → label
873
- // labelPrecision = max(1, 4 - len('10')) = max(1, 2) = 2
874
- // round(5, 2)='5', round(10, 2)='10'
875
- const rootNode: TreeNode = {
876
- ...makeNode('root', 0, [makeLeaf('a', 5), makeLeaf('b', 10)]),
877
- maxBranchLength: new Decimal(10),
878
- };
879
- const asm = EpiTreeUtil.assembleTree({ rootNode, treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
880
-
881
- expect(asm.distanceTexts).toHaveLength(2);
882
- expect(asm.distanceTexts[0].nodeNames).toEqual(['a']);
883
- expect(asm.distanceTexts[0].text).toBe('5');
884
- expect(asm.distanceTexts[1].nodeNames).toEqual(['b']);
885
- expect(asm.distanceTexts[1].text).toBe('10');
886
- });
887
-
888
- it('suppresses distance labels for branches below MINIMUM_DISTANCE_PERCENTAGE_TO_SHOW_LABEL', () => {
889
- // root.maxBranchLength=100; a.bl=4 → 4% < 5% → no label; b.bl=10 → 10% → label
890
- const rootNode: TreeNode = {
891
- ...makeNode('root', 0, [makeLeaf('a', 4), makeLeaf('b', 10)]),
892
- maxBranchLength: new Decimal(100),
893
- };
894
- const asm = EpiTreeUtil.assembleTree({ rootNode, treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
895
-
896
- expect(asm.distanceTexts).toHaveLength(1);
897
- expect(asm.distanceTexts[0].nodeNames).toEqual(['b']);
898
- });
899
-
900
- it('produces no distance texts when rootNode has no maxBranchLength', () => {
901
- // makeNode does not set maxBranchLength → all labels suppressed
902
- const tree = makeNode('root', 0, [makeLeaf('a', 5), makeLeaf('b', 10)]);
903
- const asm = EpiTreeUtil.assembleTree({ rootNode: tree, treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
904
-
905
- expect(asm.distanceTexts).toHaveLength(0);
906
- });
907
-
908
- it('returns an empty assembly for a null rootNode without throwing', () => {
909
- const asm = EpiTreeUtil.assembleTree({ rootNode: null as unknown as TreeNode, treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
910
- expect(asm.leafNodes).toHaveLength(0);
911
- expect(asm.ancestorNodes).toHaveLength(0);
912
- });
913
-
914
- it('handles a leaf node with undefined branchLength gracefully (uses ?? 0 fallback)', () => {
915
- // leaf without branchLength → branchLength?.toNumber() ?? 0 === 0
916
- const leafNoBL = { name: 'x', subTreeLeaveNames: ['x'], subTreeNames: [], size: 1 } as TreeNode;
917
- const asm = EpiTreeUtil.assembleTree({ rootNode: leafNoBL, treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
918
- expect(asm.leafNodes).toHaveLength(1);
919
- // leafXPxEnd = (0 + 0)*100 + TREE_PADDING = 10
920
- expect(asm.supportLines[0]).toEqual({ nodeName: 'x', fromX: 10, toX: treeCanvasWidth, y: 15 });
921
- });
922
-
923
- it('handles a child with undefined branchLength via ?? 0 fallback in the every() check', () => {
924
- // childNoBL has branchLength: undefined → every(bl > 0) uses ?? 0 → 0 > 0 = false → no dot
925
- const childNoBL = { name: 'a', subTreeLeaveNames: ['a'], subTreeNames: [], size: 1 } as TreeNode;
926
- const root = makeNode('root', 0, [childNoBL, makeLeaf('b', 0)]);
927
- const asm = EpiTreeUtil.assembleTree({ rootNode: root, treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
928
- expect(asm.leafNodes).toHaveLength(2);
929
- expect(asm.ancestorNodes).toHaveLength(0);
930
- });
931
-
932
- it('handles an ancestor node with undefined branchLength via ?? 0 fallback in traverseTree and assembleAncestorNode', () => {
933
- // ancestorNoBL has branchLength: undefined
934
- // L316: traverseTree(child, distance + (ancestorNoBL.branchLength?.toNumber() ?? 0)) fires ?? 0
935
- // L408: ancestorXPxDistance = (node.branchLength?.toNumber() ?? 0) * ratio fires ?? 0
936
- const ancestorNoBL = {
937
- name: 'anc',
938
- subTreeLeaveNames: ['a', 'b'],
939
- subTreeNames: ['a', 'b'],
940
- size: 2,
941
- children: [makeLeaf('a', 1), makeLeaf('b', 1)],
942
- } as unknown as TreeNode;
943
- const asm = EpiTreeUtil.assembleTree({ rootNode: ancestorNoBL, treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
944
- expect(asm.leafNodes).toHaveLength(2);
945
- expect(asm.ancestorNodes).toHaveLength(1); // both children have positive BL
946
- });
947
-
948
- it('invokes sort comparators when multiple children fall on the same side of the ancestor midpoint', () => {
949
- // 4 leaves at y=[15,45,75,105]; ancestorYPx=(15+105)/2=60
950
- // top group: a(15<60), b(45<60) → 2 elements → sort(a,b) => comparator invoked
951
- // bottom group: c(75>60), d(105>60) → 2 elements → sort(c,d) => comparator invoked
952
- const tree = makeNode('root', 0, [
953
- makeLeaf('a', 1),
954
- makeLeaf('b', 1),
955
- makeLeaf('c', 1),
956
- makeLeaf('d', 1),
957
- ]);
958
- const asm = EpiTreeUtil.assembleTree({ rootNode: tree, treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
959
- // 4 vertical chunk paths: 2 for top group (each chunk ending at ancestorYPx) + 2 for bottom group
960
- expect(asm.verticalAncestorTreeLines.length).toBeGreaterThanOrEqual(4);
961
- });
962
-
963
- it('adds distance text for an ancestor node with a significant branch length', () => {
964
- // inner.bl=5 is 50% of maxBranchLength=10, which is >= MINIMUM_DISTANCE_PERCENTAGE_TO_SHOW_LABEL=5%
965
- const inner = makeNode('inner', 5, [makeLeaf('a', 5), makeLeaf('b', 5)]);
966
- const rootNode: TreeNode = {
967
- ...makeNode('root', 0, [inner]),
968
- maxBranchLength: new Decimal(10),
969
- };
970
- const asm = EpiTreeUtil.assembleTree({ rootNode, treeCanvasWidth, pixelToGeneticDistanceRatio, itemHeight: TABLE_ROW_HEIGHT });
971
- const ancestorText = asm.distanceTexts.find(t => t.nodeNames.includes('inner'));
972
- expect(ancestorText).toBeDefined();
973
- });
974
- });
975
-
976
- describe('getNewScrollPositionForZoomLevel', () => {
977
- // Uses global devicePixelRatio which is 1 in jsdom env
978
-
979
- it('returns 0 when both scrollPosition and eventOffset are 0', () => {
980
- expect(EpiTreeUtil.getNewScrollPositionForZoomLevel({
981
- eventOffset: 0,
982
- scrollPosition: 0,
983
- dimensionSize: 1000,
984
- currentZoomLevel: 1,
985
- newZoomLevel: 2,
986
- })).toBe(0);
987
- });
988
-
989
- it('returns a negative adjustment when zooming in with cursor to the right of origin', () => {
990
- // devicePixelRatio=1; fullSizePos=(100+0)*1=100; curr=100/1=100; new=100/2=50; result=0-(100-50)=-50
991
- expect(EpiTreeUtil.getNewScrollPositionForZoomLevel({
992
- eventOffset: 100,
993
- scrollPosition: 0,
994
- dimensionSize: 1000,
995
- currentZoomLevel: 1,
996
- newZoomLevel: 2,
997
- })).toBe(-50);
998
- });
999
-
1000
- it('returns 0 when scrollPosition offsets the eventOffset exactly after zoom', () => {
1001
- // fullSizePos=(100+100)*1=200; curr=200/1=200; new=200/2=100; result=100-(200-100)=0
1002
- expect(EpiTreeUtil.getNewScrollPositionForZoomLevel({
1003
- eventOffset: 100,
1004
- scrollPosition: 100,
1005
- dimensionSize: 1000,
1006
- currentZoomLevel: 1,
1007
- newZoomLevel: 2,
1008
- })).toBe(0);
1009
- });
1010
-
1011
- it('halves the scroll when zooming from 1 to 2 with cursor at the left edge', () => {
1012
- // fullSizePos=(0+200)*1=200; curr=200; new=100; result=200-(200-100)=100
1013
- expect(EpiTreeUtil.getNewScrollPositionForZoomLevel({
1014
- eventOffset: 0,
1015
- scrollPosition: 200,
1016
- dimensionSize: 1000,
1017
- currentZoomLevel: 1,
1018
- newZoomLevel: 2,
1019
- })).toBe(100);
1020
- });
1021
-
1022
- it('increases scroll when zooming out from 2 to 1', () => {
1023
- // fullSizePos=(0+200)*2=400; curr=400/2=200; new=400/1=400; result=200-(200-400)=400
1024
- expect(EpiTreeUtil.getNewScrollPositionForZoomLevel({
1025
- eventOffset: 0,
1026
- scrollPosition: 200,
1027
- dimensionSize: 1000,
1028
- currentZoomLevel: 2,
1029
- newZoomLevel: 1,
1030
- })).toBe(400);
1031
- });
1032
- });
1033
-
1034
- describe('getSanitizedScrollPosition', () => {
1035
- const TREE_PADDING = 10;
1036
- const HEADER_HEIGHT = 40;
1037
-
1038
- beforeAll(() => {
1039
- vi.spyOn(ConfigManager.instance, 'config', 'get').mockReturnValue({
1040
- epiTree: {
1041
- TREE_PADDING,
1042
- HEADER_HEIGHT,
1043
- },
1044
- } as Config);
1045
- });
1046
-
1047
- afterAll(() => {
1048
- vi.restoreAllMocks();
1049
- });
1050
-
1051
- it('clamps positionY to 0 when linked at zoom=1 and treeHeight < treeCanvasHeight', () => {
1052
- const result = EpiTreeUtil.getSanitizedScrollPosition({
1053
- positionX: 0,
1054
- positionY: 999,
1055
- treeCanvasWidth: 400,
1056
- treeCanvasHeight: 300,
1057
- treeHeight: 200,
1058
- devicePixelRatio: 1,
1059
- internalZoomLevel: 1,
1060
- isLinked: true,
1061
- });
1062
- expect(result.newPositionY).toBe(0);
1063
- });
1064
-
1065
- it('clamps positionY to [0, treeHeight - treeCanvasHeight] when linked at zoom=1', () => {
1066
- // treeHeight=600, treeCanvasHeight=300, devicePixelRatio=1 → max=300
1067
- const clamped = EpiTreeUtil.getSanitizedScrollPosition({
1068
- positionX: 0,
1069
- positionY: 999,
1070
- treeCanvasWidth: 400,
1071
- treeCanvasHeight: 300,
1072
- treeHeight: 600,
1073
- devicePixelRatio: 1,
1074
- internalZoomLevel: 1,
1075
- isLinked: true,
1076
- });
1077
- expect(clamped.newPositionY).toBe(300);
1078
-
1079
- const atMin = EpiTreeUtil.getSanitizedScrollPosition({
1080
- positionX: 0,
1081
- positionY: -50,
1082
- treeCanvasWidth: 400,
1083
- treeCanvasHeight: 300,
1084
- treeHeight: 600,
1085
- devicePixelRatio: 1,
1086
- internalZoomLevel: 1,
1087
- isLinked: true,
1088
- });
1089
- expect(atMin.newPositionY).toBe(0);
1090
- });
1091
-
1092
- it('passes positionY through unchanged when within linked bounds', () => {
1093
- const result = EpiTreeUtil.getSanitizedScrollPosition({
1094
- positionX: 0,
1095
- positionY: 150,
1096
- treeCanvasWidth: 400,
1097
- treeCanvasHeight: 300,
1098
- treeHeight: 600,
1099
- devicePixelRatio: 1,
1100
- internalZoomLevel: 1,
1101
- isLinked: true,
1102
- });
1103
- expect(result.newPositionY).toBe(150);
1104
- });
1105
-
1106
- it('applies wider Y bounds when not linked (allows negative Y)', () => {
1107
- // positionYMin = -300 + 10 + 40 = -250; positionYMax = 600 - 10 - 40 = 550
1108
- const clampedMax = EpiTreeUtil.getSanitizedScrollPosition({
1109
- positionX: 0,
1110
- positionY: 1000,
1111
- treeCanvasWidth: 400,
1112
- treeCanvasHeight: 300,
1113
- treeHeight: 600,
1114
- devicePixelRatio: 1,
1115
- internalZoomLevel: 1,
1116
- isLinked: false,
1117
- });
1118
- expect(clampedMax.newPositionY).toBe(550);
1119
-
1120
- const clampedMin = EpiTreeUtil.getSanitizedScrollPosition({
1121
- positionX: 0,
1122
- positionY: -1000,
1123
- treeCanvasWidth: 400,
1124
- treeCanvasHeight: 300,
1125
- treeHeight: 600,
1126
- devicePixelRatio: 1,
1127
- internalZoomLevel: 1,
1128
- isLinked: false,
1129
- });
1130
- expect(clampedMin.newPositionY).toBe(-250);
1131
- });
1132
-
1133
- it('clamps positionX to [positionXMin, positionXMax]', () => {
1134
- // positionXMin = -400 + 2*10 = -380; positionXMax = 400 - 2*10 = 380
1135
- const maxClamped = EpiTreeUtil.getSanitizedScrollPosition({
1136
- positionX: 999,
1137
- positionY: 0,
1138
- treeCanvasWidth: 400,
1139
- treeCanvasHeight: 300,
1140
- treeHeight: 600,
1141
- devicePixelRatio: 1,
1142
- internalZoomLevel: 1,
1143
- isLinked: false,
1144
- });
1145
- expect(maxClamped.newPositionX).toBe(380);
1146
-
1147
- const minClamped = EpiTreeUtil.getSanitizedScrollPosition({
1148
- positionX: -999,
1149
- positionY: 0,
1150
- treeCanvasWidth: 400,
1151
- treeCanvasHeight: 300,
1152
- treeHeight: 600,
1153
- devicePixelRatio: 1,
1154
- internalZoomLevel: 1,
1155
- isLinked: false,
1156
- });
1157
- expect(minClamped.newPositionX).toBe(-380);
1158
- });
1159
-
1160
- it('passes positionX through unchanged when within bounds', () => {
1161
- const result = EpiTreeUtil.getSanitizedScrollPosition({
1162
- positionX: 100,
1163
- positionY: 0,
1164
- treeCanvasWidth: 400,
1165
- treeCanvasHeight: 300,
1166
- treeHeight: 600,
1167
- devicePixelRatio: 1,
1168
- internalZoomLevel: 1,
1169
- isLinked: false,
1170
- });
1171
- expect(result.newPositionX).toBe(100);
1172
- });
1173
-
1174
- it('uses divePixelRatioOffset=0 via the ?? fallback when devicePixelRatio exceeds all thresholds', () => {
1175
- // With dpr=4 > 3 (max threshold), thresholds.find returns undefined → ?? 0 fires
1176
- // positionYMax = (600*4) - (300*4) - 0 = 1200; positionY=999 < 1200 → no clamping
1177
- const result = EpiTreeUtil.getSanitizedScrollPosition({
1178
- positionX: 0,
1179
- positionY: 999,
1180
- treeCanvasWidth: 400,
1181
- treeCanvasHeight: 300,
1182
- treeHeight: 600,
1183
- devicePixelRatio: 4,
1184
- internalZoomLevel: 1,
1185
- isLinked: true,
1186
- });
1187
- expect(result.newPositionY).toBe(999);
1188
- });
1189
- });
1190
-
1191
- describe('drawDivider', () => {
1192
- const makeCtx = () => ({
1193
- scale: vi.fn(),
1194
- translate: vi.fn(),
1195
- beginPath: vi.fn(),
1196
- moveTo: vi.fn(),
1197
- lineTo: vi.fn(),
1198
- stroke: vi.fn(),
1199
- closePath: vi.fn(),
1200
- fillRect: vi.fn(),
1201
- fillText: vi.fn(),
1202
- setLineDash: vi.fn(),
1203
- strokeStyle: '',
1204
- fillStyle: '',
1205
- lineWidth: 0,
1206
- });
1207
-
1208
- it('draws a horizontal line at the specified y position', () => {
1209
- const ctx = makeCtx();
1210
- const canvas = {
1211
- getContext: vi.fn().mockReturnValue(ctx),
1212
- width: 600,
1213
- height: 400,
1214
- } as unknown as HTMLCanvasElement;
1215
-
1216
- EpiTreeUtil.drawDivider({ canvas, y: 20, devicePixelRatio: 1 });
1217
-
1218
- expect(ctx.moveTo).toHaveBeenCalledWith(0, 20);
1219
- expect(ctx.lineTo).toHaveBeenCalledWith(600, 20);
1220
- expect(ctx.stroke).toHaveBeenCalled();
1221
- });
1222
-
1223
- it('calls stroke with lineWidth 1', () => {
1224
- const ctx = makeCtx();
1225
- const canvas = {
1226
- getContext: vi.fn().mockReturnValue(ctx),
1227
- width: 600,
1228
- height: 400,
1229
- } as unknown as HTMLCanvasElement;
1230
-
1231
- EpiTreeUtil.drawDivider({ canvas, y: 5, devicePixelRatio: 1 });
1232
-
1233
- expect(ctx.stroke).toHaveBeenCalledTimes(1);
1234
- expect(ctx.beginPath).toHaveBeenCalledTimes(1);
1235
- });
1236
- });
1237
-
1238
- describe('drawGuides', () => {
1239
- const TREE_PADDING_GUIDES = 10;
1240
- const REGULAR_FILL_COLOR = '#dddddd';
1241
-
1242
- beforeAll(() => {
1243
- vi.spyOn(ConfigManager.instance, 'config', 'get').mockReturnValue({
1244
- epiTree: {
1245
- TREE_PADDING: TREE_PADDING_GUIDES,
1246
- REGULAR_FILL_COLOR_SUPPORT_LINE: REGULAR_FILL_COLOR,
1247
- },
1248
- } as Config);
1249
- });
1250
-
1251
- afterAll(() => {
1252
- vi.restoreAllMocks();
1253
- });
1254
-
1255
- const makeGuidesCtx = () => ({
1256
- scale: vi.fn(),
1257
- translate: vi.fn(),
1258
- beginPath: vi.fn(),
1259
- moveTo: vi.fn(),
1260
- lineTo: vi.fn(),
1261
- stroke: vi.fn(),
1262
- closePath: vi.fn(),
1263
- setLineDash: vi.fn(),
1264
- strokeStyle: '',
1265
- });
1266
-
1267
- it('calls setLineDash([3,1]) before drawing and resets to [0,0] after', () => {
1268
- const ctx = makeGuidesCtx();
1269
- const canvas = {
1270
- getContext: vi.fn().mockReturnValue(ctx),
1271
- width: 800,
1272
- height: 300,
1273
- } as unknown as HTMLCanvasElement;
1274
-
1275
- EpiTreeUtil.drawGuides({
1276
- canvas,
1277
- tickerMarkScale: [0, 0, 0],
1278
- geneticTreeWidth: new Decimal(0),
1279
- pixelToGeneticDistanceRatio: 100,
1280
- devicePixelRatio: 1,
1281
- zoomLevel: 1,
1282
- horizontalScrollPosition: 0,
1283
- });
1284
-
1285
- expect(ctx.setLineDash).toHaveBeenCalledWith([3, 1]);
1286
- expect(ctx.setLineDash).toHaveBeenCalledWith([0, 0]);
1287
- });
1288
-
1289
- it('strokes one vertical line per tick mark', () => {
1290
- const ctx = makeGuidesCtx();
1291
- const canvas = {
1292
- getContext: vi.fn().mockReturnValue(ctx),
1293
- width: 800,
1294
- height: 300,
1295
- } as unknown as HTMLCanvasElement;
1296
-
1297
- // tickerMarkScale=[3,...] → 3 iterations → 3 beginPath/moveTo/lineTo/stroke/closePath calls
1298
- EpiTreeUtil.drawGuides({
1299
- canvas,
1300
- tickerMarkScale: [3, 5, 1],
1301
- geneticTreeWidth: new Decimal(10),
1302
- pixelToGeneticDistanceRatio: 100,
1303
- devicePixelRatio: 1,
1304
- zoomLevel: 1,
1305
- horizontalScrollPosition: 0,
1306
- });
1307
-
1308
- expect(ctx.beginPath).toHaveBeenCalledTimes(3);
1309
- expect(ctx.stroke).toHaveBeenCalledTimes(3);
1310
- expect(ctx.closePath).toHaveBeenCalledTimes(3);
1311
- });
1312
-
1313
- it('positions the first guide line at TREE_PADDING pixels from the left', () => {
1314
- const ctx = makeGuidesCtx();
1315
- const canvas = {
1316
- getContext: vi.fn().mockReturnValue(ctx),
1317
- width: 800,
1318
- height: 300,
1319
- } as unknown as HTMLCanvasElement;
1320
-
1321
- // tickerMarkScale=[3,5,1], genetic=10, ratio=100, zoom=1, scroll=0
1322
- // offset = totalTicker - geneticPx = (500*2) - 1000 = 0
1323
- // i=0: x = 0*500 + (10/1) - 0 - 0 = 10
1324
- EpiTreeUtil.drawGuides({
1325
- canvas,
1326
- tickerMarkScale: [3, 5, 1],
1327
- geneticTreeWidth: new Decimal(10),
1328
- pixelToGeneticDistanceRatio: 100,
1329
- devicePixelRatio: 1,
1330
- zoomLevel: 1,
1331
- horizontalScrollPosition: 0,
1332
- });
1333
-
1334
- expect(ctx.moveTo).toHaveBeenNthCalledWith(1, 10, 0);
1335
- expect(ctx.lineTo).toHaveBeenNthCalledWith(1, 10, 300);
1336
- });
1337
- });
1338
-
1339
- describe('drawScale', () => {
1340
- const TREE_PADDING_SCALE = 10;
1341
- const HEADER_HEIGHT_SCALE = 40;
1342
-
1343
- beforeAll(() => {
1344
- vi.spyOn(ConfigManager.instance, 'config', 'get').mockReturnValue({
1345
- epiTree: {
1346
- TREE_PADDING: TREE_PADDING_SCALE,
1347
- HEADER_HEIGHT: HEADER_HEIGHT_SCALE,
1348
- },
1349
- } as Config);
1350
- });
1351
-
1352
- afterAll(() => {
1353
- vi.restoreAllMocks();
1354
- });
1355
-
1356
- const makeScaleCtx = () => ({
1357
- scale: vi.fn(),
1358
- translate: vi.fn(),
1359
- beginPath: vi.fn(),
1360
- closePath: vi.fn(),
1361
- fillText: vi.fn(),
1362
- textAlign: 'left',
1363
- font: '',
1364
- });
1365
-
1366
- const makeScaleTheme = () => ({
1367
- typography: { fontFamily: 'Arial' },
1368
- } as unknown as Theme);
1369
-
1370
- it('calls fillText once per tick mark', () => {
1371
- const ctx = makeScaleCtx();
1372
- const canvas = {
1373
- getContext: vi.fn().mockReturnValue(ctx),
1374
- width: 800,
1375
- height: 60,
1376
- } as unknown as HTMLCanvasElement;
1377
-
1378
- // tickerMarkScale=[3,5,1] → 3 fillText calls
1379
- EpiTreeUtil.drawScale({
1380
- canvas,
1381
- theme: makeScaleTheme(),
1382
- tickerMarkScale: [3, 5, 1],
1383
- geneticTreeWidth: new Decimal(10),
1384
- pixelToGeneticDistanceRatio: 100,
1385
- devicePixelRatio: 1,
1386
- zoomLevel: 1,
1387
- horizontalScrollPosition: 0,
1388
- });
1389
-
1390
- expect(ctx.fillText).toHaveBeenCalledTimes(3);
1391
- });
1392
-
1393
- it('renders labels in descending order (largest distance at leftmost tick)', () => {
1394
- const ctx = makeScaleCtx();
1395
- const canvas = {
1396
- getContext: vi.fn().mockReturnValue(ctx),
1397
- width: 800,
1398
- height: 60,
1399
- } as unknown as HTMLCanvasElement;
1400
-
1401
- // tickerMarkScale=[3,5,1], labels: i=0→10, i=1→5, i=2→0
1402
- // x positions: i=0→10, i=1→510, i=2→1010
1403
- // y = HEADER_HEIGHT * 0.61 = 40 * 0.61 = 24.4
1404
- EpiTreeUtil.drawScale({
1405
- canvas,
1406
- theme: makeScaleTheme(),
1407
- tickerMarkScale: [3, 5, 1],
1408
- geneticTreeWidth: new Decimal(10),
1409
- pixelToGeneticDistanceRatio: 100,
1410
- devicePixelRatio: 1,
1411
- zoomLevel: 1,
1412
- horizontalScrollPosition: 0,
1413
- });
1414
-
1415
- expect(ctx.fillText).toHaveBeenNthCalledWith(1, '10', 10, 24.4);
1416
- expect(ctx.fillText).toHaveBeenNthCalledWith(2, '5', 510, 24.4);
1417
- expect(ctx.fillText).toHaveBeenNthCalledWith(3, '0', 1010, 24.4);
1418
- });
1419
- });
1420
-
1421
- describe('drawTreeCanvas', () => {
1422
- const TREE_PADDING_CANVAS = 10;
1423
- const REGULAR_FILL_CANVAS = '#eeeeee';
1424
-
1425
- beforeAll(() => {
1426
- vi.spyOn(ConfigManager.instance, 'config', 'get').mockReturnValue({
1427
- epiTree: {
1428
- TREE_PADDING: TREE_PADDING_CANVAS,
1429
- REGULAR_FILL_COLOR_SUPPORT_LINE: REGULAR_FILL_CANVAS,
1430
- LEAF_DOT_RADIUS: 4,
1431
- ANCESTOR_DOT_RADIUS: 3,
1432
- MINIMUM_DISTANCE_PERCENTAGE_TO_SHOW_LABEL: 5,
1433
- },
1434
- epiLineList: {
1435
- TABLE_ROW_HEIGHT: 30,
1436
- },
1437
- } as Config);
1438
- });
1439
-
1440
- afterAll(() => {
1441
- vi.restoreAllMocks();
1442
- });
1443
-
1444
- const makeTreeCanvasCtx = () => ({
1445
- reset: vi.fn(),
1446
- scale: vi.fn(),
1447
- translate: vi.fn(),
1448
- setTransform: vi.fn(),
1449
- beginPath: vi.fn(),
1450
- moveTo: vi.fn(),
1451
- lineTo: vi.fn(),
1452
- stroke: vi.fn(),
1453
- fill: vi.fn(),
1454
- closePath: vi.fn(),
1455
- fillRect: vi.fn(),
1456
- fillText: vi.fn(),
1457
- setLineDash: vi.fn(),
1458
- strokeStyle: '',
1459
- fillStyle: '',
1460
- lineWidth: 0,
1461
- font: '',
1462
- textAlign: 'left',
1463
- imageSmoothingEnabled: false,
1464
- imageSmoothingQuality: 'low' as ImageSmoothingQuality,
1465
- });
1466
-
1467
- const makeTreeCanvasTheme = () => ({
1468
- 'gen-epix': {
1469
- tree: {
1470
- font: '12px mono',
1471
- color: '#000000',
1472
- dimFn: vi.fn((_c: string) => '#aaaaaa'),
1473
- },
1474
- },
1475
- palette: { background: { paper: '#ffffff' } },
1476
- typography: { fontFamily: 'Arial' },
1477
- } as unknown as Theme);
1478
-
1479
- const makeEmptyAssembly = (): TreeAssembly => ({
1480
- verticalAncestorTreeLines: [],
1481
- horizontalAncestorTreeLines: [],
1482
- ancestorNodes: [],
1483
- leafNodes: [],
1484
- leafTreeLines: [],
1485
- supportLines: [],
1486
- distanceTexts: [],
1487
- nodePathPropertiesMap: new Map(),
1488
- horizontalLinePathPropertiesMap: new Map(),
1489
- verticalLinePathPropertiesMap: new Map(),
1490
- });
1491
-
1492
- it('calls ctx.reset() before drawing', () => {
1493
- const ctx = makeTreeCanvasCtx();
1494
- const canvas = {
1495
- getContext: vi.fn().mockReturnValue(ctx),
1496
- clientWidth: 200,
1497
- clientHeight: 100,
1498
- width: 0,
1499
- height: 0,
1500
- } as unknown as HTMLCanvasElement;
1501
-
1502
- EpiTreeUtil.drawTreeCanvas({
1503
- canvas,
1504
- theme: makeTreeCanvasTheme(),
1505
- treeAssembly: makeEmptyAssembly(),
1506
- stratification: null,
1507
- zoomLevel: 1,
1508
- isLinked: false,
1509
- highlightedNodeNames: [],
1510
- verticalScrollPosition: 0,
1511
- horizontalScrollPosition: 0,
1512
- treeCanvasWidth: 200,
1513
- treeCanvasHeight: 100,
1514
- pixelToGeneticDistanceRatio: 100,
1515
- tickerMarkScale: [0, 0, 0],
1516
- shouldShowDistances: false,
1517
- devicePixelRatio: 1,
1518
- geneticTreeWidth: new Decimal(0),
1519
- });
1520
-
1521
- expect(ctx.reset).toHaveBeenCalled();
1522
- });
1523
-
1524
- it('sets canvas dimensions from clientWidth/clientHeight × devicePixelRatio', () => {
1525
- const ctx = makeTreeCanvasCtx();
1526
- const canvas = {
1527
- getContext: vi.fn().mockReturnValue(ctx),
1528
- clientWidth: 200,
1529
- clientHeight: 100,
1530
- width: 0,
1531
- height: 0,
1532
- } as unknown as HTMLCanvasElement;
1533
-
1534
- EpiTreeUtil.drawTreeCanvas({
1535
- canvas,
1536
- theme: makeTreeCanvasTheme(),
1537
- treeAssembly: makeEmptyAssembly(),
1538
- stratification: null,
1539
- zoomLevel: 1,
1540
- isLinked: false,
1541
- highlightedNodeNames: [],
1542
- verticalScrollPosition: 0,
1543
- horizontalScrollPosition: 0,
1544
- treeCanvasWidth: 200,
1545
- treeCanvasHeight: 100,
1546
- pixelToGeneticDistanceRatio: 100,
1547
- tickerMarkScale: [0, 0, 0],
1548
- shouldShowDistances: false,
1549
- devicePixelRatio: 2,
1550
- geneticTreeWidth: new Decimal(0),
1551
- });
1552
-
1553
- expect(canvas.width).toBe(400); // 200 * 2
1554
- expect(canvas.height).toBe(200); // 100 * 2
1555
- });
1556
-
1557
- it('sets imageSmoothingEnabled to false when zoomLevel is 1', () => {
1558
- const ctx = makeTreeCanvasCtx();
1559
- const canvas = {
1560
- getContext: vi.fn().mockReturnValue(ctx),
1561
- clientWidth: 200,
1562
- clientHeight: 100,
1563
- width: 0,
1564
- height: 0,
1565
- } as unknown as HTMLCanvasElement;
1566
-
1567
- EpiTreeUtil.drawTreeCanvas({
1568
- canvas,
1569
- theme: makeTreeCanvasTheme(),
1570
- treeAssembly: makeEmptyAssembly(),
1571
- stratification: null,
1572
- zoomLevel: 1,
1573
- isLinked: false,
1574
- highlightedNodeNames: [],
1575
- verticalScrollPosition: 0,
1576
- horizontalScrollPosition: 0,
1577
- treeCanvasWidth: 200,
1578
- treeCanvasHeight: 100,
1579
- pixelToGeneticDistanceRatio: 100,
1580
- tickerMarkScale: [0, 0, 0],
1581
- shouldShowDistances: false,
1582
- devicePixelRatio: 1,
1583
- geneticTreeWidth: new Decimal(0),
1584
- });
1585
-
1586
- expect(ctx.imageSmoothingEnabled).toBe(false);
1587
- });
1588
-
1589
- it('sets imageSmoothingEnabled to true when zoomLevel exceeds 1', () => {
1590
- const ctx = makeTreeCanvasCtx();
1591
- const canvas = {
1592
- getContext: vi.fn().mockReturnValue(ctx),
1593
- clientWidth: 200,
1594
- clientHeight: 100,
1595
- width: 0,
1596
- height: 0,
1597
- } as unknown as HTMLCanvasElement;
1598
-
1599
- EpiTreeUtil.drawTreeCanvas({
1600
- canvas,
1601
- theme: makeTreeCanvasTheme(),
1602
- treeAssembly: makeEmptyAssembly(),
1603
- stratification: null,
1604
- zoomLevel: 2,
1605
- isLinked: false,
1606
- highlightedNodeNames: [],
1607
- verticalScrollPosition: 0,
1608
- horizontalScrollPosition: 0,
1609
- treeCanvasWidth: 200,
1610
- treeCanvasHeight: 100,
1611
- pixelToGeneticDistanceRatio: 100,
1612
- tickerMarkScale: [0, 0, 0],
1613
- shouldShowDistances: false,
1614
- devicePixelRatio: 1,
1615
- geneticTreeWidth: new Decimal(0),
1616
- });
1617
-
1618
- expect(ctx.imageSmoothingEnabled).toBe(true);
1619
- });
1620
- });
1621
-
1622
- describe('getPathPropertiesFromCanvas', () => {
1623
- const makeHitTestCtx = (
1624
- nodeShapeToHit: Path2D | null,
1625
- hLineShapeToHit: Path2D | null,
1626
- vLineShapeToHit: Path2D | null,
1627
- ) => ({
1628
- isPointInPath: vi.fn(
1629
- (path: Path2D, _x: number, _y: number) => path === nodeShapeToHit,
1630
- ),
1631
- isPointInStroke: vi.fn(
1632
- (path: Path2D, _x: number, _y: number) => path === hLineShapeToHit || path === vLineShapeToHit,
1633
- ),
1634
- });
1635
-
1636
- const makeMouseEvent = (clientX: number, clientY: number) => ({
1637
- clientX,
1638
- clientY,
1639
- target: { getBoundingClientRect: () => ({ left: 0, top: 0 }) },
1640
- } as unknown as MouseEvent);
1641
-
1642
- it('returns node properties when isPointInPath matches a node dot', () => {
1643
- const nodeShape = new Path2D();
1644
- const nodeProps: TreePathProperties = { subTreeLeaveNames: ['a'], treeNode: makeLeaf('a') };
1645
- const assembly: TreeAssembly = {
1646
- verticalAncestorTreeLines: [],
1647
- horizontalAncestorTreeLines: [],
1648
- ancestorNodes: [],
1649
- leafNodes: [],
1650
- leafTreeLines: [],
1651
- supportLines: [],
1652
- distanceTexts: [],
1653
- nodePathPropertiesMap: new Map([[nodeShape, nodeProps]]),
1654
- horizontalLinePathPropertiesMap: new Map(),
1655
- verticalLinePathPropertiesMap: new Map(),
1656
- };
1657
- const ctx = makeHitTestCtx(nodeShape, null, null);
1658
- const canvas = {
1659
- getContext: vi.fn().mockReturnValue(ctx),
1660
- } as unknown as HTMLCanvasElement;
1661
-
1662
- const result = EpiTreeUtil.getPathPropertiesFromCanvas({
1663
- canvas,
1664
- event: makeMouseEvent(150, 200),
1665
- treeAssembly: assembly,
1666
- devicePixelRatio: 1,
1667
- });
1668
-
1669
- expect(result).toBe(nodeProps);
1670
- });
1671
-
1672
- it('returns horizontal line properties when isPointInStroke matches (with y tolerance)', () => {
1673
- const hShape = new Path2D();
1674
- const hProps: TreePathProperties = { subTreeLeaveNames: ['a', 'b'] };
1675
- const assembly: TreeAssembly = {
1676
- verticalAncestorTreeLines: [],
1677
- horizontalAncestorTreeLines: [],
1678
- ancestorNodes: [],
1679
- leafNodes: [],
1680
- leafTreeLines: [],
1681
- supportLines: [],
1682
- distanceTexts: [],
1683
- nodePathPropertiesMap: new Map(),
1684
- horizontalLinePathPropertiesMap: new Map([[hShape, hProps]]),
1685
- verticalLinePathPropertiesMap: new Map(),
1686
- };
1687
- const ctx = makeHitTestCtx(null, hShape, null);
1688
- const canvas = {
1689
- getContext: vi.fn().mockReturnValue(ctx),
1690
- } as unknown as HTMLCanvasElement;
1691
-
1692
- const result = EpiTreeUtil.getPathPropertiesFromCanvas({
1693
- canvas,
1694
- event: makeMouseEvent(150, 200),
1695
- treeAssembly: assembly,
1696
- devicePixelRatio: 1,
1697
- });
1698
-
1699
- expect(result).toBe(hProps);
1700
- });
1701
-
1702
- it('returns vertical line properties as last resort via x tolerance', () => {
1703
- const vShape = new Path2D();
1704
- const vProps: TreePathProperties = { subTreeLeaveNames: ['a', 'b'] };
1705
- const assembly: TreeAssembly = {
1706
- verticalAncestorTreeLines: [],
1707
- horizontalAncestorTreeLines: [],
1708
- ancestorNodes: [],
1709
- leafNodes: [],
1710
- leafTreeLines: [],
1711
- supportLines: [],
1712
- distanceTexts: [],
1713
- nodePathPropertiesMap: new Map(),
1714
- horizontalLinePathPropertiesMap: new Map(),
1715
- verticalLinePathPropertiesMap: new Map([[vShape, vProps]]),
1716
- };
1717
- // isPointInStroke returns true only for vShape
1718
- const ctx = makeHitTestCtx(null, null, vShape);
1719
- const canvas = {
1720
- getContext: vi.fn().mockReturnValue(ctx),
1721
- } as unknown as HTMLCanvasElement;
1722
-
1723
- const result = EpiTreeUtil.getPathPropertiesFromCanvas({
1724
- canvas,
1725
- event: makeMouseEvent(150, 200),
1726
- treeAssembly: assembly,
1727
- devicePixelRatio: 1,
1728
- });
1729
-
1730
- expect(result).toBe(vProps);
1731
- });
1732
-
1733
- it('returns undefined when no path matches the cursor position', () => {
1734
- const assembly: TreeAssembly = {
1735
- verticalAncestorTreeLines: [],
1736
- horizontalAncestorTreeLines: [],
1737
- ancestorNodes: [],
1738
- leafNodes: [],
1739
- leafTreeLines: [],
1740
- supportLines: [],
1741
- distanceTexts: [],
1742
- nodePathPropertiesMap: new Map([[new Path2D(), { subTreeLeaveNames: [] }]]),
1743
- horizontalLinePathPropertiesMap: new Map(),
1744
- verticalLinePathPropertiesMap: new Map(),
1745
- };
1746
- const ctx = makeHitTestCtx(null, null, null);
1747
- const canvas = {
1748
- getContext: vi.fn().mockReturnValue(ctx),
1749
- } as unknown as HTMLCanvasElement;
1750
-
1751
- const result = EpiTreeUtil.getPathPropertiesFromCanvas({
1752
- canvas,
1753
- event: makeMouseEvent(150, 200),
1754
- treeAssembly: assembly,
1755
- devicePixelRatio: 1,
1756
- });
1757
-
1758
- expect(result).toBeUndefined();
1759
- });
1760
-
1761
- it('returns undefined when vertical paths exist but isPointInStroke returns false for all', () => {
1762
- const vShape = new Path2D();
1763
- const assembly: TreeAssembly = {
1764
- verticalAncestorTreeLines: [],
1765
- horizontalAncestorTreeLines: [],
1766
- ancestorNodes: [],
1767
- leafNodes: [],
1768
- leafTreeLines: [],
1769
- supportLines: [],
1770
- distanceTexts: [],
1771
- nodePathPropertiesMap: new Map(),
1772
- horizontalLinePathPropertiesMap: new Map(),
1773
- verticalLinePathPropertiesMap: new Map([[vShape, { subTreeLeaveNames: ['a'] }]]),
1774
- };
1775
- const ctx = makeHitTestCtx(null, null, null);
1776
- const canvas = {
1777
- getContext: vi.fn().mockReturnValue(ctx),
1778
- } as unknown as HTMLCanvasElement;
1779
-
1780
- const result = EpiTreeUtil.getPathPropertiesFromCanvas({
1781
- canvas,
1782
- event: makeMouseEvent(150, 200),
1783
- treeAssembly: assembly,
1784
- devicePixelRatio: 1,
1785
- });
1786
-
1787
- expect(result).toBeUndefined();
1788
- });
1789
-
1790
- it('returns undefined when horizontal paths exist but isPointInStroke returns false for all', () => {
1791
- const hShape = new Path2D();
1792
- const assembly: TreeAssembly = {
1793
- verticalAncestorTreeLines: [],
1794
- horizontalAncestorTreeLines: [],
1795
- ancestorNodes: [],
1796
- leafNodes: [],
1797
- leafTreeLines: [],
1798
- supportLines: [],
1799
- distanceTexts: [],
1800
- nodePathPropertiesMap: new Map(),
1801
- horizontalLinePathPropertiesMap: new Map([[hShape, { subTreeLeaveNames: ['a'] }]]),
1802
- verticalLinePathPropertiesMap: new Map(),
1803
- };
1804
- // none of the shapes match → isPointInStroke always returns false
1805
- const ctx = makeHitTestCtx(null, null, null);
1806
- const canvas = {
1807
- getContext: vi.fn().mockReturnValue(ctx),
1808
- } as unknown as HTMLCanvasElement;
1809
-
1810
- const result = EpiTreeUtil.getPathPropertiesFromCanvas({
1811
- canvas,
1812
- event: makeMouseEvent(150, 200),
1813
- treeAssembly: assembly,
1814
- devicePixelRatio: 1,
1815
- });
1816
-
1817
- expect(result).toBeUndefined();
1818
- });
1819
- });
1820
-
1821
- describe('getTreeConfigurationId', () => {
1822
- it('concatenates col, refCol, protocol and algorithm IDs separated by underscores', () => {
1823
- const config: Omit<TreeConfiguration, 'computedId'> = {
1824
- col: { id: 'col1' } as TreeConfiguration['col'],
1825
- refCol: { id: 'rc1' } as TreeConfiguration['refCol'],
1826
- geneticDistanceProtocol: { id: 'gdp1' } as TreeConfiguration['geneticDistanceProtocol'],
1827
- treeAlgorithm: { id: 'algo1' } as TreeConfiguration['treeAlgorithm'],
1828
- };
1829
- expect(EpiTreeUtil.getTreeConfigurationId(config)).toBe('col1_rc1_gdp1_algo1');
1830
- });
1831
-
1832
- it('handles different ID values correctly', () => {
1833
- const config: Omit<TreeConfiguration, 'computedId'> = {
1834
- col: { id: 'my-col' } as TreeConfiguration['col'],
1835
- refCol: { id: 'my-ref' } as TreeConfiguration['refCol'],
1836
- geneticDistanceProtocol: { id: 'proto-x' } as TreeConfiguration['geneticDistanceProtocol'],
1837
- treeAlgorithm: { id: 'nj-algo' } as TreeConfiguration['treeAlgorithm'],
1838
- };
1839
- expect(EpiTreeUtil.getTreeConfigurationId(config)).toBe('my-col_my-ref_proto-x_nj-algo');
1840
- });
1841
- });
1842
-
1843
- describe('getTreeConfigurationLabel', () => {
1844
- it('returns "protocolName - algorithmName"', () => {
1845
- const config: TreeConfiguration = {
1846
- computedId: 'test',
1847
- col: { id: 'c1' } as TreeConfiguration['col'],
1848
- refCol: { id: 'r1' } as TreeConfiguration['refCol'],
1849
- geneticDistanceProtocol: { id: 'g1', name: 'My Protocol' } as TreeConfiguration['geneticDistanceProtocol'],
1850
- treeAlgorithm: { id: 'a1', name: 'NJ Algorithm' } as TreeConfiguration['treeAlgorithm'],
1851
- };
1852
- expect(EpiTreeUtil.getTreeConfigurationLabel(config)).toBe('My Protocol - NJ Algorithm');
1853
- });
1854
-
1855
- it('works with different protocol and algorithm names', () => {
1856
- const config: TreeConfiguration = {
1857
- computedId: 'x',
1858
- col: { id: 'c1' } as TreeConfiguration['col'],
1859
- refCol: { id: 'r1' } as TreeConfiguration['refCol'],
1860
- geneticDistanceProtocol: { id: 'g1', name: 'Hamming' } as TreeConfiguration['geneticDistanceProtocol'],
1861
- treeAlgorithm: { id: 'a1', name: 'UPGMA' } as TreeConfiguration['treeAlgorithm'],
1862
- };
1863
- expect(EpiTreeUtil.getTreeConfigurationLabel(config)).toBe('Hamming - UPGMA');
1864
- });
1865
- });
1866
-
1867
- describe('getTreeConfigurations', () => {
1868
- let savedTreeAlgorithms: TreeAlgorithm[];
1869
-
1870
- const mockAlgo1: TreeAlgorithm = {
1871
- id: 'algo-nj',
1872
- code: 'NJ' as TreeAlgorithm['code'],
1873
- name: 'Neighbour Joining',
1874
- tree_algorithm_class_id: 'class1',
1875
- seqdb_tree_algorithm_id: 'seqdb1',
1876
- is_ultrametric: false,
1877
- };
1878
-
1879
- const mockAlgo2: TreeAlgorithm = {
1880
- id: 'algo-upgma',
1881
- code: 'UPGMA' as TreeAlgorithm['code'],
1882
- name: 'UPGMA',
1883
- tree_algorithm_class_id: 'class1',
1884
- seqdb_tree_algorithm_id: 'seqdb2',
1885
- is_ultrametric: true,
1886
- };
1887
-
1888
- beforeAll(() => {
1889
- savedTreeAlgorithms = EpiDataManager.instance.data.treeAlgorithms;
1890
- EpiDataManager.instance.data.treeAlgorithms = [mockAlgo1, mockAlgo2];
1891
- });
1892
-
1893
- afterAll(() => {
1894
- EpiDataManager.instance.data.treeAlgorithms = savedTreeAlgorithms;
1895
- });
1896
-
1897
- it('returns empty array when no GENETIC_DISTANCE cols exist', () => {
1898
- const completeCaseType = {
1899
- cols: {},
1900
- ref_cols: {},
1901
- genetic_distance_protocols: {},
1902
- tree_algorithms: {},
1903
- } as unknown as Parameters<typeof EpiTreeUtil.getTreeConfigurations>[0];
1904
-
1905
- expect(EpiTreeUtil.getTreeConfigurations(completeCaseType)).toEqual([]);
1906
- });
1907
-
1908
- it('returns one entry per (col × algorithm) pair', () => {
1909
- const completeCaseType = {
1910
- cols: {
1911
- col1: { id: 'col1', ref_col_id: 'rc1', tree_algorithm_codes: ['NJ', 'UPGMA'] },
1912
- },
1913
- ref_cols: {
1914
- rc1: { id: 'rc1', col_type: ColType.GENETIC_DISTANCE, genetic_distance_protocol_id: 'gdp1' },
1915
- },
1916
- genetic_distance_protocols: {
1917
- gdp1: { id: 'gdp1', name: 'Protocol 1' },
1918
- },
1919
- tree_algorithms: {
1920
- NJ: mockAlgo1,
1921
- UPGMA: mockAlgo2,
1922
- },
1923
- } as unknown as Parameters<typeof EpiTreeUtil.getTreeConfigurations>[0];
1924
-
1925
- const result = EpiTreeUtil.getTreeConfigurations(completeCaseType);
1926
-
1927
- expect(result).toHaveLength(2);
1928
- expect(result[0].col.id).toBe('col1');
1929
- expect(result[0].treeAlgorithm).toBe(mockAlgo1);
1930
- expect(result[1].treeAlgorithm).toBe(mockAlgo2);
1931
- });
1932
-
1933
- it('sets computedId using getTreeConfigurationId', () => {
1934
- const completeCaseType = {
1935
- cols: {
1936
- col1: { id: 'col1', ref_col_id: 'rc1', tree_algorithm_codes: ['NJ'] },
1937
- },
1938
- ref_cols: {
1939
- rc1: { id: 'rc1', col_type: ColType.GENETIC_DISTANCE, genetic_distance_protocol_id: 'gdp1' },
1940
- },
1941
- genetic_distance_protocols: {
1942
- gdp1: { id: 'gdp1', name: 'Protocol 1' },
1943
- },
1944
- tree_algorithms: {
1945
- NJ: mockAlgo1,
1946
- },
1947
- } as unknown as Parameters<typeof EpiTreeUtil.getTreeConfigurations>[0];
1948
-
1949
- const result = EpiTreeUtil.getTreeConfigurations(completeCaseType);
1950
-
1951
- expect(result[0].computedId).toBe('col1_rc1_gdp1_algo-nj');
1952
- });
1953
-
1954
- it('sorts algorithms according to EpiDataManager.instance.data.treeAlgorithms order', () => {
1955
- // EpiDataManager order: [mockAlgo1(NJ), mockAlgo2(UPGMA)]
1956
- // col specifies UPGMA first, then NJ → result should be sorted to NJ then UPGMA
1957
- const completeCaseType = {
1958
- cols: {
1959
- col1: { id: 'col1', ref_col_id: 'rc1', tree_algorithm_codes: ['UPGMA', 'NJ'] },
1960
- },
1961
- ref_cols: {
1962
- rc1: { id: 'rc1', col_type: ColType.GENETIC_DISTANCE, genetic_distance_protocol_id: 'gdp1' },
1963
- },
1964
- genetic_distance_protocols: {
1965
- gdp1: { id: 'gdp1', name: 'Protocol 1' },
1966
- },
1967
- tree_algorithms: {
1968
- NJ: mockAlgo1,
1969
- UPGMA: mockAlgo2,
1970
- },
1971
- } as unknown as Parameters<typeof EpiTreeUtil.getTreeConfigurations>[0];
1972
-
1973
- const result = EpiTreeUtil.getTreeConfigurations(completeCaseType);
1974
-
1975
- expect(result[0].treeAlgorithm.code).toBe('NJ');
1976
- expect(result[1].treeAlgorithm.code).toBe('UPGMA');
1977
- });
1978
- });
1979
-
1980
- describe('drawTree', () => {
1981
- const TREE_COLOR = '#123456';
1982
- const DIM_COLOR = '#aabbcc';
1983
-
1984
- type CtxDrawCall = { type: 'stroke' | 'fill'; color: string };
1985
-
1986
- const makeTrackedCtx = () => {
1987
- let currentStrokeStyle = '';
1988
- let currentFillStyle = '';
1989
- const drawCalls: CtxDrawCall[] = [];
1990
-
1991
- const mockCtx = {
1992
- setTransform: vi.fn(),
1993
- textAlign: 'left',
1994
- font: '',
1995
- lineWidth: 0,
1996
- stroke: vi.fn(() => {
1997
- drawCalls.push({ type: 'stroke', color: currentStrokeStyle });
1998
- }),
1999
- fill: vi.fn(() => {
2000
- drawCalls.push({ type: 'fill', color: currentFillStyle });
2001
- }),
2002
- fillText: vi.fn(),
2003
- moveTo: vi.fn(),
2004
- lineTo: vi.fn(),
2005
- beginPath: vi.fn(),
2006
- setLineDash: vi.fn(),
2007
- };
2008
-
2009
- Object.defineProperty(mockCtx, 'strokeStyle', {
2010
- get: () => currentStrokeStyle,
2011
- set: (v: string) => {
2012
- currentStrokeStyle = v;
2013
- },
2014
- configurable: true,
2015
- enumerable: true,
2016
- });
2017
-
2018
- Object.defineProperty(mockCtx, 'fillStyle', {
2019
- get: () => currentFillStyle,
2020
- set: (v: string) => {
2021
- currentFillStyle = v;
2022
- },
2023
- configurable: true,
2024
- enumerable: true,
2025
- });
2026
-
2027
- return { ctx: mockCtx, drawCalls };
2028
- };
2029
-
2030
- const makeDimFn = () => vi.fn((_c: string) => DIM_COLOR);
2031
-
2032
- const makeTheme = (dimFn: (c: string) => string) => ({
2033
- 'gen-epix': {
2034
- tree: {
2035
- font: '12px monospace',
2036
- color: TREE_COLOR,
2037
- dimFn,
2038
- },
2039
- },
2040
- } as unknown as Theme);
2041
-
2042
- const makeCanvas = (ctx: ReturnType<typeof makeTrackedCtx>['ctx']): HTMLCanvasElement =>
2043
- ({ getContext: vi.fn().mockReturnValue(ctx) } as unknown as HTMLCanvasElement);
2044
-
2045
- const makeAssembly = (): TreeAssembly => ({
2046
- verticalAncestorTreeLines: [],
2047
- horizontalAncestorTreeLines: [],
2048
- ancestorNodes: [],
2049
- leafNodes: [],
2050
- leafTreeLines: [],
2051
- supportLines: [],
2052
- distanceTexts: [],
2053
- nodePathPropertiesMap: new Map(),
2054
- horizontalLinePathPropertiesMap: new Map(),
2055
- verticalLinePathPropertiesMap: new Map(),
2056
- });
2057
-
2058
- const p2d = () => new Path2D();
2059
-
2060
- it('calls setTransform with correct scale and offset parameters', () => {
2061
- const { ctx } = makeTrackedCtx();
2062
- EpiTreeUtil.drawTree({
2063
- canvas: makeCanvas(ctx),
2064
- theme: makeTheme(makeDimFn()),
2065
- treeAssembly: makeAssembly(),
2066
- stratification: null,
2067
- zoomLevel: 2,
2068
- highlightedNodeNames: [],
2069
- verticalScrollPosition: 100,
2070
- horizontalScrollPosition: 50,
2071
- shouldShowDistances: false,
2072
- devicePixelRatio: 2,
2073
- isLinked: false,
2074
- });
2075
- // (1/zoomLevel)*devicePixelRatio = (1/2)*2 = 1
2076
- // -horizontalScrollPosition + 0.5 = -50 + 0.5 = -49.5
2077
- // -verticalScrollPosition + 0.5 = -100 + 0.5 = -99.5
2078
- expect(ctx.setTransform).toHaveBeenCalledWith(1, 0, 0, 1, -49.5, -99.5);
2079
- });
2080
-
2081
- it('sets initial canvas context properties after drawing', () => {
2082
- const { ctx } = makeTrackedCtx();
2083
- EpiTreeUtil.drawTree({
2084
- canvas: makeCanvas(ctx),
2085
- theme: makeTheme(makeDimFn()),
2086
- treeAssembly: makeAssembly(),
2087
- stratification: null,
2088
- zoomLevel: 1,
2089
- highlightedNodeNames: [],
2090
- verticalScrollPosition: 0,
2091
- horizontalScrollPosition: 0,
2092
- shouldShowDistances: false,
2093
- devicePixelRatio: 1,
2094
- isLinked: false,
2095
- });
2096
- expect(ctx.font).toBe('12px monospace');
2097
- expect(ctx.lineWidth).toBe(1);
2098
- expect(ctx.textAlign).toBe('center');
2099
- });
2100
-
2101
- it('strokes every vertical ancestor tree line shape', () => {
2102
- const { ctx } = makeTrackedCtx();
2103
- const shape1 = p2d();
2104
- const shape2 = p2d();
2105
- const assembly = makeAssembly();
2106
- assembly.verticalAncestorTreeLines = [
2107
- { nodeNames: ['root', 'a'], shape: shape1 },
2108
- { nodeNames: ['root', 'b'], shape: shape2 },
2109
- ];
2110
- EpiTreeUtil.drawTree({
2111
- canvas: makeCanvas(ctx),
2112
- theme: makeTheme(makeDimFn()),
2113
- treeAssembly: assembly,
2114
- stratification: null,
2115
- zoomLevel: 1,
2116
- highlightedNodeNames: [],
2117
- verticalScrollPosition: 0,
2118
- horizontalScrollPosition: 0,
2119
- shouldShowDistances: false,
2120
- devicePixelRatio: 1,
2121
- isLinked: false,
2122
- });
2123
- expect(ctx.stroke).toHaveBeenCalledWith(shape1);
2124
- expect(ctx.stroke).toHaveBeenCalledWith(shape2);
2125
- });
2126
-
2127
- it('strokes every horizontal ancestor tree line shape', () => {
2128
- const { ctx } = makeTrackedCtx();
2129
- const shape1 = p2d();
2130
- const assembly = makeAssembly();
2131
- assembly.horizontalAncestorTreeLines = [
2132
- { nodeNames: ['root', 'a', 'b'], shape: shape1 },
2133
- ];
2134
- EpiTreeUtil.drawTree({
2135
- canvas: makeCanvas(ctx),
2136
- theme: makeTheme(makeDimFn()),
2137
- treeAssembly: assembly,
2138
- stratification: null,
2139
- zoomLevel: 1,
2140
- highlightedNodeNames: [],
2141
- verticalScrollPosition: 0,
2142
- horizontalScrollPosition: 0,
2143
- shouldShowDistances: false,
2144
- devicePixelRatio: 1,
2145
- isLinked: false,
2146
- });
2147
- expect(ctx.stroke).toHaveBeenCalledWith(shape1);
2148
- });
2149
-
2150
- it('strokes every leaf tree line shape', () => {
2151
- const { ctx } = makeTrackedCtx();
2152
- const shapeA = p2d();
2153
- const shapeB = p2d();
2154
- const assembly = makeAssembly();
2155
- assembly.leafTreeLines = [
2156
- { nodeName: 'a', shape: shapeA },
2157
- { nodeName: 'b', shape: shapeB },
2158
- ];
2159
- EpiTreeUtil.drawTree({
2160
- canvas: makeCanvas(ctx),
2161
- theme: makeTheme(makeDimFn()),
2162
- treeAssembly: assembly,
2163
- stratification: null,
2164
- zoomLevel: 1,
2165
- highlightedNodeNames: [],
2166
- verticalScrollPosition: 0,
2167
- horizontalScrollPosition: 0,
2168
- shouldShowDistances: false,
2169
- devicePixelRatio: 1,
2170
- isLinked: false,
2171
- });
2172
- expect(ctx.stroke).toHaveBeenCalledWith(shapeA);
2173
- expect(ctx.stroke).toHaveBeenCalledWith(shapeB);
2174
- });
2175
-
2176
- it('fills every ancestor node shape', () => {
2177
- const { ctx } = makeTrackedCtx();
2178
- const shape1 = p2d();
2179
- const assembly = makeAssembly();
2180
- assembly.ancestorNodes = [
2181
- { nodeNames: ['root', 'a', 'b'], shape: shape1 },
2182
- ];
2183
- EpiTreeUtil.drawTree({
2184
- canvas: makeCanvas(ctx),
2185
- theme: makeTheme(makeDimFn()),
2186
- treeAssembly: assembly,
2187
- stratification: null,
2188
- zoomLevel: 1,
2189
- highlightedNodeNames: [],
2190
- verticalScrollPosition: 0,
2191
- horizontalScrollPosition: 0,
2192
- shouldShowDistances: false,
2193
- devicePixelRatio: 1,
2194
- isLinked: false,
2195
- });
2196
- expect(ctx.fill).toHaveBeenCalledWith(shape1);
2197
- });
2198
-
2199
- it('fills every leaf node shape', () => {
2200
- const { ctx } = makeTrackedCtx();
2201
- const shapeA = p2d();
2202
- const assembly = makeAssembly();
2203
- assembly.leafNodes = [{ nodeName: 'a', shape: shapeA }];
2204
- EpiTreeUtil.drawTree({
2205
- canvas: makeCanvas(ctx),
2206
- theme: makeTheme(makeDimFn()),
2207
- treeAssembly: assembly,
2208
- stratification: null,
2209
- zoomLevel: 1,
2210
- highlightedNodeNames: [],
2211
- verticalScrollPosition: 0,
2212
- horizontalScrollPosition: 0,
2213
- shouldShowDistances: false,
2214
- devicePixelRatio: 1,
2215
- isLinked: false,
2216
- });
2217
- expect(ctx.fill).toHaveBeenCalledWith(shapeA);
2218
- });
2219
-
2220
- it('does not draw support lines when isLinked is false', () => {
2221
- const { ctx } = makeTrackedCtx();
2222
- const assembly = makeAssembly();
2223
- assembly.supportLines = [{ nodeName: 'a', fromX: 100, toX: 800, y: 15 }];
2224
- EpiTreeUtil.drawTree({
2225
- canvas: makeCanvas(ctx),
2226
- theme: makeTheme(makeDimFn()),
2227
- treeAssembly: assembly,
2228
- stratification: null,
2229
- zoomLevel: 1,
2230
- highlightedNodeNames: [],
2231
- verticalScrollPosition: 0,
2232
- horizontalScrollPosition: 0,
2233
- shouldShowDistances: false,
2234
- devicePixelRatio: 1,
2235
- isLinked: false,
2236
- });
2237
- expect(ctx.setLineDash).not.toHaveBeenCalled();
2238
- expect(ctx.moveTo).not.toHaveBeenCalled();
2239
- expect(ctx.lineTo).not.toHaveBeenCalled();
2240
- });
2241
-
2242
- it('draws dashed support lines when isLinked is true', () => {
2243
- const { ctx } = makeTrackedCtx();
2244
- const assembly = makeAssembly();
2245
- assembly.supportLines = [
2246
- { nodeName: 'a', fromX: 100, toX: 800, y: 15 },
2247
- { nodeName: 'b', fromX: 200, toX: 800, y: 45 },
2248
- ];
2249
- EpiTreeUtil.drawTree({
2250
- canvas: makeCanvas(ctx),
2251
- theme: makeTheme(makeDimFn()),
2252
- treeAssembly: assembly,
2253
- stratification: null,
2254
- zoomLevel: 1,
2255
- highlightedNodeNames: [],
2256
- verticalScrollPosition: 0,
2257
- horizontalScrollPosition: 0,
2258
- shouldShowDistances: false,
2259
- devicePixelRatio: 1,
2260
- isLinked: true,
2261
- });
2262
- expect(ctx.setLineDash).toHaveBeenCalledWith([1, 4]);
2263
- expect(ctx.setLineDash).toHaveBeenCalledWith([]);
2264
- expect(ctx.beginPath).toHaveBeenCalledTimes(2);
2265
- expect(ctx.moveTo).toHaveBeenCalledTimes(2);
2266
- expect(ctx.lineTo).toHaveBeenCalledTimes(2);
2267
- });
2268
-
2269
- it('draws support lines extending to toX plus the horizontal scroll offset', () => {
2270
- const { ctx } = makeTrackedCtx();
2271
- const assembly = makeAssembly();
2272
- // toX=800, horizontalScrollPosition=40, devicePixelRatio=2 → lineTo(820, 15)
2273
- assembly.supportLines = [{ nodeName: 'a', fromX: 100, toX: 800, y: 15 }];
2274
- EpiTreeUtil.drawTree({
2275
- canvas: makeCanvas(ctx),
2276
- theme: makeTheme(makeDimFn()),
2277
- treeAssembly: assembly,
2278
- stratification: null,
2279
- zoomLevel: 1,
2280
- highlightedNodeNames: [],
2281
- verticalScrollPosition: 0,
2282
- horizontalScrollPosition: 40,
2283
- shouldShowDistances: false,
2284
- devicePixelRatio: 2,
2285
- isLinked: true,
2286
- });
2287
- expect(ctx.moveTo).toHaveBeenCalledWith(100, 15);
2288
- expect(ctx.lineTo).toHaveBeenCalledWith(820, 15);
2289
- });
2290
-
2291
- it('does not call fillText when shouldShowDistances is false', () => {
2292
- const { ctx } = makeTrackedCtx();
2293
- const assembly = makeAssembly();
2294
- assembly.distanceTexts = [{ nodeNames: ['a'], x: 100, y: 20, text: '0.5' }];
2295
- EpiTreeUtil.drawTree({
2296
- canvas: makeCanvas(ctx),
2297
- theme: makeTheme(makeDimFn()),
2298
- treeAssembly: assembly,
2299
- stratification: null,
2300
- zoomLevel: 1,
2301
- highlightedNodeNames: ['a'],
2302
- verticalScrollPosition: 0,
2303
- horizontalScrollPosition: 0,
2304
- shouldShowDistances: false,
2305
- devicePixelRatio: 1,
2306
- isLinked: false,
2307
- });
2308
- expect(ctx.fillText).not.toHaveBeenCalled();
2309
- });
2310
-
2311
- it('renders distance text only for nodes present in highlightedNodeNames', () => {
2312
- const { ctx } = makeTrackedCtx();
2313
- const assembly = makeAssembly();
2314
- assembly.distanceTexts = [
2315
- { nodeNames: ['a'], x: 100, y: 20, text: 'label-a' },
2316
- { nodeNames: ['b'], x: 200, y: 50, text: 'label-b' }, // not highlighted
2317
- ];
2318
- EpiTreeUtil.drawTree({
2319
- canvas: makeCanvas(ctx),
2320
- theme: makeTheme(makeDimFn()),
2321
- treeAssembly: assembly,
2322
- stratification: null,
2323
- zoomLevel: 1,
2324
- highlightedNodeNames: ['a'],
2325
- verticalScrollPosition: 0,
2326
- horizontalScrollPosition: 0,
2327
- shouldShowDistances: true,
2328
- devicePixelRatio: 1,
2329
- isLinked: false,
2330
- });
2331
- expect(ctx.fillText).toHaveBeenCalledTimes(1);
2332
- expect(ctx.fillText).toHaveBeenCalledWith('label-a', 100, 20);
2333
- });
2334
-
2335
- it('uses full tree color for every shape when highlightedNodeNames is empty', () => {
2336
- const { ctx, drawCalls } = makeTrackedCtx();
2337
- const assembly = makeAssembly();
2338
- assembly.leafTreeLines = [{ nodeName: 'a', shape: p2d() }];
2339
- assembly.leafNodes = [{ nodeName: 'a', shape: p2d() }];
2340
- const dimFn = makeDimFn();
2341
- EpiTreeUtil.drawTree({
2342
- canvas: makeCanvas(ctx),
2343
- theme: makeTheme(dimFn),
2344
- treeAssembly: assembly,
2345
- stratification: null,
2346
- zoomLevel: 1,
2347
- highlightedNodeNames: [],
2348
- verticalScrollPosition: 0,
2349
- horizontalScrollPosition: 0,
2350
- shouldShowDistances: false,
2351
- devicePixelRatio: 1,
2352
- isLinked: false,
2353
- });
2354
- expect(dimFn).not.toHaveBeenCalled();
2355
- expect(drawCalls.every(c => c.color === TREE_COLOR)).toBe(true);
2356
- });
2357
-
2358
- it('dims non-highlighted shapes when highlightedNodeNames is non-empty', () => {
2359
- const { ctx, drawCalls } = makeTrackedCtx();
2360
- const assembly = makeAssembly();
2361
- // leaf 'a' is highlighted; leaf 'b' is not → its line and dot should be dimmed
2362
- assembly.leafTreeLines = [
2363
- { nodeName: 'a', shape: p2d() },
2364
- { nodeName: 'b', shape: p2d() },
2365
- ];
2366
- assembly.leafNodes = [
2367
- { nodeName: 'a', shape: p2d() },
2368
- { nodeName: 'b', shape: p2d() },
2369
- ];
2370
- const dimFn = makeDimFn();
2371
- EpiTreeUtil.drawTree({
2372
- canvas: makeCanvas(ctx),
2373
- theme: makeTheme(dimFn),
2374
- treeAssembly: assembly,
2375
- stratification: null,
2376
- zoomLevel: 1,
2377
- highlightedNodeNames: ['a'],
2378
- verticalScrollPosition: 0,
2379
- horizontalScrollPosition: 0,
2380
- shouldShowDistances: false,
2381
- devicePixelRatio: 1,
2382
- isLinked: false,
2383
- });
2384
- // 'b' leaf tree line + 'b' leaf node = 2 dimmed draw calls
2385
- const dimmedCalls = drawCalls.filter(c => c.color === DIM_COLOR);
2386
- expect(dimmedCalls).toHaveLength(2);
2387
- expect(dimFn).toHaveBeenCalledWith(TREE_COLOR);
2388
- });
2389
-
2390
- it('uses the stratification caseIdColor for leaf node fill', () => {
2391
- const { ctx, drawCalls } = makeTrackedCtx();
2392
- const leafShape = p2d();
2393
- const assembly = makeAssembly();
2394
- assembly.leafNodes = [{ nodeName: 'case-1', shape: leafShape }];
2395
- const stratification: Stratification = {
2396
- mode: STRATIFICATION_MODE.FIELD,
2397
- caseIdColors: { 'case-1': '#ff0000' },
2398
- };
2399
- EpiTreeUtil.drawTree({
2400
- canvas: makeCanvas(ctx),
2401
- theme: makeTheme(makeDimFn()),
2402
- treeAssembly: assembly,
2403
- stratification,
2404
- zoomLevel: 1,
2405
- highlightedNodeNames: [],
2406
- verticalScrollPosition: 0,
2407
- horizontalScrollPosition: 0,
2408
- shouldShowDistances: false,
2409
- devicePixelRatio: 1,
2410
- isLinked: false,
2411
- });
2412
- const fillCall = drawCalls.find(c => c.type === 'fill');
2413
- expect(fillCall?.color).toBe('#ff0000');
2414
- expect(ctx.fill).toHaveBeenCalledWith(leafShape);
2415
- });
2416
-
2417
- it('uses the tree color for leaf node fill when stratification is null', () => {
2418
- const { ctx, drawCalls } = makeTrackedCtx();
2419
- const leafShape = p2d();
2420
- const assembly = makeAssembly();
2421
- assembly.leafNodes = [{ nodeName: 'case-1', shape: leafShape }];
2422
- EpiTreeUtil.drawTree({
2423
- canvas: makeCanvas(ctx),
2424
- theme: makeTheme(makeDimFn()),
2425
- treeAssembly: assembly,
2426
- stratification: null,
2427
- zoomLevel: 1,
2428
- highlightedNodeNames: [],
2429
- verticalScrollPosition: 0,
2430
- horizontalScrollPosition: 0,
2431
- shouldShowDistances: false,
2432
- devicePixelRatio: 1,
2433
- isLinked: false,
2434
- });
2435
- const fillCall = drawCalls.find(c => c.type === 'fill');
2436
- expect(fillCall?.color).toBe(TREE_COLOR);
2437
- });
2438
-
2439
- it('dims ancestor tree lines (array nodeNames) that are not in highlightedNodeNames', () => {
2440
- const { ctx, drawCalls } = makeTrackedCtx();
2441
- const assembly = makeAssembly();
2442
- // verticalAncestorTreeLines use nodeNames: string[], not nodeName: string
2443
- // 'a' is highlighted; ['b','c'] is NOT highlighted → should be dimmed via Array.isArray path
2444
- assembly.verticalAncestorTreeLines = [
2445
- { nodeNames: ['a'], shape: p2d() },
2446
- { nodeNames: ['b', 'c'], shape: p2d() },
2447
- ];
2448
- const dimFn = makeDimFn();
2449
- EpiTreeUtil.drawTree({
2450
- canvas: makeCanvas(ctx),
2451
- theme: makeTheme(dimFn),
2452
- treeAssembly: assembly,
2453
- stratification: null,
2454
- zoomLevel: 1,
2455
- highlightedNodeNames: ['a'],
2456
- verticalScrollPosition: 0,
2457
- horizontalScrollPosition: 0,
2458
- shouldShowDistances: false,
2459
- devicePixelRatio: 1,
2460
- isLinked: false,
2461
- });
2462
- const dimmedCalls = drawCalls.filter(c => c.color === DIM_COLOR);
2463
- expect(dimmedCalls.length).toBeGreaterThanOrEqual(1);
2464
- expect(dimFn).toHaveBeenCalledWith(TREE_COLOR);
2465
- });
2466
- });
2467
- });