@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.
- package/dist/environment.d.ts +54 -0
- package/dist/index.d.ts +16078 -0
- package/dist/index.js.map +1 -0
- package/package.json +13 -16
- package/src/@types/environment.d.ts +0 -54
- package/src/api/api.ts +0 -43576
- package/src/api/base.ts +0 -111
- package/src/api/common.ts +0 -168
- package/src/api/configuration.ts +0 -118
- package/src/api/index.ts +0 -20
- package/src/assets/icons/CollectionIcon.svg +0 -1
- package/src/assets/icons/DnaIcon.svg +0 -1
- package/src/assets/icons/VirusIcon.svg +0 -1
- package/src/assets/logo/logo-rijksoverheid.svg +0 -42
- package/src/assets/logo/logo-rivm.svg +0 -6
- package/src/classes/Subject/Subject.ts +0 -26
- package/src/classes/Subject/index.ts +0 -1
- package/src/classes/TableEventBus/TableEventBus.ts +0 -14
- package/src/classes/TableEventBus/index.ts +0 -1
- package/src/classes/abstracts/EventBusAbstract/EventBusAbstract.ts +0 -37
- package/src/classes/abstracts/EventBusAbstract/index.ts +0 -1
- package/src/classes/abstracts/FilterAbstract/FilterAbstract.ts +0 -54
- package/src/classes/abstracts/FilterAbstract/index.ts +0 -1
- package/src/classes/abstracts/SubscribableAbstract/SubscribableAbstract.ts +0 -20
- package/src/classes/abstracts/SubscribableAbstract/index.ts +0 -1
- package/src/classes/errors/UploadError.ts +0 -11
- package/src/classes/errors/index.ts +0 -1
- package/src/classes/filters/BooleanFilter.ts +0 -31
- package/src/classes/filters/DateFilter.ts +0 -133
- package/src/classes/filters/GeoFilter.ts +0 -60
- package/src/classes/filters/MultiSelectFilter.ts +0 -70
- package/src/classes/filters/NumberRangeFilter.ts +0 -82
- package/src/classes/filters/SelectionFilter.ts +0 -25
- package/src/classes/filters/TextFilter.ts +0 -36
- package/src/classes/filters/TreeFilter.ts +0 -22
- package/src/classes/managers/AuthenticationManager/AuthenticationManager.ts +0 -94
- package/src/classes/managers/AuthenticationManager/index.ts +0 -1
- package/src/classes/managers/AuthorizationManager/AuthorizationManager.ts +0 -70
- package/src/classes/managers/AuthorizationManager/index.ts +0 -1
- package/src/classes/managers/BackendVersionManager/BackendVersionManager.ts +0 -23
- package/src/classes/managers/BackendVersionManager/index.ts +0 -1
- package/src/classes/managers/BreadcrumbManager/BreadcrumbManager.ts +0 -30
- package/src/classes/managers/BreadcrumbManager/index.ts +0 -1
- package/src/classes/managers/ConfigManager/ConfigManager.ts +0 -31
- package/src/classes/managers/ConfigManager/index.ts +0 -1
- package/src/classes/managers/DevicePixelRatioManager/DevicePixelRatioManager.ts +0 -27
- package/src/classes/managers/DevicePixelRatioManager/index.ts +0 -1
- package/src/classes/managers/EmotionCacheManager/EmotionCacheManager.ts +0 -27
- package/src/classes/managers/EmotionCacheManager/index.ts +0 -1
- package/src/classes/managers/EpiDataManager/EpiDataManager.ts +0 -196
- package/src/classes/managers/EpiDataManager/index.ts +0 -1
- package/src/classes/managers/EpiEventBusManager/EpiEventBusManager.ts +0 -61
- package/src/classes/managers/EpiEventBusManager/index.ts +0 -1
- package/src/classes/managers/EpiHighlightingManager/EpiHighlightingManager.ts +0 -32
- package/src/classes/managers/EpiHighlightingManager/index.ts +0 -1
- package/src/classes/managers/EpiLineListCaseSetMembersManager/EpiListsCaseSetMembersManager.ts +0 -118
- package/src/classes/managers/EpiLineListCaseSetMembersManager/index.ts +0 -1
- package/src/classes/managers/FeatureFlagsManager/FeatureFlagsManager.ts +0 -30
- package/src/classes/managers/FeatureFlagsManager/index.ts +0 -1
- package/src/classes/managers/I18nManager/I18nManager.ts +0 -105
- package/src/classes/managers/I18nManager/index.ts +0 -1
- package/src/classes/managers/InactivityManager/InactivityManager.ts +0 -119
- package/src/classes/managers/InactivityManager/index.ts +0 -1
- package/src/classes/managers/KeyboardShortcutManager/KeyboardShortcutManager.ts +0 -78
- package/src/classes/managers/KeyboardShortcutManager/index.ts +0 -1
- package/src/classes/managers/LogManager/LogManager.ts +0 -151
- package/src/classes/managers/LogManager/index.ts +0 -1
- package/src/classes/managers/NavigationHistoryManager/NavigationHistoryManager.ts +0 -16
- package/src/classes/managers/NavigationHistoryManager/index.ts +0 -1
- package/src/classes/managers/NotificationManager/NotificationManager.ts +0 -104
- package/src/classes/managers/NotificationManager/index.ts +0 -1
- package/src/classes/managers/PageEventBusManager/PageEventBusManager.ts +0 -116
- package/src/classes/managers/PageEventBusManager/index.ts +0 -1
- package/src/classes/managers/QueryClientManager/QueryClientManager.ts +0 -51
- package/src/classes/managers/QueryClientManager/index.ts +0 -1
- package/src/classes/managers/RouterManager/RouterManager.ts +0 -25
- package/src/classes/managers/RouterManager/index.ts +0 -1
- package/src/classes/managers/UserSettingsManager/UserSettingsManager.ts +0 -16
- package/src/classes/managers/UserSettingsManager/index.ts +0 -1
- package/src/classes/managers/WindowManager/WindowManager.test.ts +0 -23
- package/src/classes/managers/WindowManager/WindowManager.ts +0 -26
- package/src/classes/managers/WindowManager/index.ts +0 -1
- package/src/components/app/App/App.tsx +0 -58
- package/src/components/app/App/index.ts +0 -1
- package/src/components/app/ApplicationBootstrap/ApplicationBootstrap.tsx +0 -223
- package/src/components/app/ApplicationBootstrap/index.ts +0 -1
- package/src/components/app/RouterRoot/RouterRoot.tsx +0 -187
- package/src/components/app/RouterRoot/index.ts +0 -1
- package/src/components/epi/EpiAddCasesToEventDialog/EpiAddCasesToEventDialog.tsx +0 -347
- package/src/components/epi/EpiAddCasesToEventDialog/EpiAddCasesToEventDialogSuccessNotificationMessage.tsx +0 -38
- package/src/components/epi/EpiAddCasesToEventDialog/index.ts +0 -1
- package/src/components/epi/EpiBulkEditCaseDialog/EpiBulkEditCaseDialog.tsx +0 -61
- package/src/components/epi/EpiBulkEditCaseDialog/index.ts +0 -1
- package/src/components/epi/EpiCaseInfoDialog/EpiCaseCaseSetInfo.tsx +0 -137
- package/src/components/epi/EpiCaseInfoDialog/EpiCaseContent.tsx +0 -127
- package/src/components/epi/EpiCaseInfoDialog/EpiCaseForm.tsx +0 -111
- package/src/components/epi/EpiCaseInfoDialog/EpiCaseInfoDialog.tsx +0 -377
- package/src/components/epi/EpiCaseInfoDialog/EpiCaseSharingForm.tsx +0 -140
- package/src/components/epi/EpiCaseInfoDialog/EpiCaseSharingInfo.tsx +0 -23
- package/src/components/epi/EpiCaseInfoDialog/EpiReadOnlyCaseContent.tsx +0 -89
- package/src/components/epi/EpiCaseInfoDialog/index.ts +0 -1
- package/src/components/epi/EpiCaseSetInfoDialog/EpiCaseSetContent.tsx +0 -108
- package/src/components/epi/EpiCaseSetInfoDialog/EpiCaseSetDescription.tsx +0 -34
- package/src/components/epi/EpiCaseSetInfoDialog/EpiCaseSetForm.tsx +0 -151
- package/src/components/epi/EpiCaseSetInfoDialog/EpiCaseSetInfoDialog.tsx +0 -389
- package/src/components/epi/EpiCaseSetInfoDialog/EpiCaseSetSharingForm.tsx +0 -155
- package/src/components/epi/EpiCaseSetInfoDialog/EpiCaseSetSharingInfo.tsx +0 -23
- package/src/components/epi/EpiCaseSetInfoDialog/index.ts +0 -1
- package/src/components/epi/EpiCaseSummary/EpiCaseSummary.tsx +0 -141
- package/src/components/epi/EpiCaseSummary/index.ts +0 -1
- package/src/components/epi/EpiCaseTypeInfoDialog/EpiCaseTypeInfoAccessRights.tsx +0 -91
- package/src/components/epi/EpiCaseTypeInfoDialog/EpiCaseTypeInfoColAccessRights.tsx +0 -118
- package/src/components/epi/EpiCaseTypeInfoDialog/EpiCaseTypeInfoData.tsx +0 -38
- package/src/components/epi/EpiCaseTypeInfoDialog/EpiCaseTypeInfoDialog.tsx +0 -39
- package/src/components/epi/EpiCaseTypeInfoDialog/EpiCaseTypeInfoDialogContent.tsx +0 -169
- package/src/components/epi/EpiCaseTypeInfoDialog/EpiCaseTypeInfoDialogWithLoader.tsx +0 -42
- package/src/components/epi/EpiCaseTypeInfoDialog/EpiCaseTypeInfoRegions.tsx +0 -87
- package/src/components/epi/EpiCaseTypeInfoDialog/EpiCaseTypeInfoTrees.tsx +0 -73
- package/src/components/epi/EpiCaseTypeInfoDialog/EpiCaseTypeInfoValues.tsx +0 -61
- package/src/components/epi/EpiCaseTypeInfoDialog/EpiCaseTypeInfoVariableDetails.tsx +0 -151
- package/src/components/epi/EpiCaseTypeInfoDialog/index.ts +0 -10
- package/src/components/epi/EpiCasesAlreadyInCaseSetWarning/EpiCasesAlreadyInCaseSetWarning.tsx +0 -190
- package/src/components/epi/EpiCasesAlreadyInCaseSetWarning/EpiCasesAlreadyInCaseSetWarningCaseSetLink.tsx +0 -62
- package/src/components/epi/EpiCasesAlreadyInCaseSetWarning/index.ts +0 -1
- package/src/components/epi/EpiCompletCaseTypeLoader/EpiCompletCaseTypeLoader.tsx +0 -87
- package/src/components/epi/EpiCompletCaseTypeLoader/index.ts +0 -1
- package/src/components/epi/EpiContactDetailsDialog/EpiContactDetailsDialog.tsx +0 -151
- package/src/components/epi/EpiContactDetailsDialog/index.ts +0 -1
- package/src/components/epi/EpiContextMenu/EpiContextMenu.tsx +0 -155
- package/src/components/epi/EpiContextMenu/index.ts +0 -1
- package/src/components/epi/EpiCreateEventDialog/EpiCreateEventDialog.tsx +0 -386
- package/src/components/epi/EpiCreateEventDialog/EpiCreateEventDialogSuccessNotificationMessage.tsx +0 -30
- package/src/components/epi/EpiCreateEventDialog/index.ts +0 -1
- package/src/components/epi/EpiCurve/EpiCurve.tsx +0 -525
- package/src/components/epi/EpiCurve/index.ts +0 -1
- package/src/components/epi/EpiCustomTabPanel/EpiCustomTabPanel.tsx +0 -27
- package/src/components/epi/EpiCustomTabPanel/index.ts +0 -1
- package/src/components/epi/EpiDashboard/EpiDashboard.tsx +0 -390
- package/src/components/epi/EpiDashboard/EpiDashboardDownloadSidebarItem.tsx +0 -154
- package/src/components/epi/EpiDashboard/EpiDashboardGeneralSettingsForm.tsx +0 -89
- package/src/components/epi/EpiDashboard/EpiDashboardLayoutRenderer.tsx +0 -248
- package/src/components/epi/EpiDashboard/EpiDashboardLayoutSettingsForm.tsx +0 -160
- package/src/components/epi/EpiDashboard/EpiDashboardSettingsSidebarItem.tsx +0 -93
- package/src/components/epi/EpiDashboard/EpiDashboardTreeSettingsForm.tsx +0 -90
- package/src/components/epi/EpiDashboard/index.ts +0 -1
- package/src/components/epi/EpiDashboardStoreLoader/EpiDashboardStoreLoader.tsx +0 -34
- package/src/components/epi/EpiDashboardStoreLoader/EpiDashboardStoreLoaderContent.tsx +0 -73
- package/src/components/epi/EpiDashboardStoreLoader/index.ts +0 -2
- package/src/components/epi/EpiDashboardStoreLoader/withEpiDashboardStore.tsx +0 -19
- package/src/components/epi/EpiDataCollectionAccessInfo/EpiDataCollectionAccessInfo.tsx +0 -88
- package/src/components/epi/EpiDataCollectionAccessInfo/index.ts +0 -1
- package/src/components/epi/EpiFindSimilarCasesDialog/EpiFindSimilarCasesDialog.tsx +0 -302
- package/src/components/epi/EpiFindSimilarCasesDialog/index.ts +0 -1
- package/src/components/epi/EpiLegendaItem/EpiLegendaItem.tsx +0 -106
- package/src/components/epi/EpiLegendaItem/index.ts +0 -1
- package/src/components/epi/EpiLineList/EpiLineList.tsx +0 -492
- package/src/components/epi/EpiLineList/EpiLineListPrimaryMenu.tsx +0 -189
- package/src/components/epi/EpiLineList/EpiLineListSecondaryMenu.tsx +0 -56
- package/src/components/epi/EpiLineList/EpiLineListTitle.tsx +0 -28
- package/src/components/epi/EpiLineList/index.ts +0 -1
- package/src/components/epi/EpiLineList/useEpiLineListEmitDownloadOptions.tsx +0 -102
- package/src/components/epi/EpiMap/EpiMap.tsx +0 -547
- package/src/components/epi/EpiMap/index.ts +0 -1
- package/src/components/epi/EpiRemoveCasesFromEventDialog/EpiRemoveCasesFromEventDialog.tsx +0 -169
- package/src/components/epi/EpiRemoveCasesFromEventDialog/index.ts +0 -1
- package/src/components/epi/EpiRemoveFindSimilarCasesResultDialog/EpiRemoveFindSimilarCasesResultDialog.tsx +0 -216
- package/src/components/epi/EpiSequenceDownloadDialog/EpiSequenceDownloadDialog.tsx +0 -151
- package/src/components/epi/EpiSequenceDownloadDialog/index.ts +0 -1
- package/src/components/epi/EpiStratification/EpiStratification.tsx +0 -241
- package/src/components/epi/EpiStratification/index.ts +0 -1
- package/src/components/epi/EpiTree/EpiTree.tsx +0 -906
- package/src/components/epi/EpiTree/index.ts +0 -1
- package/src/components/epi/EpiTreeDescription/EpiTreeDescription.tsx +0 -71
- package/src/components/epi/EpiTreeDescription/index.ts +0 -1
- package/src/components/epi/EpiUpload/EpiUpload.tsx +0 -107
- package/src/components/epi/EpiUpload/EpiUploadCaseResultTable.tsx +0 -383
- package/src/components/epi/EpiUpload/EpiUploadCreateCases.tsx +0 -266
- package/src/components/epi/EpiUpload/EpiUploadMapColumns.tsx +0 -261
- package/src/components/epi/EpiUpload/EpiUploadMapSequences.tsx +0 -332
- package/src/components/epi/EpiUpload/EpiUploadNavigation.tsx +0 -52
- package/src/components/epi/EpiUpload/EpiUploadSelectFile.tsx +0 -285
- package/src/components/epi/EpiUpload/EpiUploadSelectSequenceFiles.tsx +0 -375
- package/src/components/epi/EpiUpload/EpiUploadValidate.tsx +0 -199
- package/src/components/epi/EpiUpload/EpiUploadValidateNavigation.tsx +0 -34
- package/src/components/epi/EpiUpload/index.ts +0 -1
- package/src/components/epi/EpiUserRightsDialog/EpiUserRightsDialog.tsx +0 -101
- package/src/components/epi/EpiUserRightsDialog/EpiUserRightsDialogCaseAccessPolicy.tsx +0 -160
- package/src/components/epi/EpiUserRightsDialog/index.ts +0 -1
- package/src/components/epi/EpiWarning/EpiWarning.tsx +0 -39
- package/src/components/epi/EpiWarning/index.ts +0 -1
- package/src/components/epi/EpiWidget/EpiWidget.tsx +0 -255
- package/src/components/epi/EpiWidget/index.ts +0 -1
- package/src/components/epi/EpiWidgetHeaderIconButton/EpiWidgetHeaderIconButton.tsx +0 -40
- package/src/components/epi/EpiWidgetHeaderIconButton/index.ts +0 -1
- package/src/components/epi/EpiWidgetMenu/EpiWidgetMenu.tsx +0 -56
- package/src/components/epi/EpiWidgetMenu/index.ts +0 -1
- package/src/components/epi/EpiWidgetUnavailable/EpiWidgetUnavailable.tsx +0 -51
- package/src/components/epi/EpiWidgetUnavailable/index.ts +0 -1
- package/src/components/filters/BooleanFilterField/BooleanFilterField.tsx +0 -26
- package/src/components/filters/BooleanFilterField/index.ts +0 -1
- package/src/components/filters/DateFilterField/DateFilterField.tsx +0 -23
- package/src/components/filters/DateFilterField/index.ts +0 -1
- package/src/components/filters/GeoFilterField/GeoFilterField.tsx +0 -32
- package/src/components/filters/GeoFilterField/index.ts +0 -1
- package/src/components/filters/MultiSelectFilterField/MultiSelectFilterField.tsx +0 -31
- package/src/components/filters/MultiSelectFilterField/index.ts +0 -1
- package/src/components/filters/NumberRangeFilterField/NumberRangeFilterField.tsx +0 -20
- package/src/components/filters/NumberRangeFilterField/index.ts +0 -1
- package/src/components/filters/TextFilterField/TextFilterField.tsx +0 -18
- package/src/components/filters/TextFilterField/index.ts +0 -1
- package/src/components/form/fields/Autocomplete/Autocomplete.tsx +0 -270
- package/src/components/form/fields/Autocomplete/index.ts +0 -1
- package/src/components/form/fields/CheckboxGroup/CheckboxGroup.tsx +0 -192
- package/src/components/form/fields/CheckboxGroup/index.ts +0 -1
- package/src/components/form/fields/DatePicker/DatePicker.tsx +0 -167
- package/src/components/form/fields/DatePicker/index.ts +0 -1
- package/src/components/form/fields/DateRangePicker/DateRangePicker.tsx +0 -359
- package/src/components/form/fields/DateRangePicker/index.ts +0 -1
- package/src/components/form/fields/NumberField/NumberField.tsx +0 -277
- package/src/components/form/fields/NumberField/index.ts +0 -1
- package/src/components/form/fields/NumberRangeInput/NumberRangeInput.tsx +0 -297
- package/src/components/form/fields/NumberRangeInput/index.ts +0 -1
- package/src/components/form/fields/RadioGroup/RadioGroup.tsx +0 -130
- package/src/components/form/fields/RadioGroup/index.ts +0 -1
- package/src/components/form/fields/RichTextEditor/RichTextEditor.tsx +0 -321
- package/src/components/form/fields/RichTextEditor/RichTextEditorContent.tsx +0 -34
- package/src/components/form/fields/RichTextEditor/index.ts +0 -2
- package/src/components/form/fields/RichTextEditor/useRichTextEditorExtensions.ts +0 -174
- package/src/components/form/fields/Select/Select.tsx +0 -219
- package/src/components/form/fields/Select/index.ts +0 -1
- package/src/components/form/fields/Switch/Switch.tsx +0 -116
- package/src/components/form/fields/Switch/index.ts +0 -1
- package/src/components/form/fields/TextField/TextField.tsx +0 -170
- package/src/components/form/fields/TextField/index.ts +0 -1
- package/src/components/form/fields/ToggleButtonGroup/ToggleButtonGroup.tsx +0 -118
- package/src/components/form/fields/ToggleButtonGroup/index.ts +0 -1
- package/src/components/form/fields/TransferList/TransferList.tsx +0 -360
- package/src/components/form/fields/TransferList/index.ts +0 -1
- package/src/components/form/fields/UploadButton/UploadButton.tsx +0 -157
- package/src/components/form/helpers/FormFieldHelperText/FormFieldHelperText.tsx +0 -68
- package/src/components/form/helpers/FormFieldHelperText/index.ts +0 -1
- package/src/components/form/helpers/FormFieldLoadingIndicator/FormFieldLoadingIndicator.tsx +0 -22
- package/src/components/form/helpers/FormFieldLoadingIndicator/index.ts +0 -1
- package/src/components/form/helpers/GenericForm/GenericForm.tsx +0 -187
- package/src/components/form/helpers/GenericForm/index.ts +0 -1
- package/src/components/ui/ApplicationBar/ApplicationBar.tsx +0 -74
- package/src/components/ui/ApplicationBar/ApplicationBarActions.tsx +0 -124
- package/src/components/ui/ApplicationBar/ApplicationBarActionsFeedbackItem.tsx +0 -102
- package/src/components/ui/ApplicationBar/ApplicationBarActionsInfotem.tsx +0 -60
- package/src/components/ui/ApplicationBar/ApplicationBarActionsNotificationsItem.tsx +0 -82
- package/src/components/ui/ApplicationBar/ApplicationBarActionsOrganizationSwitcherItem.tsx +0 -58
- package/src/components/ui/ApplicationBar/ApplicationBarActionsOutagesItem.tsx +0 -75
- package/src/components/ui/ApplicationBar/ApplicationBarActionsUserItem.tsx +0 -60
- package/src/components/ui/ApplicationBar/ApplicationBarNavigationMenu.tsx +0 -159
- package/src/components/ui/ApplicationBar/InfoMenu.tsx +0 -120
- package/src/components/ui/ApplicationBar/UserMenu.tsx +0 -208
- package/src/components/ui/ApplicationBar/UserOrganizationAdminMenuItem.tsx +0 -69
- package/src/components/ui/ApplicationBar/UserOwnOrganizationMenuItem.tsx +0 -54
- package/src/components/ui/ApplicationBar/index.ts +0 -1
- package/src/components/ui/ApplicationFooter/ApplicationFooter.tsx +0 -121
- package/src/components/ui/ApplicationFooter/ApplicationFooterLink.tsx +0 -48
- package/src/components/ui/ApplicationFooter/ApplicationFooterLinkSection.tsx +0 -56
- package/src/components/ui/ApplicationFooter/index.ts +0 -1
- package/src/components/ui/AuthenticationWrapper/AuthenticationWrapper.tsx +0 -208
- package/src/components/ui/AuthenticationWrapper/index.ts +0 -1
- package/src/components/ui/AuthorizationWrapper/AuthorizationWrapper.tsx +0 -75
- package/src/components/ui/AuthorizationWrapper/index.ts +0 -1
- package/src/components/ui/Breadcrumbs/Breadcrumbs.tsx +0 -92
- package/src/components/ui/Breadcrumbs/index.ts +0 -1
- package/src/components/ui/Confirmation/Confirmation.tsx +0 -10
- package/src/components/ui/Confirmation/ConfirmationRender.tsx +0 -81
- package/src/components/ui/Confirmation/index.ts +0 -2
- package/src/components/ui/ConsentDialog/ConsentDialog.tsx +0 -57
- package/src/components/ui/ConsentDialog/index.ts +0 -1
- package/src/components/ui/CopyToClipboardButton/CopyToClipboardButton.tsx +0 -94
- package/src/components/ui/CopyToClipboardButton/index.ts +0 -1
- package/src/components/ui/Dialog/Dialog.tsx +0 -196
- package/src/components/ui/Dialog/index.ts +0 -1
- package/src/components/ui/FileSelector/FileSelector.tsx +0 -358
- package/src/components/ui/FileSelector/index.ts +0 -1
- package/src/components/ui/GenericErrorMessage/GenericErrorMessage.tsx +0 -168
- package/src/components/ui/GenericErrorMessage/index.ts +0 -1
- package/src/components/ui/HomePageTrends/HomePageTrendCard.tsx +0 -132
- package/src/components/ui/HomePageTrends/HomePageTrends.tsx +0 -312
- package/src/components/ui/HomePageTrends/index.ts +0 -1
- package/src/components/ui/LicensesDialog/LicensesDialog.tsx +0 -231
- package/src/components/ui/LicensesDialog/index.ts +0 -1
- package/src/components/ui/LinearProgressWithLabel/LinearProgressWithLabel.tsx +0 -31
- package/src/components/ui/LinearProgressWithLabel/index.ts +0 -1
- package/src/components/ui/LoadingIndicator/LoadingIndicator.tsx +0 -11
- package/src/components/ui/LoadingIndicator/index.ts +0 -1
- package/src/components/ui/MyPermissionsDialog/MyPermissionsDialog.tsx +0 -95
- package/src/components/ui/MyPermissionsDialog/index.ts +0 -1
- package/src/components/ui/NavLink/NavLink.tsx +0 -47
- package/src/components/ui/NavLink/index.ts +0 -1
- package/src/components/ui/NestedMenu/IconMenuItem.tsx +0 -64
- package/src/components/ui/NestedMenu/NestedDropdown.tsx +0 -133
- package/src/components/ui/NestedMenu/NestedMenuItem.tsx +0 -250
- package/src/components/ui/NestedMenu/index.ts +0 -4
- package/src/components/ui/NestedMenu/nestedMenuItemsFromObject.tsx +0 -98
- package/src/components/ui/Notifications/NotificationItem.tsx +0 -70
- package/src/components/ui/Notifications/NotificationsDrawer.tsx +0 -161
- package/src/components/ui/Notifications/NotificationsStack.tsx +0 -45
- package/src/components/ui/Notifications/index.ts +0 -2
- package/src/components/ui/OrganizationSwitcherDialog/OrganizationSwitcherDialog.tsx +0 -204
- package/src/components/ui/OrganizationSwitcherDialog/index.ts +0 -1
- package/src/components/ui/OutageList/OutageItem.tsx +0 -48
- package/src/components/ui/OutageList/OutageList.tsx +0 -54
- package/src/components/ui/OutageList/OutageSection.tsx +0 -39
- package/src/components/ui/OutageList/index.ts +0 -1
- package/src/components/ui/OutagesDialog/OutagesDialog.tsx +0 -71
- package/src/components/ui/OutagesDialog/index.ts +0 -1
- package/src/components/ui/PageContainer/PageContainer.tsx +0 -207
- package/src/components/ui/PageContainer/index.ts +0 -1
- package/src/components/ui/PanelSeparator/PanelResizeHandle.tsx +0 -67
- package/src/components/ui/PanelSeparator/index.ts +0 -1
- package/src/components/ui/ResponseHandler/ResponseHandler.tsx +0 -79
- package/src/components/ui/ResponseHandler/index.ts +0 -1
- package/src/components/ui/Sidebar/SidebarItem.tsx +0 -138
- package/src/components/ui/Sidebar/SidebarMenu.tsx +0 -33
- package/src/components/ui/Sidebar/SidebarMenuItem.tsx +0 -61
- package/src/components/ui/Sidebar/index.ts +0 -3
- package/src/components/ui/SortableList/SortableList.tsx +0 -172
- package/src/components/ui/SortableList/SortableListItem.tsx +0 -73
- package/src/components/ui/SortableList/SortableListItemDragHandle.tsx +0 -53
- package/src/components/ui/SortableList/SortableOverlay.tsx +0 -35
- package/src/components/ui/SortableList/context/SortableListItemContext.tsx +0 -17
- package/src/components/ui/SortableList/context/SortableListItemContextProvider.tsx +0 -19
- package/src/components/ui/SortableList/context/useSortableListItemContext.tsx +0 -5
- package/src/components/ui/Spinner/Spinner.tsx +0 -84
- package/src/components/ui/Spinner/index.ts +0 -1
- package/src/components/ui/Stepper/Stepper.tsx +0 -194
- package/src/components/ui/Stepper/index.ts +0 -2
- package/src/components/ui/Stepper/stepperModel.ts +0 -4
- package/src/components/ui/Table/Table.tsx +0 -812
- package/src/components/ui/Table/TableActionsCell.tsx +0 -81
- package/src/components/ui/Table/TableCaption.tsx +0 -33
- package/src/components/ui/Table/TableCell.tsx +0 -162
- package/src/components/ui/Table/TableCellAsyncContent.tsx +0 -28
- package/src/components/ui/Table/TableCheckboxCell.tsx +0 -65
- package/src/components/ui/Table/TableCheckboxHeader.tsx +0 -72
- package/src/components/ui/Table/TableColumnsEditorDialog.tsx +0 -241
- package/src/components/ui/Table/TableFilter.tsx +0 -31
- package/src/components/ui/Table/TableFiltersSidebarItem.tsx +0 -398
- package/src/components/ui/Table/TableHeader.tsx +0 -40
- package/src/components/ui/Table/TableHeaderCell.tsx +0 -434
- package/src/components/ui/Table/TableHeaderFilter.tsx +0 -104
- package/src/components/ui/Table/TableMenu.tsx +0 -26
- package/src/components/ui/Table/TableReadableIndexCell.tsx +0 -65
- package/src/components/ui/Table/TableSidebarMenu.tsx +0 -59
- package/src/components/ui/Table/classNames.ts +0 -7
- package/src/components/ui/Table/index.ts +0 -12
- package/src/components/ui/UserFeedbackDialog/UserFeedbackDialog.tsx +0 -168
- package/src/components/ui/UserFeedbackDialog/index.ts +0 -1
- package/src/components/ui/UserInactivityConfirmation/UserInactivityConfirmation.tsx +0 -59
- package/src/components/ui/UserInactivityConfirmation/index.ts +0 -1
- package/src/components/ui/UsersEffectiveRightsDetailsDialog/UsersEffectiveRightsDetailsDialog.tsx +0 -584
- package/src/components/ui/UsersEffectiveRightsDetailsDialog/index.ts +0 -1
- package/src/context/caseAbac/CaseAbacContext.tsx +0 -24
- package/src/context/caseAbac/CaseAbacContextProvider.tsx +0 -69
- package/src/context/caseAbac/index.ts +0 -3
- package/src/context/caseAbac/useCaseAbacContext.tsx +0 -6
- package/src/context/caseTypeAbac/CaseTypeAbacContext.tsx +0 -16
- package/src/context/caseTypeAbac/CaseTypeAbacContextProvider.tsx +0 -40
- package/src/context/caseTypeAbac/index.ts +0 -3
- package/src/context/caseTypeAbac/useCaseTypeAbacContext.tsx +0 -6
- package/src/data/date.ts +0 -8
- package/src/data/queryDependencies.ts +0 -166
- package/src/dataHooks/useAssemblyProtocolsQuery/index.ts +0 -1
- package/src/dataHooks/useAssemblyProtocolsQuery/useAssemblyProtocolsQuery.ts +0 -39
- package/src/dataHooks/useCaseRightsQuery/index.ts +0 -1
- package/src/dataHooks/useCaseRightsQuery/useCaseRightsQuery.ts +0 -21
- package/src/dataHooks/useCaseSetCategoriesQuery/index.ts +0 -1
- package/src/dataHooks/useCaseSetCategoriesQuery/useCaseSetCategoriesQuery.ts +0 -39
- package/src/dataHooks/useCaseSetRightsQuery/index.ts +0 -1
- package/src/dataHooks/useCaseSetRightsQuery/useCaseSetRightsQuery.ts +0 -17
- package/src/dataHooks/useCaseSetStatsQuery/index.ts +0 -1
- package/src/dataHooks/useCaseSetStatsQuery/useCaseSetStatsQuery.ts +0 -31
- package/src/dataHooks/useCaseSetStatusesQuery/index.ts +0 -1
- package/src/dataHooks/useCaseSetStatusesQuery/useCaseSetStatusesQuery.ts +0 -39
- package/src/dataHooks/useCaseSetsQuery/index.ts +0 -1
- package/src/dataHooks/useCaseSetsQuery/useCaseSetsQuery.ts +0 -41
- package/src/dataHooks/useCaseTypeSetCategoriesQuery/index.ts +0 -1
- package/src/dataHooks/useCaseTypeSetCategoriesQuery/useCaseTypeSetCategoriesQuery.ts +0 -41
- package/src/dataHooks/useCaseTypeSetMembersQuery/index.ts +0 -1
- package/src/dataHooks/useCaseTypeSetMembersQuery/useCaseTypeSetMembersQuery.ts +0 -17
- package/src/dataHooks/useCaseTypeSetsQuery/index.ts +0 -1
- package/src/dataHooks/useCaseTypeSetsQuery/useCaseTypeSetsQuery.ts +0 -58
- package/src/dataHooks/useCaseTypeStatsQuery/index.ts +0 -1
- package/src/dataHooks/useCaseTypeStatsQuery/useCaseTypeStatsQuery.ts +0 -20
- package/src/dataHooks/useCaseTypesQuery/index.ts +0 -1
- package/src/dataHooks/useCaseTypesQuery/useCaseTypesQuery.ts +0 -43
- package/src/dataHooks/useColSetMembersQuery/index.ts +0 -1
- package/src/dataHooks/useColSetMembersQuery/useColSetMembersQuery.ts +0 -17
- package/src/dataHooks/useColSetsQuery/index.ts +0 -1
- package/src/dataHooks/useColSetsQuery/useColSetsQuery.ts +0 -39
- package/src/dataHooks/useColTypesQuery/index.ts +0 -1
- package/src/dataHooks/useColTypesQuery/useColTypesQuery.ts +0 -51
- package/src/dataHooks/useColsQuery/index.ts +0 -1
- package/src/dataHooks/useColsQuery/useColsQuery.ts +0 -60
- package/src/dataHooks/useConceptQuery/index.ts +0 -1
- package/src/dataHooks/useConceptQuery/useConceptQuery.ts +0 -52
- package/src/dataHooks/useConceptRelationTypeQuery/index.ts +0 -1
- package/src/dataHooks/useConceptRelationTypeQuery/useConceptRelationTypeQuery.ts +0 -21
- package/src/dataHooks/useConceptSetTypeQuery/index.ts +0 -1
- package/src/dataHooks/useConceptSetTypeQuery/useConceptSetTypeQuery.ts +0 -27
- package/src/dataHooks/useConceptSetsQuery/index.ts +0 -1
- package/src/dataHooks/useConceptSetsQuery/useConceptSetsQuery.ts +0 -38
- package/src/dataHooks/useDataCollectionSetMembersQuery/index.ts +0 -1
- package/src/dataHooks/useDataCollectionSetMembersQuery/useDataCollectionSetMembersQuery.ts +0 -17
- package/src/dataHooks/useDataCollectionsQuery/index.ts +0 -1
- package/src/dataHooks/useDataCollectionsQuery/useDataCollectionsQuery.ts +0 -45
- package/src/dataHooks/useDimTypesQuery/index.ts +0 -1
- package/src/dataHooks/useDimTypesQuery/useDimTypesQuery.ts +0 -26
- package/src/dataHooks/useDimsQuery/index.ts +0 -1
- package/src/dataHooks/useDimsQuery/useDimsQuery.ts +0 -55
- package/src/dataHooks/useDiseasesQuery/index.ts +0 -1
- package/src/dataHooks/useDiseasesQuery/useDiseasesQuery.ts +0 -39
- package/src/dataHooks/useEtiologicalAgentsQuery/index.ts +0 -1
- package/src/dataHooks/useEtiologicalAgentsQuery/useEtiologicalAgentsQuery.ts +0 -39
- package/src/dataHooks/useGeneticDistanceProtocolsQuery/index.ts +0 -1
- package/src/dataHooks/useGeneticDistanceProtocolsQuery/useGeneticDistanceProtocolsQuery.ts +0 -29
- package/src/dataHooks/useIdentifierIssuerOwnOrganizationQuery/index.ts +0 -1
- package/src/dataHooks/useIdentifierIssuerOwnOrganizationQuery/useIdentifierIssuerOwnOrganizationQuery.ts +0 -34
- package/src/dataHooks/useIdentifierIssuerQuery/index.ts +0 -1
- package/src/dataHooks/useIdentifierIssuerQuery/useIdentifierIssuerQuery.ts +0 -42
- package/src/dataHooks/useInviteUserConstraintsQuery/index.ts +0 -1
- package/src/dataHooks/useInviteUserConstraintsQuery/useInviteUserConstraintsQuery.ts +0 -32
- package/src/dataHooks/useOrganizationAccessCasePoliciesQuery/index.ts +0 -1
- package/src/dataHooks/useOrganizationAccessCasePoliciesQuery/useOrganizationAccessCasePoliciesQuery.ts +0 -20
- package/src/dataHooks/useOrganizationAdminPoliciesQuery/index.ts +0 -1
- package/src/dataHooks/useOrganizationAdminPoliciesQuery/useOrganizationAdminPoliciesQuery.ts +0 -60
- package/src/dataHooks/useOrganizationIdentifierIssuerLinksQuery/index.ts +0 -1
- package/src/dataHooks/useOrganizationIdentifierIssuerLinksQuery/useOrganizationIdentifierIssuerLinksQuery.ts +0 -18
- package/src/dataHooks/useOrganizationShareCasePoliciesQuery/index.ts +0 -1
- package/src/dataHooks/useOrganizationShareCasePoliciesQuery/useOrganizationShareCasePoliciesQuery.ts +0 -18
- package/src/dataHooks/useOrganizationsQuery/index.ts +0 -1
- package/src/dataHooks/useOrganizationsQuery/useOrganizationsQuery.ts +0 -40
- package/src/dataHooks/useRefColsQuery/index.ts +0 -1
- package/src/dataHooks/useRefColsQuery/useRefColsQuery.ts +0 -39
- package/src/dataHooks/useRefColsValidationRulesQuery/index.ts +0 -1
- package/src/dataHooks/useRefColsValidationRulesQuery/useRefColsValidationRulesQuery.ts +0 -17
- package/src/dataHooks/useRefDimsQuery/index.ts +0 -1
- package/src/dataHooks/useRefDimsQuery/useRefDimsQuery.ts +0 -39
- package/src/dataHooks/useRegionQuery/index.ts +0 -1
- package/src/dataHooks/useRegionQuery/useRegionQuery.ts +0 -52
- package/src/dataHooks/useRegionRelationTypeQuery/index.ts +0 -1
- package/src/dataHooks/useRegionRelationTypeQuery/useRegionRelationTypeQuery.ts +0 -24
- package/src/dataHooks/useRegionSetsQuery/index.ts +0 -1
- package/src/dataHooks/useRegionSetsQuery/useRegionSetsQuery.ts +0 -39
- package/src/dataHooks/useSequencingProtocolsQuery/index.ts +0 -1
- package/src/dataHooks/useSequencingProtocolsQuery/useSequencingProtocolsQuery.ts +0 -39
- package/src/dataHooks/useTreeAlgorithmCodesQuery/index.ts +0 -1
- package/src/dataHooks/useTreeAlgorithmCodesQuery/useTreeAlgorithmCodesQuery.ts +0 -42
- package/src/dataHooks/useUserAccessCasePoliciesQuery/index.ts +0 -1
- package/src/dataHooks/useUserAccessCasePoliciesQuery/useUserAccessCasePoliciesQuery.ts +0 -18
- package/src/dataHooks/useUserEffectiveRightsQuery/index.ts +0 -1
- package/src/dataHooks/useUserEffectiveRightsQuery/useUserEffectiveRightsQuery.ts +0 -125
- package/src/dataHooks/useUserShareCasePoliciesQuery/index.ts +0 -1
- package/src/dataHooks/useUserShareCasePoliciesQuery/useUserShareCasePoliciesQuery.ts +0 -18
- package/src/dataHooks/useUsersQuery/index.ts +0 -1
- package/src/dataHooks/useUsersQuery/useUsersQuery.ts +0 -43
- package/src/hoc/withDialog/index.ts +0 -1
- package/src/hoc/withDialog/withDialog.tsx +0 -132
- package/src/hoc/withPermissions/index.ts +0 -1
- package/src/hoc/withPermissions/withPermissions.tsx +0 -34
- package/src/hooks/useArray/index.ts +0 -1
- package/src/hooks/useArray/useArray.ts +0 -5
- package/src/hooks/useColumnsMenu/index.ts +0 -1
- package/src/hooks/useColumnsMenu/useColumnsMenu.tsx +0 -152
- package/src/hooks/useCreateMutation/index.ts +0 -1
- package/src/hooks/useCreateMutation/useCreateMutation.ts +0 -103
- package/src/hooks/useDeleteMutation/index.ts +0 -1
- package/src/hooks/useDeleteMutation/useDeleteMutation.ts +0 -92
- package/src/hooks/useDimensions/index.ts +0 -1
- package/src/hooks/useDimensions/useDimensions.ts +0 -82
- package/src/hooks/useEditMutation/index.ts +0 -1
- package/src/hooks/useEditMutation/useEditMutation.ts +0 -112
- package/src/hooks/useInitializeTableStore/index.ts +0 -1
- package/src/hooks/useInitializeTableStore/useInitializeTableStore.ts +0 -41
- package/src/hooks/useIsFormFieldRequiredFromSchema/index.ts +0 -1
- package/src/hooks/useIsFormFieldRequiredFromSchema/useIsFormFieldRequiredFromSchema.ts +0 -32
- package/src/hooks/useItemQuery/index.ts +0 -1
- package/src/hooks/useItemQuery/useItemQuery.ts +0 -82
- package/src/hooks/useOrganizationCasePolicyNameFactory/index.ts +0 -1
- package/src/hooks/useOrganizationCasePolicyNameFactory/useOrganizationCasePolicyNameFactory.ts +0 -31
- package/src/hooks/useQueryMemo/index.ts +0 -1
- package/src/hooks/useQueryMemo/useQueryMemo.ts +0 -24
- package/src/hooks/useScrollbarSize/index.ts +0 -1
- package/src/hooks/useScrollbarSize/useScrollbarSize.ts +0 -23
- package/src/hooks/useSubscribable/index.ts +0 -1
- package/src/hooks/useSubscribable/useSubscribable.ts +0 -30
- package/src/hooks/useUpdateBreadcrumb/index.ts +0 -1
- package/src/hooks/useUpdateBreadcrumb/useUpdateBreadcrumb.ts +0 -21
- package/src/hooks/useUpdateDocumentTitle/index.ts +0 -1
- package/src/hooks/useUpdateDocumentTitle/useUpdateDocumentTitle.ts +0 -10
- package/src/hooks/useUserCasePolicyNameFactory/index.ts +0 -1
- package/src/hooks/useUserCasePolicyNameFactory/useUserCasePolicyNameFactory.ts +0 -37
- package/src/index.ts +0 -301
- package/src/models/admin.ts +0 -7
- package/src/models/auth.ts +0 -14
- package/src/models/caseAccess.ts +0 -21
- package/src/models/config.ts +0 -153
- package/src/models/data.ts +0 -11
- package/src/models/dataHooks.ts +0 -18
- package/src/models/environment.ts +0 -10
- package/src/models/epi.ts +0 -188
- package/src/models/filter.ts +0 -39
- package/src/models/form.ts +0 -74
- package/src/models/generic.ts +0 -4
- package/src/models/nestedMenu.ts +0 -21
- package/src/models/notification.ts +0 -12
- package/src/models/outage.ts +0 -7
- package/src/models/query.ts +0 -75
- package/src/models/reactRouter.ts +0 -36
- package/src/models/table.ts +0 -191
- package/src/models/testId.ts +0 -1
- package/src/models/tree.ts +0 -32
- package/src/pages/AcceptInvitationPage/AcceptInvitationPage.tsx +0 -96
- package/src/pages/AcceptInvitationPage/index.ts +0 -1
- package/src/pages/AdminPage/AdminPage.tsx +0 -164
- package/src/pages/AdminPage/index.ts +0 -1
- package/src/pages/CaseSetStatusAdminPage/CaseSetStatusAdminPage.tsx +0 -98
- package/src/pages/CaseSetStatusAdminPage/index.ts +0 -1
- package/src/pages/CaseTypeSetCategoriesAdminPage/CaseTypeSetCategoriesAdminPage.tsx +0 -108
- package/src/pages/CaseTypeSetCategoriesAdminPage/index.ts +0 -1
- package/src/pages/CaseTypeSetsAdminPage/CaseTypeSetsAdminPage.tsx +0 -200
- package/src/pages/CaseTypeSetsAdminPage/index.ts +0 -1
- package/src/pages/CaseTypesAdminPage/CaseTypesAdminPage.tsx +0 -204
- package/src/pages/CaseTypesAdminPage/index.ts +0 -1
- package/src/pages/CasesDetailPage/CasesDetailPage.tsx +0 -57
- package/src/pages/CasesDetailPage/index.ts +0 -1
- package/src/pages/CasesPage/CasesPage.tsx +0 -338
- package/src/pages/CasesPage/index.ts +0 -1
- package/src/pages/ChooseIdentityProviderPage/ChooseIdentityProviderPage.tsx +0 -166
- package/src/pages/ChooseIdentityProviderPage/index.ts +0 -1
- package/src/pages/ColSetsAdminPage/ColSetsAdminPage.tsx +0 -175
- package/src/pages/ColSetsAdminPage/index.ts +0 -1
- package/src/pages/ColsAdminPage/ColsAdminPage.tsx +0 -400
- package/src/pages/ColsAdminPage/index.ts +0 -1
- package/src/pages/ConceptRelationsAdminPage/ConceptRelationsAdminPage.tsx +0 -134
- package/src/pages/ConceptRelationsAdminPage/index.ts +0 -1
- package/src/pages/ConceptSetsAdminPage/ConceptSetsAdminPage.tsx +0 -167
- package/src/pages/ConceptSetsAdminPage/index.ts +0 -1
- package/src/pages/ConceptsAdminPage/ConceptsAdminPage.tsx +0 -138
- package/src/pages/ConceptsAdminPage/index.ts +0 -1
- package/src/pages/CrudPage/CrudPage.tsx +0 -646
- package/src/pages/CrudPage/CrudPageDeleteDialog.tsx +0 -85
- package/src/pages/CrudPage/CrudPageEditDialog.tsx +0 -165
- package/src/pages/CrudPage/index.ts +0 -1
- package/src/pages/DataCollectionSetsAdminPage/DataCollectionSetsAdminPage.tsx +0 -156
- package/src/pages/DataCollectionSetsAdminPage/index.ts +0 -1
- package/src/pages/DataCollectionVisualizationPage/DataCollectionVisualizationPage.tsx +0 -238
- package/src/pages/DataCollectionVisualizationPage/index.ts +0 -1
- package/src/pages/DataCollectionsAdminPage/DataCollectionsAdminPage.tsx +0 -96
- package/src/pages/DataCollectionsAdminPage/index.ts +0 -1
- package/src/pages/DimsAdminPage/DimsAdminPage.tsx +0 -208
- package/src/pages/DimsAdminPage/index.ts +0 -1
- package/src/pages/DiseasesAdminPage/DiseasesAdminPage.tsx +0 -97
- package/src/pages/DiseasesAdminPage/index.ts +0 -1
- package/src/pages/ErrorPage/ErrorPage.tsx +0 -25
- package/src/pages/ErrorPage/index.ts +0 -1
- package/src/pages/EtiologicalAgentsAdminPage/EtiologicalAgentsAdminPage.tsx +0 -96
- package/src/pages/EtiologicalAgentsAdminPage/index.ts +0 -1
- package/src/pages/EtiologiesAdminPage/EtiologiesAdminPage.tsx +0 -110
- package/src/pages/EtiologiesAdminPage/index.ts +0 -1
- package/src/pages/EventsDetailPage/EventsDetailPage.tsx +0 -67
- package/src/pages/EventsDetailPage/index.ts +0 -1
- package/src/pages/EventsPage/EventsPage.tsx +0 -262
- package/src/pages/EventsPage/index.ts +0 -1
- package/src/pages/HomePage/HomePage.tsx +0 -47
- package/src/pages/HomePage/index.ts +0 -1
- package/src/pages/IdentifierIssuersAdminPage/IdentifierIssuersAdminPage.tsx +0 -106
- package/src/pages/IdentifierIssuersAdminPage/index.ts +0 -1
- package/src/pages/OrganizationAccessCasePoliciesAdminPage/OrganizationAccessCasePoliciesAdminPage.tsx +0 -217
- package/src/pages/OrganizationAccessCasePoliciesAdminPage/index.ts +0 -1
- package/src/pages/OrganizationAdminPoliciesAdminPage/OrganizationAdminPoliciesAdminPage.tsx +0 -119
- package/src/pages/OrganizationAdminPoliciesAdminPage/index.ts +0 -1
- package/src/pages/OrganizationContactsAdminPage/OrganizationContactsAdminPage.tsx +0 -127
- package/src/pages/OrganizationContactsAdminPage/index.ts +0 -1
- package/src/pages/OrganizationShareCasePoliciesAdminPage/OrganizationShareCasePoliciesAdminPage.tsx +0 -183
- package/src/pages/OrganizationShareCasePoliciesAdminPage/index.ts +0 -1
- package/src/pages/OrganizationSitesAdminPage/OrganizationSitesAdminPage.tsx +0 -132
- package/src/pages/OrganizationSitesAdminPage/index.ts +0 -1
- package/src/pages/OrganizationsAdminPage/OrganizationsAdminPage.tsx +0 -188
- package/src/pages/OrganizationsAdminPage/index.ts +0 -1
- package/src/pages/OutagesAdminPage/OutagesAdminPage.tsx +0 -158
- package/src/pages/OutagesAdminPage/index.ts +0 -1
- package/src/pages/PostLoginPage/PostLoginPage.tsx +0 -39
- package/src/pages/PostLoginPage/index.ts +0 -1
- package/src/pages/PostLogoutPage/PostLogoutPage.tsx +0 -44
- package/src/pages/PostLogoutPage/index.ts +0 -1
- package/src/pages/RefColsAdminPage/RefColsAdminPage.tsx +0 -286
- package/src/pages/RefColsAdminPage/index.ts +0 -1
- package/src/pages/RefDimsAdminPage/RefDimsAdminPage.tsx +0 -152
- package/src/pages/RefDimsAdminPage/index.ts +0 -1
- package/src/pages/RegionRelationsAdminPage/RegionRelationsAdminPage.tsx +0 -132
- package/src/pages/RegionRelationsAdminPage/index.ts +0 -1
- package/src/pages/RegionSetShapesAdminPage/RegionSetShapesAdminPage.tsx +0 -132
- package/src/pages/RegionSetShapesAdminPage/index.ts +0 -1
- package/src/pages/RegionSetsAdminPage/RegionSetsAdminPage.tsx +0 -147
- package/src/pages/RegionSetsAdminPage/index.ts +0 -1
- package/src/pages/RegionsAdminPage/RegionsAdminPage.tsx +0 -150
- package/src/pages/RegionsAdminPage/index.ts +0 -1
- package/src/pages/RouterErrorPage/RouterErrorPage.tsx +0 -23
- package/src/pages/RouterErrorPage/index.ts +0 -1
- package/src/pages/TrendsPage/TrendsPage.tsx +0 -17
- package/src/pages/TrendsPage/index.ts +0 -1
- package/src/pages/UploadPage/UploadPage.tsx +0 -40
- package/src/pages/UploadPage/index.ts +0 -1
- package/src/pages/UserAccessCasePoliciesAdminPage/UserAccessCasePoliciesAdminPage.tsx +0 -209
- package/src/pages/UserAccessCasePoliciesAdminPage/index.ts +0 -1
- package/src/pages/UserEffectiveRightsAdminPage/UserEffectiveRightsAdminPage.tsx +0 -331
- package/src/pages/UserEffectiveRightsAdminPage/index.ts +0 -1
- package/src/pages/UserEffectiveRightsTesterAdminPage/UserEffectiveRightsTesterAdminPage.tsx +0 -283
- package/src/pages/UserEffectiveRightsTesterAdminPage/index.ts +0 -1
- package/src/pages/UserInvitationsAdminPage/UserInvitationConsumeDialog.tsx +0 -143
- package/src/pages/UserInvitationsAdminPage/UserInvitationShareDialog.tsx +0 -100
- package/src/pages/UserInvitationsAdminPage/UserInvitationsAdminPage.tsx +0 -218
- package/src/pages/UserInvitationsAdminPage/index.ts +0 -1
- package/src/pages/UserShareCasePoliciesAdminPage/UserShareCasePoliciesAdminPage.tsx +0 -183
- package/src/pages/UserShareCasePoliciesAdminPage/index.ts +0 -1
- package/src/pages/UsersAdminPage/UsersAdminPage.tsx +0 -240
- package/src/pages/UsersAdminPage/index.ts +0 -1
- package/src/routes/adminRoutes.tsx +0 -801
- package/src/routes/index.ts +0 -2
- package/src/routes/routes.tsx +0 -235
- package/src/setup/index.ts +0 -1
- package/src/setup/setup.ts +0 -5
- package/src/setup/yup.ts +0 -203
- package/src/stores/epiDashboardStore/epiDashboardStore.ts +0 -750
- package/src/stores/epiDashboardStore/epiDashboardStoreContext.tsx +0 -6
- package/src/stores/epiDashboardStore/index.ts +0 -2
- package/src/stores/epiUploadStore/epiUploadStore.ts +0 -351
- package/src/stores/epiUploadStore/epiUploadStoreContext.tsx +0 -6
- package/src/stores/epiUploadStore/index.ts +0 -2
- package/src/stores/oidcStore/index.ts +0 -1
- package/src/stores/oidcStore/oidcStore.ts +0 -43
- package/src/stores/outagesStore/index.ts +0 -1
- package/src/stores/outagesStore/outagesStore.ts +0 -31
- package/src/stores/tableStore/TableStoreContext.tsx +0 -6
- package/src/stores/tableStore/TableStoreContextProvider.tsx +0 -20
- package/src/stores/tableStore/index.ts +0 -4
- package/src/stores/tableStore/tableStore.ts +0 -547
- package/src/stores/tableStore/useTableStoreContext.tsx +0 -7
- package/src/stores/userProfileStore/index.ts +0 -1
- package/src/stores/userProfileStore/userProfileStore.ts +0 -112
- package/src/test/integration/lib/render.tsx +0 -33
- package/src/test/integration/tests/integration-example/HelloWorld.test.tsx +0 -14
- package/src/test/integration/tests/integration-example/HelloWorld.tsx +0 -9
- package/src/test/setup-browser.ts +0 -3
- package/src/test/setup-jsdom.ts +0 -6
- package/src/test/setup.ts +0 -9
- package/src/utils/AbacUtil/AbacUtil.ts +0 -21
- package/src/utils/AbacUtil/index.ts +0 -1
- package/src/utils/AxiosUtil/AxiosUtil.test.ts +0 -66
- package/src/utils/AxiosUtil/AxiosUtil.ts +0 -47
- package/src/utils/AxiosUtil/index.ts +0 -1
- package/src/utils/CaseSelectionUtil/CaseSelectionUtil.ts +0 -32
- package/src/utils/CaseSelectionUtil/index.ts +0 -1
- package/src/utils/CaseSetUtil/CaseSetUtil.ts +0 -13
- package/src/utils/CaseSetUtil/index.ts +0 -1
- package/src/utils/CaseTypeUtil/CaseTypeUtil.ts +0 -178
- package/src/utils/CaseTypeUtil/index.ts +0 -1
- package/src/utils/CaseUtil/CaseUtil.ts +0 -401
- package/src/utils/CaseUtil/index.ts +0 -1
- package/src/utils/DashboardUtil/DashboardUtil.ts +0 -46
- package/src/utils/DashboardUtil/index.ts +0 -1
- package/src/utils/DataHookUtil/DataHookUtil.ts +0 -137
- package/src/utils/DataHookUtil/index.ts +0 -1
- package/src/utils/DataSetUtil/DataSetUtil.ts +0 -70
- package/src/utils/DataSetUtil/index.ts +0 -1
- package/src/utils/DataUtil/DataUtil.ts +0 -84
- package/src/utils/DataUtil/index.ts +0 -1
- package/src/utils/DownloadUtil/DownloadUtil.ts +0 -237
- package/src/utils/DownloadUtil/index.ts +0 -1
- package/src/utils/EffectiveRightsUtil/EffectiveRightsUtil.ts +0 -142
- package/src/utils/EffectiveRightsUtil/index.ts +0 -1
- package/src/utils/EpiCurveUtil/EpiCurveUtil.ts +0 -180
- package/src/utils/EpiCurveUtil/index.ts +0 -1
- package/src/utils/EpiFilterUtil/EpiFilterUtil.ts +0 -232
- package/src/utils/EpiFilterUtil/index.ts +0 -1
- package/src/utils/EpiLineListUtil/EpiLineListUtil.ts +0 -15
- package/src/utils/EpiLineListUtil/index.ts +0 -1
- package/src/utils/EpiMapUtil/EpiMapUtil.test.ts +0 -54
- package/src/utils/EpiMapUtil/EpiMapUtil.ts +0 -80
- package/src/utils/EpiMapUtil/index.ts +0 -1
- package/src/utils/EpiTreeUtil/EpiTreeUtil.test.ts +0 -2467
- package/src/utils/EpiTreeUtil/EpiTreeUtil.ts +0 -1055
- package/src/utils/EpiTreeUtil/index.ts +0 -1
- package/src/utils/EpiUploadUtil/EpiUploadUtil.ts +0 -857
- package/src/utils/EpiUploadUtil/index.ts +0 -1
- package/src/utils/FileUtil/FileUtil.ts +0 -22
- package/src/utils/FileUtil/index.ts +0 -1
- package/src/utils/FormUtil/FormUtil.test.ts +0 -36
- package/src/utils/FormUtil/FormUtil.ts +0 -63
- package/src/utils/FormUtil/index.ts +0 -1
- package/src/utils/MenuDataUtil/MenuDataUtil.ts +0 -54
- package/src/utils/MenuDataUtil/index.ts +0 -1
- package/src/utils/NewickUtil/NewickUtil.test.ts +0 -187
- package/src/utils/NewickUtil/NewickUtil.ts +0 -69
- package/src/utils/NewickUtil/index.ts +0 -1
- package/src/utils/NotificationUtil/NotificationUtil.ts +0 -28
- package/src/utils/NotificationUtil/index.ts +0 -1
- package/src/utils/NumberUtil/NumberUtil.test.ts +0 -113
- package/src/utils/NumberUtil/NumberUtil.ts +0 -67
- package/src/utils/NumberUtil/index.ts +0 -1
- package/src/utils/ObjectUtil/ObjectUtil.test.ts +0 -43
- package/src/utils/ObjectUtil/ObjectUtil.ts +0 -28
- package/src/utils/ObjectUtil/index.ts +0 -1
- package/src/utils/OutageUtil/OutageUtil.test.ts +0 -108
- package/src/utils/OutageUtil/OutageUtil.ts +0 -81
- package/src/utils/OutageUtil/index.ts +0 -1
- package/src/utils/QueryUtil/QueryUtil.test.ts +0 -42
- package/src/utils/QueryUtil/QueryUtil.ts +0 -105
- package/src/utils/QueryUtil/index.ts +0 -1
- package/src/utils/StringUtil/StringUtil.test.ts +0 -61
- package/src/utils/StringUtil/StringUtil.ts +0 -91
- package/src/utils/StringUtil/index.ts +0 -1
- package/src/utils/TableUtil/TableUtil.ts +0 -665
- package/src/utils/TableUtil/index.ts +0 -1
- package/src/utils/TestIdUtil/TestIdUtil.test.ts +0 -12
- package/src/utils/TestIdUtil/TestIdUtil.ts +0 -13
- package/src/utils/TestIdUtil/index.ts +0 -1
- package/src/utils/TimeUtil/TimeUtil.ts +0 -38
- package/src/utils/TimeUtil/index.ts +0 -1
- package/src/utils/UserManagerUtil/UserManagerUtil.test.ts +0 -41
- package/src/utils/UserManagerUtil/UserManagerUtil.ts +0 -39
- package/src/utils/UserManagerUtil/index.ts +0 -1
- package/src/utils/ValidationUtil/ValidationUtil.test.ts +0 -61
- package/src/utils/ValidationUtil/ValidationUtil.ts +0 -56
- package/src/utils/ValidationUtil/index.ts +0 -1
- /package/{src → dist}/locale/en.json +0 -0
- /package/{src → dist}/locale/nl.json +0 -0
- /package/{src/@types → dist}/mui.d.ts +0 -0
- /package/{src/@types → dist}/ui.d.ts +0 -0
- /package/{src/@types → dist}/vite-env.d.ts +0 -0
- /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
|
-
});
|