@oneuptime/common 12.0.9 → 12.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/NetworkDevice.ts +44 -0
- package/Models/DatabaseModels/NetworkDeviceLink.ts +116 -0
- package/Models/DatabaseModels/RunnerJob.ts +17 -2
- package/Server/API/LlmProviderAPI.ts +8 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.ts +52 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/NetworkDeviceLabelRuleEngineService.ts +240 -0
- package/Server/Services/NetworkDeviceLinkService.ts +338 -2
- package/Server/Services/NetworkDeviceService.ts +265 -12
- package/Server/Services/RunnerJobService.ts +24 -1
- package/Server/Types/Billing/MeteredPlan/AllMeteredPlans.ts +19 -5
- package/Server/Types/Database/JSONColumnQuery.ts +42 -4
- package/Server/Utils/LLM/LLMService.ts +108 -19
- package/Server/Utils/Monitor/MonitorAlert.ts +22 -115
- package/Server/Utils/Monitor/MonitorIncident.ts +7 -337
- package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +27 -2
- package/Server/Utils/Monitor/SeriesResourceLabels.ts +32 -0
- package/Server/Utils/Monitor/SeriesResourceLinker.ts +469 -0
- package/Server/Utils/SSRFProtection.ts +5 -4
- package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +186 -38
- package/Tests/App/AdminDashboard/AdminUsersBulkAddToProject.test.tsx +955 -0
- package/Tests/App/AdminDashboard/BulkAddUsersToProject.test.ts +778 -0
- package/Tests/App/AdminDashboard/BulkAddUsersToProjectModal.test.tsx +1051 -0
- package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +420 -112
- package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +81 -6
- package/Tests/App/Dashboard/PayAsYouGoConsentGate.test.tsx +543 -0
- package/Tests/App/Dashboard/PayAsYouGoFreePlan.test.ts +125 -0
- package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +612 -0
- package/Tests/App/Dashboard/SloWidgetChartRendering.test.tsx +536 -0
- package/Tests/App/Dashboard/UserElementEmail.test.tsx +447 -0
- package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +284 -0
- package/Tests/Models/AnalyticsModels/CommonModel.test.ts +312 -0
- package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +250 -0
- package/Tests/Server/Infrastructure/Postgres/EpisodeMemberNotifyIndexesMigration.test.ts +46 -18
- package/Tests/Server/Services/NetworkDeviceLabelRuleRun.test.ts +618 -0
- package/Tests/Server/Services/NetworkDeviceLinkParentValidation.test.ts +1043 -0
- package/Tests/Server/Services/NetworkSiteAssignmentRuleRun.test.ts +706 -0
- package/Tests/Server/Services/RunnerJobEnqueue.test.ts +412 -0
- package/Tests/Server/Types/Billing/PayAsYouGoMeteredPricing.test.ts +125 -0
- package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +508 -0
- package/Tests/Server/Utils/AI/LLMServiceRequestPolicy.test.ts +2 -2
- package/Tests/Server/Utils/AI/LLMServiceRetryPolicy.test.ts +450 -0
- package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +556 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +547 -0
- package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +30 -0
- package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +699 -0
- package/Tests/Server/Utils/SeriesResourceLabels.test.ts +55 -0
- package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +668 -0
- package/Tests/Types/API/URLMalformedTolerance.test.ts +301 -0
- package/Tests/Types/API/URLTelScheme.test.ts +261 -0
- package/Tests/Types/API/URLUnsupportedScheme.test.ts +148 -0
- package/Tests/Types/Billing/PayAsYouGoPricing.test.ts +64 -0
- package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +107 -0
- package/Tests/Types/Monitor/MonitorType.test.ts +62 -0
- package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +184 -0
- package/Tests/Types/Runbook/RunbookStepType.test.ts +124 -0
- package/Tests/Types/Workflow/BaseModelComponents.test.ts +17 -0
- package/Tests/UI/Components/Button.test.tsx +279 -0
- package/Tests/UI/Components/Charts/ChartXAxisDataKey.test.tsx +256 -0
- package/Tests/UI/Components/Charts/DataPointUtil.test.ts +323 -0
- package/Tests/UI/Components/DictionaryValuePlaceholderAndSuggestions.test.tsx +595 -0
- package/Tests/UI/Components/Icon.test.tsx +57 -0
- package/Tests/UI/Components/List.test.tsx +20 -3
- package/Tests/UI/Components/LogsSavedViewsDropdown.test.tsx +304 -0
- package/Tests/UI/Components/LogsViewerClearSavedView.test.tsx +179 -0
- package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +349 -1
- package/Tests/UI/Components/Pagination.test.tsx +721 -146
- package/Tests/UI/Components/PaginationIntegration.test.tsx +386 -0
- package/Tests/UI/Components/PaginationUtil.test.ts +417 -0
- package/Tests/UI/Components/TelemetrySavedViewsDropdown.test.tsx +343 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnControl.test.ts +289 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnOperators.test.ts +271 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnRow.test.ts +119 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.test.ts +968 -0
- package/Tests/UI/Components/Workflow/ModelColumnEditor.render.test.tsx +766 -0
- package/Tests/UI/Utils/RunNetworkRule.test.ts +320 -0
- package/Tests/Utils/APIRetryPolicy.test.ts +823 -0
- package/Tests/Utils/Monitor/NetworkDeviceRoleUtil.test.ts +502 -0
- package/Tests/Utils/Monitor/NetworkTopologyDeclaredHierarchy.test.ts +759 -0
- package/Tests/Utils/NetworkAutomation/RuleRunSummary.test.ts +286 -0
- package/Tests/Utils/Schema/BaseSchema.test.ts +651 -0
- package/Types/API/Protocol.ts +9 -0
- package/Types/API/URL.ts +286 -20
- package/Types/Billing/PayAsYouGoPricing.ts +47 -0
- package/Types/CustomField/CustomFieldType.ts +28 -0
- package/Types/Monitor/MonitorType.ts +11 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
- package/Types/NetworkAutomation/RuleRunResult.ts +121 -0
- package/Types/Runbook/RunbookStepType.ts +23 -0
- package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +7 -0
- package/UI/Components/Button/Button.tsx +14 -22
- package/UI/Components/Charts/Area/AreaChart.tsx +4 -2
- package/UI/Components/Charts/Bar/BarChart.tsx +4 -2
- package/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.ts +13 -0
- package/UI/Components/Charts/Line/LineChart.tsx +4 -2
- package/UI/Components/Charts/Types/XAxis/XAxis.ts +6 -0
- package/UI/Components/Charts/Utils/DataPoint.ts +15 -12
- package/UI/Components/Dictionary/Dictionary.tsx +4 -39
- package/UI/Components/Icon/Icon.tsx +1 -1
- package/UI/Components/LogsViewer/LogsViewer.tsx +5 -1
- package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +8 -0
- package/UI/Components/LogsViewer/components/LogsPagination.tsx +29 -91
- package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +2 -0
- package/UI/Components/LogsViewer/components/SavedViewsDropdown.tsx +66 -1
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +55 -0
- package/UI/Components/Pagination/Pagination.tsx +373 -328
- package/UI/Components/Pagination/PaginationUtil.ts +263 -0
- package/UI/Components/Pagination/Screenshots/README.md +15 -0
- package/UI/Components/Pagination/Screenshots/pagination-before-after.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-go-to-page-modal.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-long-list.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-mobile.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-telemetry.png +0 -0
- package/UI/Components/TelemetryViewer/components/SavedViewsDropdown.tsx +66 -1
- package/UI/Components/TelemetryViewer/components/TelemetryPagination.tsx +38 -92
- package/UI/Components/TextArea/TextArea.tsx +3 -0
- package/UI/Components/Workflow/ArgumentsForm.tsx +18 -1
- package/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.tsx +177 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.tsx +243 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnControl.ts +255 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.tsx +178 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnOperators.ts +206 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnRow.ts +157 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.ts +517 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnValueInput.tsx +482 -0
- package/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.tsx +131 -0
- package/UI/Components/Workflow/ColumnEditor/ModelRecordForm.tsx +163 -0
- package/UI/Components/Workflow/ModelColumnEditor.tsx +284 -100
- package/UI/Utils/NetworkAutomation/RunNetworkRule.ts +122 -0
- package/Utils/API.ts +243 -36
- package/Utils/Monitor/NetworkDeviceRoleUtil.ts +45 -0
- package/Utils/Monitor/NetworkTopologyUtil.ts +42 -2
- package/Utils/NetworkAutomation/RuleRunSummary.ts +147 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +45 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js +115 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RunnerJob.js +17 -2
- package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
- package/build/dist/Server/API/LlmProviderAPI.js +9 -1
- package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.js +33 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js +202 -0
- package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceLinkService.js +253 -1
- package/build/dist/Server/Services/NetworkDeviceLinkService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +223 -12
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/RunnerJobService.js +20 -2
- package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
- package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js +12 -5
- package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js.map +1 -1
- package/build/dist/Server/Types/Database/JSONColumnQuery.js +40 -4
- package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -1
- package/build/dist/Server/Utils/LLM/LLMService.js +78 -22
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +22 -98
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +7 -254
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +14 -2
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +27 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +285 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -0
- package/build/dist/Server/Utils/SSRFProtection.js +5 -4
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +128 -35
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
- package/build/dist/Types/API/Protocol.js +9 -0
- package/build/dist/Types/API/Protocol.js.map +1 -1
- package/build/dist/Types/API/URL.js +241 -18
- package/build/dist/Types/API/URL.js.map +1 -1
- package/build/dist/Types/Billing/PayAsYouGoPricing.js +41 -0
- package/build/dist/Types/Billing/PayAsYouGoPricing.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldType.js +26 -0
- package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorType.js +10 -0
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js +39 -0
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -0
- package/build/dist/Types/Runbook/RunbookStepType.js +21 -0
- package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +1 -1
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
- package/build/dist/UI/Components/Button/Button.js +10 -16
- package/build/dist/UI/Components/Button/Button.js.map +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +2 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +2 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js +12 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js.map +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js +2 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js +17 -10
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
- package/build/dist/UI/Components/Dictionary/Dictionary.js +4 -18
- package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +1 -1
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +3 -3
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +5 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js +14 -44
- package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js +20 -2
- package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +42 -0
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/Pagination/Pagination.js +174 -199
- package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
- package/build/dist/UI/Components/Pagination/PaginationUtil.js +160 -0
- package/build/dist/UI/Components/Pagination/PaginationUtil.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js +20 -2
- package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js +23 -47
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js.map +1 -1
- package/build/dist/UI/Components/TextArea/TextArea.js +1 -1
- package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js +14 -2
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.js +86 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.js +119 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnControl.js +206 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnControl.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.js +57 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnOperators.js +150 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnOperators.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRow.js +67 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRow.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.js +309 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnValueInput.js +204 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnValueInput.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js +66 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelRecordForm.js +83 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelRecordForm.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +187 -68
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -1
- package/build/dist/UI/Utils/NetworkAutomation/RunNetworkRule.js +75 -0
- package/build/dist/UI/Utils/NetworkAutomation/RunNetworkRule.js.map +1 -0
- package/build/dist/Utils/API.js +175 -34
- package/build/dist/Utils/API.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +37 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +25 -3
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkAutomation/RuleRunSummary.js +78 -0
- package/build/dist/Utils/NetworkAutomation/RuleRunSummary.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import SavedViewsDropdown, {
|
|
2
|
+
SavedViewsDropdownProps,
|
|
3
|
+
} from "../../../UI/Components/TelemetryViewer/components/SavedViewsDropdown";
|
|
4
|
+
import { SavedViewOption } from "../../../UI/Components/TelemetryViewer/types";
|
|
5
|
+
import "@testing-library/jest-dom";
|
|
6
|
+
import {
|
|
7
|
+
cleanup,
|
|
8
|
+
fireEvent,
|
|
9
|
+
render,
|
|
10
|
+
screen,
|
|
11
|
+
RenderResult,
|
|
12
|
+
} from "@testing-library/react";
|
|
13
|
+
import React from "react";
|
|
14
|
+
import { afterEach, describe, expect, jest, test } from "@jest/globals";
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* Applying a saved view used to be a one-way door. The dropdown drew a
|
|
18
|
+
* checkmark next to the active view, but clicking that row just re-applied it
|
|
19
|
+
* and there was no other control that meant "none" — so anyone who picked a
|
|
20
|
+
* view on the Traces explorer was stuck inside it for the rest of the session
|
|
21
|
+
* (github.com/OneUptime/oneuptime/issues/3189).
|
|
22
|
+
*
|
|
23
|
+
* Two affordances undo it, and both are pinned here: the checkmark behaves
|
|
24
|
+
* like the checkbox it looks like, and an explicit "Clear view" row spells the
|
|
25
|
+
* same thing out for anyone who never thinks to click a selected item twice.
|
|
26
|
+
*
|
|
27
|
+
* Nothing about deselection is allowed to leak into hosts that do not support
|
|
28
|
+
* it — a dropdown rendered without onClear must behave exactly as it did
|
|
29
|
+
* before, which the back-compat block below holds to.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const VIEWS: Array<SavedViewOption> = [
|
|
33
|
+
{ id: "wb-ims", name: "WB - IMS" },
|
|
34
|
+
{ id: "checkout", name: "Errors in checkout", isDefault: true },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const CLEAR_LABEL: string = "Clear view";
|
|
38
|
+
// Only ever rendered inside the open panel, so it doubles as an "is open" probe.
|
|
39
|
+
const MENU_PROBE: string = "+ Save Current View";
|
|
40
|
+
|
|
41
|
+
interface Handlers {
|
|
42
|
+
onSelect: (viewId: string) => void;
|
|
43
|
+
onClear: () => void;
|
|
44
|
+
onCreate: () => void;
|
|
45
|
+
onEdit: (viewId: string) => void;
|
|
46
|
+
onDelete: (viewId: string) => void;
|
|
47
|
+
onUpdateCurrent: () => void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function makeHandlers(): Handlers {
|
|
51
|
+
return {
|
|
52
|
+
onSelect: jest.fn(),
|
|
53
|
+
onClear: jest.fn(),
|
|
54
|
+
onCreate: jest.fn(),
|
|
55
|
+
onEdit: jest.fn(),
|
|
56
|
+
onDelete: jest.fn(),
|
|
57
|
+
onUpdateCurrent: jest.fn(),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function renderDropdown(
|
|
62
|
+
overrides: Partial<SavedViewsDropdownProps> = {},
|
|
63
|
+
handlers: Handlers = makeHandlers(),
|
|
64
|
+
): { handlers: Handlers; result: RenderResult } {
|
|
65
|
+
const props: SavedViewsDropdownProps = {
|
|
66
|
+
savedViews: VIEWS,
|
|
67
|
+
selectedSavedViewId: null,
|
|
68
|
+
...handlers,
|
|
69
|
+
...overrides,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return { handlers, result: render(<SavedViewsDropdown {...props} />) };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function rerenderDropdown(
|
|
76
|
+
result: RenderResult,
|
|
77
|
+
handlers: Handlers,
|
|
78
|
+
overrides: Partial<SavedViewsDropdownProps>,
|
|
79
|
+
): void {
|
|
80
|
+
const props: SavedViewsDropdownProps = {
|
|
81
|
+
savedViews: VIEWS,
|
|
82
|
+
selectedSavedViewId: null,
|
|
83
|
+
...handlers,
|
|
84
|
+
...overrides,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
result.rerender(<SavedViewsDropdown {...props} />);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function trigger(): HTMLElement {
|
|
91
|
+
return screen.getAllByRole("button")[0]!;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function openMenu(): void {
|
|
95
|
+
fireEvent.click(trigger());
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isMenuOpen(): boolean {
|
|
99
|
+
return screen.queryByText(MENU_PROBE) !== null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function rowFor(name: string): HTMLElement {
|
|
103
|
+
return screen.getByRole("button", { name: name });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
describe("SavedViewsDropdown — clearing the applied view", () => {
|
|
107
|
+
afterEach(() => {
|
|
108
|
+
cleanup();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("clicking the applied view clears it instead of re-applying it", () => {
|
|
112
|
+
const { handlers } = renderDropdown({ selectedSavedViewId: "wb-ims" });
|
|
113
|
+
|
|
114
|
+
openMenu();
|
|
115
|
+
fireEvent.click(rowFor("WB - IMS"));
|
|
116
|
+
|
|
117
|
+
expect(handlers.onClear).toHaveBeenCalledTimes(1);
|
|
118
|
+
// Re-applying what is already applied is the behaviour being replaced.
|
|
119
|
+
expect(handlers.onSelect).not.toHaveBeenCalled();
|
|
120
|
+
expect(isMenuOpen()).toBe(false);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("the explicit Clear view row clears and closes the menu", () => {
|
|
124
|
+
const { handlers } = renderDropdown({ selectedSavedViewId: "wb-ims" });
|
|
125
|
+
|
|
126
|
+
openMenu();
|
|
127
|
+
fireEvent.click(rowFor(CLEAR_LABEL));
|
|
128
|
+
|
|
129
|
+
expect(handlers.onClear).toHaveBeenCalledTimes(1);
|
|
130
|
+
expect(handlers.onSelect).not.toHaveBeenCalled();
|
|
131
|
+
expect(isMenuOpen()).toBe(false);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("the Clear view row is offered only while a view is applied", () => {
|
|
135
|
+
const { handlers, result } = renderDropdown({ selectedSavedViewId: null });
|
|
136
|
+
|
|
137
|
+
openMenu();
|
|
138
|
+
// Nothing is applied, so there is nothing the row could undo.
|
|
139
|
+
expect(screen.queryByText(CLEAR_LABEL)).toBeNull();
|
|
140
|
+
|
|
141
|
+
rerenderDropdown(result, handlers, { selectedSavedViewId: "checkout" });
|
|
142
|
+
|
|
143
|
+
expect(screen.getByText(CLEAR_LABEL)).toBeInTheDocument();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("a selection pointing at a view that no longer exists offers no clear row", () => {
|
|
147
|
+
/*
|
|
148
|
+
* The host nulls a stale id in an effect, so for one render the dropdown
|
|
149
|
+
* holds an id matching nothing. Offering "Clear view" then would be a
|
|
150
|
+
* button that undoes a view the user cannot see.
|
|
151
|
+
*/
|
|
152
|
+
renderDropdown({ selectedSavedViewId: "deleted-view" });
|
|
153
|
+
|
|
154
|
+
openMenu();
|
|
155
|
+
|
|
156
|
+
expect(screen.queryByText(CLEAR_LABEL)).toBeNull();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("clicking a different view still applies it", () => {
|
|
160
|
+
const { handlers } = renderDropdown({ selectedSavedViewId: "wb-ims" });
|
|
161
|
+
|
|
162
|
+
openMenu();
|
|
163
|
+
fireEvent.click(rowFor("Errors in checkout (default)"));
|
|
164
|
+
|
|
165
|
+
expect(handlers.onSelect).toHaveBeenCalledTimes(1);
|
|
166
|
+
expect(handlers.onSelect).toHaveBeenCalledWith("checkout");
|
|
167
|
+
expect(handlers.onClear).not.toHaveBeenCalled();
|
|
168
|
+
expect(isMenuOpen()).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("the trigger drops back to the generic label once the view is cleared", () => {
|
|
172
|
+
const { handlers, result } = renderDropdown({
|
|
173
|
+
selectedSavedViewId: "wb-ims",
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
expect(trigger()).toHaveTextContent("WB - IMS");
|
|
177
|
+
|
|
178
|
+
// What the host renders after onClear runs.
|
|
179
|
+
rerenderDropdown(result, handlers, { selectedSavedViewId: null });
|
|
180
|
+
|
|
181
|
+
expect(trigger()).toHaveTextContent("Saved Views");
|
|
182
|
+
expect(trigger()).not.toHaveTextContent("WB - IMS");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("the cleared menu has nothing left to clear", () => {
|
|
186
|
+
const { handlers, result } = renderDropdown({
|
|
187
|
+
selectedSavedViewId: "wb-ims",
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
openMenu();
|
|
191
|
+
fireEvent.click(rowFor(CLEAR_LABEL));
|
|
192
|
+
|
|
193
|
+
rerenderDropdown(result, handlers, { selectedSavedViewId: null });
|
|
194
|
+
openMenu();
|
|
195
|
+
|
|
196
|
+
expect(screen.queryByText(CLEAR_LABEL)).toBeNull();
|
|
197
|
+
expect(handlers.onClear).toHaveBeenCalledTimes(1);
|
|
198
|
+
// Every row is now selectable again rather than being a toggle-off.
|
|
199
|
+
expect(rowFor("WB - IMS")).toHaveAttribute("aria-pressed", "false");
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("clearing then picking the same view again re-applies it", () => {
|
|
203
|
+
const { handlers, result } = renderDropdown({
|
|
204
|
+
selectedSavedViewId: "wb-ims",
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
openMenu();
|
|
208
|
+
fireEvent.click(rowFor("WB - IMS"));
|
|
209
|
+
|
|
210
|
+
rerenderDropdown(result, handlers, { selectedSavedViewId: null });
|
|
211
|
+
openMenu();
|
|
212
|
+
fireEvent.click(rowFor("WB - IMS"));
|
|
213
|
+
|
|
214
|
+
expect(handlers.onClear).toHaveBeenCalledTimes(1);
|
|
215
|
+
expect(handlers.onSelect).toHaveBeenCalledTimes(1);
|
|
216
|
+
expect(handlers.onSelect).toHaveBeenCalledWith("wb-ims");
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe("SavedViewsDropdown — the row reports its own state", () => {
|
|
221
|
+
afterEach(() => {
|
|
222
|
+
cleanup();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("aria-pressed tracks which view is applied", () => {
|
|
226
|
+
renderDropdown({ selectedSavedViewId: "wb-ims" });
|
|
227
|
+
|
|
228
|
+
openMenu();
|
|
229
|
+
|
|
230
|
+
expect(rowFor("WB - IMS")).toHaveAttribute("aria-pressed", "true");
|
|
231
|
+
expect(rowFor("Errors in checkout (default)")).toHaveAttribute(
|
|
232
|
+
"aria-pressed",
|
|
233
|
+
"false",
|
|
234
|
+
);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("the accessible name is the view name, not the checkmark glyph", () => {
|
|
238
|
+
renderDropdown({ selectedSavedViewId: "wb-ims" });
|
|
239
|
+
|
|
240
|
+
openMenu();
|
|
241
|
+
|
|
242
|
+
// "✓ WB - IMS" would be the computed name if the glyph leaked into it.
|
|
243
|
+
expect(rowFor("WB - IMS")).toBeInTheDocument();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("the default badge is announced as a word, not dropped", () => {
|
|
247
|
+
/*
|
|
248
|
+
* The badge is inside the button, so an aria-label of just the view name
|
|
249
|
+
* would hide "default" from a screen reader while sighted users see it.
|
|
250
|
+
*/
|
|
251
|
+
renderDropdown({ selectedSavedViewId: null });
|
|
252
|
+
|
|
253
|
+
openMenu();
|
|
254
|
+
|
|
255
|
+
expect(rowFor("Errors in checkout (default)")).toBeInTheDocument();
|
|
256
|
+
expect(
|
|
257
|
+
screen.queryByRole("button", { name: "Errors in checkout" }),
|
|
258
|
+
).toBeNull();
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
describe("SavedViewsDropdown — hosts that do not support clearing", () => {
|
|
263
|
+
afterEach(() => {
|
|
264
|
+
cleanup();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test("no Clear view row is rendered without onClear", () => {
|
|
268
|
+
renderDropdown({ selectedSavedViewId: "wb-ims", onClear: undefined });
|
|
269
|
+
|
|
270
|
+
openMenu();
|
|
271
|
+
|
|
272
|
+
expect(screen.queryByText(CLEAR_LABEL)).toBeNull();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test("clicking the applied view falls back to re-applying it", () => {
|
|
276
|
+
const { handlers } = renderDropdown({
|
|
277
|
+
selectedSavedViewId: "wb-ims",
|
|
278
|
+
onClear: undefined,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
openMenu();
|
|
282
|
+
fireEvent.click(rowFor("WB - IMS"));
|
|
283
|
+
|
|
284
|
+
expect(handlers.onSelect).toHaveBeenCalledWith("wb-ims");
|
|
285
|
+
expect(handlers.onClear).not.toHaveBeenCalled();
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
describe("SavedViewsDropdown — the rest of the menu is unaffected", () => {
|
|
290
|
+
afterEach(() => {
|
|
291
|
+
cleanup();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("Edit on the applied row edits and does not clear", () => {
|
|
295
|
+
const { handlers } = renderDropdown({ selectedSavedViewId: "wb-ims" });
|
|
296
|
+
|
|
297
|
+
openMenu();
|
|
298
|
+
fireEvent.click(screen.getAllByRole("button", { name: "Edit" })[0]!);
|
|
299
|
+
|
|
300
|
+
expect(handlers.onEdit).toHaveBeenCalledWith("wb-ims");
|
|
301
|
+
expect(handlers.onClear).not.toHaveBeenCalled();
|
|
302
|
+
expect(handlers.onSelect).not.toHaveBeenCalled();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test("Delete on the applied row deletes and does not clear", () => {
|
|
306
|
+
const { handlers } = renderDropdown({ selectedSavedViewId: "wb-ims" });
|
|
307
|
+
|
|
308
|
+
openMenu();
|
|
309
|
+
fireEvent.click(screen.getAllByRole("button", { name: "Delete" })[0]!);
|
|
310
|
+
|
|
311
|
+
expect(handlers.onDelete).toHaveBeenCalledWith("wb-ims");
|
|
312
|
+
expect(handlers.onClear).not.toHaveBeenCalled();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test("Update on the applied row saves over it and does not clear", () => {
|
|
316
|
+
const { handlers } = renderDropdown({ selectedSavedViewId: "wb-ims" });
|
|
317
|
+
|
|
318
|
+
openMenu();
|
|
319
|
+
fireEvent.click(rowFor("Update"));
|
|
320
|
+
|
|
321
|
+
expect(handlers.onUpdateCurrent).toHaveBeenCalledTimes(1);
|
|
322
|
+
expect(handlers.onClear).not.toHaveBeenCalled();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test("Save Current View still creates and does not clear", () => {
|
|
326
|
+
const { handlers } = renderDropdown({ selectedSavedViewId: "wb-ims" });
|
|
327
|
+
|
|
328
|
+
openMenu();
|
|
329
|
+
fireEvent.click(rowFor(MENU_PROBE));
|
|
330
|
+
|
|
331
|
+
expect(handlers.onCreate).toHaveBeenCalledTimes(1);
|
|
332
|
+
expect(handlers.onClear).not.toHaveBeenCalled();
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test("an empty view list still renders its empty state and no clear row", () => {
|
|
336
|
+
renderDropdown({ savedViews: [], selectedSavedViewId: null });
|
|
337
|
+
|
|
338
|
+
openMenu();
|
|
339
|
+
|
|
340
|
+
expect(screen.getByText("No saved views yet.")).toBeInTheDocument();
|
|
341
|
+
expect(screen.queryByText(CLEAR_LABEL)).toBeNull();
|
|
342
|
+
});
|
|
343
|
+
});
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Which control a column gets, and what values that control can hold without
|
|
3
|
+
* changing them.
|
|
4
|
+
*
|
|
5
|
+
* Every `type` here is a TableColumnType *value*, because that is what the
|
|
6
|
+
* /model-schema endpoint puts on the wire. Several values differ sharply from
|
|
7
|
+
* the member name that reads like the obvious literal — ShortText is "Text",
|
|
8
|
+
* ObjectID is "Object ID", LongURL is "URL", JavaScript is "JavaSCript" — and
|
|
9
|
+
* a mapping written against the names would classify the two most-declared
|
|
10
|
+
* column types as unknown while every test still passed.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import TableColumnType from "../../../../../Types/Database/TableColumnType";
|
|
14
|
+
import {
|
|
15
|
+
columnTypeLabel,
|
|
16
|
+
controlForColumn,
|
|
17
|
+
isOfferableColumn,
|
|
18
|
+
literalFitsControl,
|
|
19
|
+
} from "../../../../../UI/Components/Workflow/ColumnEditor/ColumnControl";
|
|
20
|
+
import { ModelColumnControl } from "../../../../../UI/Components/Workflow/ColumnEditor/ColumnRow";
|
|
21
|
+
import { ModelSchemaColumn } from "../../../../../UI/Components/Workflow/ModelSchema";
|
|
22
|
+
import { describe, expect, test } from "@jest/globals";
|
|
23
|
+
|
|
24
|
+
type MakeColumnFunction = (
|
|
25
|
+
overrides: Partial<ModelSchemaColumn>,
|
|
26
|
+
) => ModelSchemaColumn;
|
|
27
|
+
|
|
28
|
+
const makeColumn: MakeColumnFunction = (
|
|
29
|
+
overrides: Partial<ModelSchemaColumn>,
|
|
30
|
+
): ModelSchemaColumn => {
|
|
31
|
+
return {
|
|
32
|
+
id: "name",
|
|
33
|
+
title: "Name",
|
|
34
|
+
type: TableColumnType.ShortText,
|
|
35
|
+
isRelation: false,
|
|
36
|
+
...overrides,
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
describe("controlForColumn — totality", () => {
|
|
41
|
+
test("every column type the database can declare gets a control", () => {
|
|
42
|
+
const allTypes: Array<string> = Object.values(TableColumnType);
|
|
43
|
+
|
|
44
|
+
// Guards against the enum growing and a new type silently becoming a text box.
|
|
45
|
+
expect(allTypes.length).toBeGreaterThan(0);
|
|
46
|
+
|
|
47
|
+
for (const type of allTypes) {
|
|
48
|
+
const control: ModelColumnControl = controlForColumn(
|
|
49
|
+
makeColumn({ type: type }),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
expect(Object.values(ModelColumnControl)).toContain(control);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("a column the schema does not describe still gets a usable control", () => {
|
|
57
|
+
/*
|
|
58
|
+
* The runner accepts column names this endpoint never surfaces, so an
|
|
59
|
+
* unknown key is typed as text rather than locked as unsupported.
|
|
60
|
+
*/
|
|
61
|
+
expect(controlForColumn(undefined)).toBe(ModelColumnControl.Text);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("controlForColumn — the wire values, not the member names", () => {
|
|
66
|
+
test('ShortText, whose value is "Text", is a text box', () => {
|
|
67
|
+
expect(
|
|
68
|
+
controlForColumn(makeColumn({ type: TableColumnType.ShortText })),
|
|
69
|
+
).toBe(ModelColumnControl.Text);
|
|
70
|
+
expect(TableColumnType.ShortText).toBe("Text");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('ObjectID, whose value is "Object ID", is an ID box', () => {
|
|
74
|
+
expect(
|
|
75
|
+
controlForColumn(makeColumn({ type: TableColumnType.ObjectID })),
|
|
76
|
+
).toBe(ModelColumnControl.ObjectId);
|
|
77
|
+
expect(TableColumnType.ObjectID).toBe("Object ID");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('LongURL, whose value is "URL", is a text box', () => {
|
|
81
|
+
expect(
|
|
82
|
+
controlForColumn(makeColumn({ type: TableColumnType.LongURL })),
|
|
83
|
+
).toBe(ModelColumnControl.Text);
|
|
84
|
+
expect(TableColumnType.LongURL).toBe("URL");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("JavaScript, whose value carries a capital S typo, is long text", () => {
|
|
88
|
+
expect(
|
|
89
|
+
controlForColumn(makeColumn({ type: TableColumnType.JavaScript })),
|
|
90
|
+
).toBe(ModelColumnControl.LongText);
|
|
91
|
+
expect(TableColumnType.JavaScript).toBe("JavaSCript");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("every numeric flavour is a number box", () => {
|
|
95
|
+
const numericTypes: Array<string> = [
|
|
96
|
+
TableColumnType.Number,
|
|
97
|
+
TableColumnType.SmallNumber,
|
|
98
|
+
TableColumnType.BigNumber,
|
|
99
|
+
TableColumnType.PositiveNumber,
|
|
100
|
+
TableColumnType.SmallPositiveNumber,
|
|
101
|
+
TableColumnType.BigPositiveNumber,
|
|
102
|
+
TableColumnType.Port,
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
for (const type of numericTypes) {
|
|
106
|
+
expect(controlForColumn(makeColumn({ type: type }))).toBe(
|
|
107
|
+
ModelColumnControl.Number,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("boolean, date and color each get their own control", () => {
|
|
113
|
+
expect(
|
|
114
|
+
controlForColumn(makeColumn({ type: TableColumnType.Boolean })),
|
|
115
|
+
).toBe(ModelColumnControl.Boolean);
|
|
116
|
+
expect(controlForColumn(makeColumn({ type: TableColumnType.Date }))).toBe(
|
|
117
|
+
ModelColumnControl.Date,
|
|
118
|
+
);
|
|
119
|
+
expect(controlForColumn(makeColumn({ type: TableColumnType.Color }))).toBe(
|
|
120
|
+
ModelColumnControl.Color,
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("the long-form text types share one textarea", () => {
|
|
125
|
+
const longTypes: Array<string> = [
|
|
126
|
+
TableColumnType.LongText,
|
|
127
|
+
TableColumnType.VeryLongText,
|
|
128
|
+
TableColumnType.Description,
|
|
129
|
+
TableColumnType.Markdown,
|
|
130
|
+
TableColumnType.HTML,
|
|
131
|
+
TableColumnType.CSS,
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
for (const type of longTypes) {
|
|
135
|
+
expect(controlForColumn(makeColumn({ type: type }))).toBe(
|
|
136
|
+
ModelColumnControl.LongText,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("blobs, nested structures and relations have no row that could hold them", () => {
|
|
142
|
+
const unsupportedTypes: Array<string> = [
|
|
143
|
+
TableColumnType.Entity,
|
|
144
|
+
TableColumnType.EntityArray,
|
|
145
|
+
TableColumnType.JSON,
|
|
146
|
+
TableColumnType.Array,
|
|
147
|
+
TableColumnType.Buffer,
|
|
148
|
+
TableColumnType.File,
|
|
149
|
+
TableColumnType.MonitorSteps,
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
for (const type of unsupportedTypes) {
|
|
153
|
+
expect(controlForColumn(makeColumn({ type: type }))).toBe(
|
|
154
|
+
ModelColumnControl.Unsupported,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("a relation is unsupported whatever its declared type says", () => {
|
|
160
|
+
expect(
|
|
161
|
+
controlForColumn(
|
|
162
|
+
makeColumn({ type: TableColumnType.ShortText, isRelation: true }),
|
|
163
|
+
),
|
|
164
|
+
).toBe(ModelColumnControl.Unsupported);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("a single-value enum column is a text box, not a blob", () => {
|
|
168
|
+
/*
|
|
169
|
+
* Permission, MonitorType, WorkflowStatus and CustomFieldType are stored as
|
|
170
|
+
* short text with one enum value in them - TeamPermission.permission ships
|
|
171
|
+
* example: "ProjectOwner".
|
|
172
|
+
*/
|
|
173
|
+
const enumTypes: Array<string> = [
|
|
174
|
+
TableColumnType.Permission,
|
|
175
|
+
TableColumnType.MonitorType,
|
|
176
|
+
TableColumnType.WorkflowStatus,
|
|
177
|
+
TableColumnType.CustomFieldType,
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
for (const type of enumTypes) {
|
|
181
|
+
expect(controlForColumn(makeColumn({ type: type }))).toBe(
|
|
182
|
+
ModelColumnControl.Text,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe("literalFitsControl", () => {
|
|
189
|
+
test("an untouched value fits every control", () => {
|
|
190
|
+
for (const control of Object.values(ModelColumnControl)) {
|
|
191
|
+
expect(literalFitsControl(control, "")).toBe(true);
|
|
192
|
+
expect(literalFitsControl(control, undefined)).toBe(true);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("null does not fit, because an empty box cannot say null", () => {
|
|
197
|
+
/*
|
|
198
|
+
* This is the whole reason stored nulls survive a round trip: an empty
|
|
199
|
+
* control emits "", which a record drops, so a null has to be kept raw.
|
|
200
|
+
*/
|
|
201
|
+
expect(literalFitsControl(ModelColumnControl.Text, null)).toBe(false);
|
|
202
|
+
expect(literalFitsControl(ModelColumnControl.Number, null)).toBe(false);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("a number fits a number box and the string spelling of one does not", () => {
|
|
206
|
+
expect(literalFitsControl(ModelColumnControl.Number, 8080)).toBe(true);
|
|
207
|
+
expect(literalFitsControl(ModelColumnControl.Number, "8080")).toBe(false);
|
|
208
|
+
expect(literalFitsControl(ModelColumnControl.Number, "08080")).toBe(false);
|
|
209
|
+
expect(literalFitsControl(ModelColumnControl.Number, NaN)).toBe(false);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("a boolean fits a boolean control and its string spelling does not", () => {
|
|
213
|
+
expect(literalFitsControl(ModelColumnControl.Boolean, true)).toBe(true);
|
|
214
|
+
expect(literalFitsControl(ModelColumnControl.Boolean, false)).toBe(true);
|
|
215
|
+
expect(literalFitsControl(ModelColumnControl.Boolean, "true")).toBe(false);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("a date fits only as a parseable string", () => {
|
|
219
|
+
expect(
|
|
220
|
+
literalFitsControl(ModelColumnControl.Date, "2026-08-15T00:00:00.000Z"),
|
|
221
|
+
).toBe(true);
|
|
222
|
+
expect(literalFitsControl(ModelColumnControl.Date, "not a date")).toBe(
|
|
223
|
+
false,
|
|
224
|
+
);
|
|
225
|
+
expect(literalFitsControl(ModelColumnControl.Date, 1786818975367)).toBe(
|
|
226
|
+
false,
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("a text control takes strings and nothing else", () => {
|
|
231
|
+
expect(literalFitsControl(ModelColumnControl.Text, "hello")).toBe(true);
|
|
232
|
+
expect(literalFitsControl(ModelColumnControl.Text, 12)).toBe(false);
|
|
233
|
+
expect(literalFitsControl(ModelColumnControl.Text, true)).toBe(false);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
describe("isOfferableColumn", () => {
|
|
238
|
+
test("an ordinary scalar column is offered", () => {
|
|
239
|
+
expect(isOfferableColumn(makeColumn({}))).toBe(true);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("a relation is not offered — its scalar ID sibling is", () => {
|
|
243
|
+
expect(isOfferableColumn(makeColumn({ isRelation: true }))).toBe(false);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("the project column is not offered, because the runner stamps it", () => {
|
|
247
|
+
expect(isOfferableColumn(makeColumn({ isTenantColumn: true }))).toBe(false);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("a column no row can hold is not offered", () => {
|
|
251
|
+
expect(
|
|
252
|
+
isOfferableColumn(makeColumn({ type: TableColumnType.MonitorSteps })),
|
|
253
|
+
).toBe(false);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe("columnTypeLabel", () => {
|
|
258
|
+
test("says what kind of value the field wants, in words", () => {
|
|
259
|
+
expect(columnTypeLabel(makeColumn({ type: TableColumnType.Boolean }))).toBe(
|
|
260
|
+
"True or false",
|
|
261
|
+
);
|
|
262
|
+
expect(columnTypeLabel(makeColumn({ type: TableColumnType.Date }))).toBe(
|
|
263
|
+
"Date and time",
|
|
264
|
+
);
|
|
265
|
+
expect(
|
|
266
|
+
columnTypeLabel(makeColumn({ type: TableColumnType.ObjectID })),
|
|
267
|
+
).toBe("ID");
|
|
268
|
+
expect(columnTypeLabel(makeColumn({ type: TableColumnType.Port }))).toBe(
|
|
269
|
+
"Number",
|
|
270
|
+
);
|
|
271
|
+
expect(
|
|
272
|
+
columnTypeLabel(makeColumn({ type: TableColumnType.ShortText })),
|
|
273
|
+
).toBe("Text");
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("a relation says so rather than naming its inner type", () => {
|
|
277
|
+
expect(columnTypeLabel(makeColumn({ isRelation: true }))).toBe("Relation");
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test("an unsupported column names its raw type, which is what has to be written by hand", () => {
|
|
281
|
+
expect(
|
|
282
|
+
columnTypeLabel(makeColumn({ type: TableColumnType.MonitorSteps })),
|
|
283
|
+
).toBe(TableColumnType.MonitorSteps);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("an unknown column is labelled unknown rather than guessed at", () => {
|
|
287
|
+
expect(columnTypeLabel(undefined)).toBe("Unknown");
|
|
288
|
+
});
|
|
289
|
+
});
|