@rancher/shell 3.0.12-rc.3 → 3.0.12-rc.4
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/assets/styles/global/_layout.scss +4 -0
- package/assets/translations/en-us.yaml +144 -41
- package/assets/translations/zh-hans.yaml +1 -7
- package/chart/monitoring/ClusterSelector.vue +0 -21
- package/chart/monitoring/prometheus/index.vue +6 -3
- package/components/CruResource.vue +161 -14
- package/components/ExplorerMembers.vue +8 -4
- package/components/ExplorerProjectsNamespaces.vue +10 -6
- package/components/GrowlManager.vue +4 -0
- package/components/MgmtNodeList.vue +184 -0
- package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +90 -1
- package/components/Resource/Detail/Card/StateCard/composables.ts +57 -87
- package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +61 -0
- package/components/Resource/Detail/Card/StatusCard/index.vue +61 -15
- package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +2 -0
- package/components/Resource/Detail/Metadata/KeyValue.vue +5 -2
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +2 -6
- package/components/ResourceDetail/index.vue +1 -1
- package/components/ResourceList/Masthead.vue +7 -1
- package/components/ResourceList/index.vue +82 -1
- package/components/RichTranslation.vue +5 -2
- package/components/Setting.vue +1 -0
- package/components/SubtleLink.vue +31 -6
- package/components/Tabbed/Tab.vue +29 -3
- package/components/Tabbed/index.vue +25 -3
- package/components/TableOfContents/TableOfContents.vue +109 -0
- package/components/TableOfContents/composables.ts +258 -0
- package/components/Window/ContainerShell.vue +21 -11
- package/components/Window/__tests__/ContainerShell.test.ts +107 -37
- package/components/Wizard.vue +9 -4
- package/components/fleet/AppCoChartGrid.vue +401 -0
- package/components/fleet/AppCoEmptyState.vue +127 -0
- package/components/fleet/AppCoPageHeader.vue +119 -0
- package/components/fleet/AppCoVersionSelect.vue +70 -0
- package/components/fleet/FleetClusterTargets/ClusterSelectionFields.vue +217 -0
- package/components/fleet/FleetClusterTargets/TargetsList.vue +123 -35
- package/components/fleet/FleetClusterTargets/index.vue +189 -146
- package/components/fleet/FleetIntro.vue +7 -3
- package/components/fleet/FleetNoWorkspaces.vue +7 -3
- package/components/fleet/FleetSecretSelector.vue +5 -3
- package/components/fleet/FleetValuesFrom.vue +8 -2
- package/components/fleet/GitRepoTargetTab.vue +0 -2
- package/components/fleet/HelmOpAdvancedTab.vue +19 -53
- package/components/fleet/HelmOpAppCoConfigTab.vue +593 -0
- package/components/fleet/HelmOpAppCoResourcesSection.vue +162 -0
- package/components/fleet/HelmOpResourcesSection.vue +82 -0
- package/components/fleet/HelmOpTargetOptionsSection.vue +89 -0
- package/components/fleet/HelmOpTargetTab.vue +64 -60
- package/components/fleet/HelmOpValuesTab.vue +129 -105
- package/components/fleet/__tests__/AppCoEmptyState.test.ts +71 -0
- package/components/fleet/__tests__/AppCoVersionSelect.test.ts +36 -0
- package/components/fleet/__tests__/ClusterSelectionFields.test.ts +62 -0
- package/components/fleet/__tests__/FleetClusterTargets.test.ts +253 -0
- package/components/fleet/__tests__/FleetSecretSelector.test.ts +16 -0
- package/components/fleet/__tests__/FleetValuesFrom.test.ts +44 -0
- package/components/fleet/__tests__/HelmOpAppCoConfigTab.test.ts +59 -0
- package/components/fleet/__tests__/HelmOpAppCoResourcesSection.test.ts +62 -0
- package/components/fleet/__tests__/HelmOpResourcesSection.test.ts +43 -0
- package/components/fleet/__tests__/HelmOpTargetOptionsSection.test.ts +34 -0
- package/components/fleet/__tests__/HelmOpValuesTab.test.ts +39 -0
- package/components/fleet/__tests__/__snapshots__/AppCoEmptyState.test.ts.snap +97 -0
- package/components/fleet/__tests__/__snapshots__/AppCoVersionSelect.test.ts.snap +30 -0
- package/components/fleet/__tests__/__snapshots__/ClusterSelectionFields.test.ts.snap +209 -0
- package/components/fleet/__tests__/__snapshots__/HelmOpTargetOptionsSection.test.ts.snap +140 -0
- package/components/fleet/dashboard/Empty.vue +8 -4
- package/components/fleet/dashboard/ResourceCard.vue +28 -0
- package/components/fleet/dashboard/ResourceDetails.vue +28 -0
- package/components/fleet/dashboard/__tests__/ResourceCard.test.ts +87 -0
- package/components/form/ArrayList.vue +61 -4
- package/components/form/KeyValue.vue +23 -2
- package/components/form/LabeledSelect.vue +39 -1
- package/components/form/Labels.vue +22 -3
- package/components/form/NameNsDescription.vue +13 -5
- package/components/form/ResourceTabs/index.vue +1 -0
- package/components/form/__tests__/NameNsDescription.test.ts +75 -0
- package/components/formatter/InternalExternalIP.vue +10 -4
- package/components/formatter/ServiceTargets.vue +26 -7
- package/components/formatter/__tests__/InternalExternalIP.test.ts +132 -0
- package/components/formatter/__tests__/ServiceTargets.test.ts +412 -0
- package/components/nav/Header.vue +4 -0
- package/components/nav/TopLevelMenu.vue +7 -2
- package/components/nav/__tests__/Header.test.ts +15 -0
- package/components/nav/__tests__/TopLevelMenu.test.ts +120 -2
- package/components/templates/default.vue +9 -4
- package/components/templates/home.vue +9 -4
- package/components/templates/plain.vue +9 -4
- package/composables/useHelmOpResources.test.ts +56 -0
- package/composables/useHelmOpResources.ts +32 -0
- package/composables/useStateColor.test.ts +325 -0
- package/composables/useStateColor.ts +128 -0
- package/config/home-links.js +1 -1
- package/config/labels-annotations.js +1 -0
- package/config/product/explorer.js +17 -4
- package/config/product/manager.js +2 -0
- package/config/router/index.js +16 -0
- package/config/router/navigation-guards/__tests__/authentication.test.ts +130 -0
- package/config/router/navigation-guards/authentication.js +10 -4
- package/config/router/routes.js +20 -6
- package/config/settings.ts +0 -2
- package/config/table-headers.js +3 -4
- package/config/types.js +9 -0
- package/core/plugin-products-base.ts +3 -3
- package/core/plugin-types.ts +83 -30
- package/core/plugin.ts +3 -0
- package/core/types-provisioning.ts +34 -1
- package/core/types.ts +15 -2
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +114 -0
- package/detail/__tests__/workload.test.ts +3 -152
- package/detail/catalog.cattle.io.clusterrepo.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +30 -4
- package/detail/workload/index.vue +12 -55
- package/edit/__tests__/catalog.cattle.io.clusterrepo.test.ts +248 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +105 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +1 -0
- package/edit/auth/__tests__/azuread.test.ts +34 -9
- package/edit/auth/__tests__/github.test.ts +234 -0
- package/edit/auth/__tests__/oidc.test.ts +26 -6
- package/edit/auth/__tests__/saml.test.ts +196 -0
- package/edit/auth/azuread.vue +128 -95
- package/edit/auth/github.vue +72 -13
- package/edit/auth/ldap/__tests__/index.test.ts +206 -0
- package/edit/auth/ldap/config.vue +8 -0
- package/edit/auth/ldap/index.vue +75 -1
- package/edit/auth/oidc.vue +119 -73
- package/edit/auth/saml.vue +76 -12
- package/edit/catalog.cattle.io.clusterrepo.vue +140 -32
- package/edit/fleet.cattle.io.helmop.vue +491 -136
- package/edit/management.cattle.io.user.vue +5 -2
- package/edit/provisioning.cattle.io.cluster/rke2.vue +84 -10
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
- package/list/group.principal.vue +5 -4
- package/list/harvesterhci.io.management.cluster.vue +8 -9
- package/list/management.cattle.io.user.vue +12 -9
- package/list/provisioning.cattle.io.cluster.vue +16 -10
- package/mixins/__tests__/auth-config.test.ts +90 -0
- package/mixins/__tests__/chart.test.ts +94 -0
- package/mixins/__tests__/resource-fetch-api-pagination.test.ts +48 -0
- package/mixins/auth-config.js +7 -0
- package/mixins/chart.js +11 -2
- package/mixins/child-hook.js +12 -6
- package/mixins/create-edit-view/impl.js +5 -3
- package/mixins/resource-fetch-api-pagination.js +21 -1
- package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +57 -0
- package/models/__tests__/compliance.cattle.io.clusterscan.test.ts +144 -0
- package/models/__tests__/fleet-application.test.ts +175 -0
- package/models/__tests__/fleet.cattle.io.bundle.test.ts +169 -0
- package/models/__tests__/fleet.cattle.io.helmop.test.ts +84 -0
- package/models/__tests__/management.cattle.io.node.ts +22 -0
- package/models/__tests__/namespace.test.ts +36 -0
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +49 -0
- package/models/__tests__/workload.test.ts +401 -26
- package/models/catalog.cattle.io.clusterrepo.js +28 -4
- package/models/compliance.cattle.io.clusterscan.js +39 -4
- package/models/fleet-application.js +4 -0
- package/models/fleet.cattle.io.helmop.js +20 -1
- package/models/management.cattle.io.cluster.js +18 -2
- package/models/management.cattle.io.node.js +44 -3
- package/models/namespace.js +1 -1
- package/models/pod.js +33 -1
- package/models/provisioning.cattle.io.cluster.js +5 -5
- package/models/workload.js +108 -13
- package/models/workload.service.js +5 -0
- package/package.json +14 -13
- package/pages/about.vue +5 -6
- package/pages/auth/login.vue +0 -35
- package/pages/auth/setup.vue +11 -0
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +2 -2
- package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +10 -1
- package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +93 -0
- package/pages/c/_cluster/apps/charts/chart.vue +2 -1
- package/pages/c/_cluster/apps/charts/index.vue +48 -10
- package/pages/c/_cluster/apps/charts/install.vue +122 -116
- package/pages/c/_cluster/auth/roles/index.vue +5 -4
- package/pages/c/_cluster/explorer/workload-dashboard/ByNamespaceSection.vue +31 -0
- package/pages/c/_cluster/explorer/workload-dashboard/ByStateSection.vue +138 -0
- package/pages/c/_cluster/explorer/workload-dashboard/ByTypeSection.vue +30 -0
- package/pages/c/_cluster/explorer/workload-dashboard/WorkloadCard.vue +155 -0
- package/pages/c/_cluster/explorer/workload-dashboard/WorkloadNamespaceCard.vue +142 -0
- package/pages/c/_cluster/explorer/workload-dashboard/WorkloadTypeCard.vue +159 -0
- package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +561 -0
- package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +440 -0
- package/pages/c/_cluster/explorer/workload-dashboard/index.vue +187 -0
- package/pages/c/_cluster/explorer/workload-dashboard/types.ts +80 -0
- package/pages/c/_cluster/fleet/application/create.vue +187 -136
- package/pages/c/_cluster/fleet/application/index.vue +5 -3
- package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailBody.vue +338 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailHeader.vue +121 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/chart.vue +369 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/charts.vue +248 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/credentials.vue +310 -0
- package/pages/c/_cluster/fleet/index.vue +2 -2
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +96 -0
- package/pages/c/_cluster/uiplugins/index.vue +15 -0
- package/pages/fail-whale.vue +16 -11
- package/pages/home.vue +16 -46
- package/plugins/clean-html.d.ts +9 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +93 -0
- package/plugins/dashboard-store/resource-class.js +62 -7
- package/plugins/steve/__tests__/actions.test.ts +212 -0
- package/plugins/steve/actions.js +96 -0
- package/plugins/steve/steve-pagination-utils.ts +1 -1
- package/rancher-components/Accordion/Accordion.vue +53 -9
- package/rancher-components/Form/Checkbox/Checkbox.vue +14 -0
- package/rancher-components/Form/Radio/RadioButton.vue +17 -1
- package/rancher-components/Form/Radio/RadioGroup.vue +10 -0
- package/rancher-components/Pill/RcTag/RcTag.vue +3 -2
- package/rancher-components/RcButton/RcButton.test.ts +103 -0
- package/rancher-components/RcButton/RcButton.vue +94 -15
- package/rancher-components/RcButton/types.ts +3 -0
- package/rancher-components/RcItemCard/RcItemCard.test.ts +18 -0
- package/rancher-components/RcItemCard/RcItemCard.vue +2 -2
- package/rancher-components/RcSection/RcSection.vue +28 -3
- package/scripts/extension/helm/package/Dockerfile +1 -1
- package/scripts/test-plugins-build.sh +2 -1
- package/store/__tests__/notifications.test.ts +434 -0
- package/store/catalog.js +57 -0
- package/store/plugins.js +7 -4
- package/types/components/buttonGroup.ts +5 -0
- package/types/shell/index.d.ts +104 -70
- package/utils/__tests__/auth.test.ts +273 -0
- package/utils/__tests__/computed.test.ts +193 -0
- package/utils/__tests__/cspAdaptor.test.ts +163 -0
- package/utils/__tests__/dom.test.ts +81 -0
- package/utils/__tests__/duration.test.ts +37 -1
- package/utils/__tests__/dynamic-importer.test.ts +102 -0
- package/utils/__tests__/fleet-appco.test.ts +312 -0
- package/utils/__tests__/monitoring.test.ts +130 -0
- package/utils/__tests__/object.test.ts +22 -0
- package/utils/__tests__/platform.test.ts +91 -0
- package/utils/__tests__/position.test.ts +237 -0
- package/utils/__tests__/provider.test.ts +51 -1
- package/utils/__tests__/queue.test.ts +232 -0
- package/utils/__tests__/release-notes.test.ts +221 -0
- package/utils/__tests__/router.test.js +254 -1
- package/utils/__tests__/select.test.ts +208 -0
- package/utils/__tests__/time.test.ts +265 -1
- package/utils/__tests__/title.test.ts +47 -0
- package/utils/__tests__/width.test.ts +53 -0
- package/utils/__tests__/window.test.ts +158 -0
- package/utils/__tests__/xccdf.test.ts +126 -6
- package/utils/crypto/__tests__/browserHashUtils.test.ts +98 -0
- package/utils/crypto/__tests__/index.test.ts +144 -0
- package/utils/duration.ts +104 -0
- package/utils/dynamic-content/__tests__/notification-handler.test.ts +196 -0
- package/utils/dynamic-content/info.ts +2 -1
- package/utils/error.js +13 -0
- package/utils/fleet-appco.ts +323 -0
- package/utils/object.js +22 -2
- package/utils/provider.ts +12 -0
- package/utils/validators/__tests__/container-images.test.ts +104 -0
- package/utils/validators/__tests__/flow-output.test.ts +91 -0
- package/utils/validators/__tests__/logging-outputs.test.ts +58 -0
- package/utils/validators/__tests__/monitoring-route.test.ts +119 -0
- package/utils/xccdf.ts +39 -42
- package/vue.config.js +1 -1
- package/pages/support/index.vue +0 -264
- package/utils/duration.js +0 -43
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {
|
|
2
|
+
alternateKey,
|
|
3
|
+
isAlternate,
|
|
4
|
+
isMore,
|
|
5
|
+
isRange,
|
|
6
|
+
moreKey,
|
|
7
|
+
rangeKey,
|
|
8
|
+
suppressContextMenu,
|
|
9
|
+
version,
|
|
10
|
+
} from '@shell/utils/platform';
|
|
11
|
+
|
|
12
|
+
describe('platform utils', () => {
|
|
13
|
+
describe('isAlternate', () => {
|
|
14
|
+
it.each([
|
|
15
|
+
{
|
|
16
|
+
desc: 'returns true when the alternate key is pressed',
|
|
17
|
+
event: { [alternateKey]: true },
|
|
18
|
+
expected: true,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
desc: 'returns false when the alternate key is not pressed',
|
|
22
|
+
event: { [alternateKey]: false },
|
|
23
|
+
expected: false,
|
|
24
|
+
},
|
|
25
|
+
])('$desc', ({ event, expected }) => {
|
|
26
|
+
expect(isAlternate(event)).toStrictEqual(expected);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('isMore', () => {
|
|
31
|
+
it.each([
|
|
32
|
+
{
|
|
33
|
+
desc: 'returns true when the more key is pressed',
|
|
34
|
+
event: { [moreKey]: true },
|
|
35
|
+
expected: true,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
desc: 'returns false when the more key is not pressed',
|
|
39
|
+
event: { [moreKey]: false },
|
|
40
|
+
expected: false,
|
|
41
|
+
},
|
|
42
|
+
])('$desc', ({ event, expected }) => {
|
|
43
|
+
expect(isMore(event)).toStrictEqual(expected);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('isRange', () => {
|
|
48
|
+
it.each([
|
|
49
|
+
{
|
|
50
|
+
desc: 'returns true when the range key (shift) is pressed',
|
|
51
|
+
event: { [rangeKey]: true },
|
|
52
|
+
expected: true,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
desc: 'returns false when the range key is not pressed',
|
|
56
|
+
event: { [rangeKey]: false },
|
|
57
|
+
expected: false,
|
|
58
|
+
},
|
|
59
|
+
])('$desc', ({ event, expected }) => {
|
|
60
|
+
expect(isRange(event)).toStrictEqual(expected);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('suppressContextMenu', () => {
|
|
65
|
+
it.each([
|
|
66
|
+
{
|
|
67
|
+
desc: 'returns true when ctrlKey is pressed and mouse button is 2',
|
|
68
|
+
event: { ctrlKey: true, button: 2 },
|
|
69
|
+
expected: true,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
desc: 'returns false when ctrlKey is not pressed',
|
|
73
|
+
event: { ctrlKey: false, button: 2 },
|
|
74
|
+
expected: false,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
desc: 'returns false when mouse button is not 2',
|
|
78
|
+
event: { ctrlKey: true, button: 0 },
|
|
79
|
+
expected: false,
|
|
80
|
+
},
|
|
81
|
+
])('$desc', ({ event, expected }) => {
|
|
82
|
+
expect(suppressContextMenu(event)).toStrictEqual(expected);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('version', () => {
|
|
87
|
+
it('returns null when userAgent does not contain a Version/ segment', () => {
|
|
88
|
+
expect(version()).toBeNull();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fakeRectFor,
|
|
3
|
+
fitOnScreen,
|
|
4
|
+
AUTO,
|
|
5
|
+
LEFT,
|
|
6
|
+
RIGHT,
|
|
7
|
+
TOP,
|
|
8
|
+
BOTTOM,
|
|
9
|
+
CENTER,
|
|
10
|
+
MIDDLE,
|
|
11
|
+
} from '@shell/utils/position';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Set up JSDOM window dimensions for all tests in this file.
|
|
15
|
+
* fitOnScreen calls screenRect() which reads window.innerWidth/Height/pageX/YOffset.
|
|
16
|
+
*/
|
|
17
|
+
function setScreen(width: number, height: number, scrollX = 0, scrollY = 0) {
|
|
18
|
+
Object.defineProperty(window, 'innerWidth', { configurable: true, value: width });
|
|
19
|
+
Object.defineProperty(window, 'innerHeight', { configurable: true, value: height });
|
|
20
|
+
Object.defineProperty(window, 'pageXOffset', { configurable: true, value: scrollX });
|
|
21
|
+
Object.defineProperty(window, 'pageYOffset', { configurable: true, value: scrollY });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Build a real MouseEvent so that instanceof Event check in fitOnScreen passes. */
|
|
25
|
+
function makeEvent(clientX: number, clientY: number): MouseEvent {
|
|
26
|
+
return new MouseEvent('click', { clientX, clientY });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('position.js', () => {
|
|
30
|
+
describe('fakeRectFor', () => {
|
|
31
|
+
it.each([
|
|
32
|
+
{
|
|
33
|
+
desc: 'creates a zero-size rect centred at the event coordinates',
|
|
34
|
+
clientX: 100,
|
|
35
|
+
clientY: 200,
|
|
36
|
+
expected: {
|
|
37
|
+
top: 200, left: 100, bottom: 200, right: 100, width: 0, height: 0
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
desc: 'handles origin (0,0)',
|
|
42
|
+
clientX: 0,
|
|
43
|
+
clientY: 0,
|
|
44
|
+
expected: {
|
|
45
|
+
top: 0, left: 0, bottom: 0, right: 0, width: 0, height: 0
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
desc: 'handles negative coordinates',
|
|
50
|
+
clientX: -5,
|
|
51
|
+
clientY: -10,
|
|
52
|
+
expected: {
|
|
53
|
+
top: -10, left: -5, bottom: -10, right: -5, width: 0, height: 0
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
])('$desc', ({ clientX, clientY, expected }) => {
|
|
57
|
+
expect(fakeRectFor(makeEvent(clientX, clientY))).toStrictEqual(expected);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('fitOnScreen', () => {
|
|
62
|
+
beforeEach(() => {
|
|
63
|
+
setScreen(1000, 800);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('horizontal AUTO positioning', () => {
|
|
67
|
+
it('chooses LEFT when there is more room on the left than the right', () => {
|
|
68
|
+
// Trigger at x=500 on a 1000px screen. Content is 147px wide.
|
|
69
|
+
// gapIf.left = 1000 - 147 - 500 = 353; gapIf.right = 500 - 147 - 0 = 353
|
|
70
|
+
// condition: gapIf.left < 0 || gapIf.right * 1.5 > gapIf.left → 353*1.5=529.5 > 353 → RIGHT
|
|
71
|
+
// Actually with equal gaps condition picks RIGHT. Let's use a trigger far from left.
|
|
72
|
+
// Trigger at x=700: gapIf.left = 1000-147-700=153; gapIf.right = 700-147-0=553
|
|
73
|
+
// condition: gapIf.right * 1.5 > gapIf.left → 553*1.5=829.5 > 153 → RIGHT
|
|
74
|
+
// For LEFT: need gapIf.right * 1.5 <= gapIf.left
|
|
75
|
+
// Example: trigger at x=200, content=147: gapIf.left=653, gapIf.right=53; 53*1.5=79.5 < 653 → LEFT
|
|
76
|
+
const style = fitOnScreen(null, makeEvent(200, 400), { positionX: AUTO, positionY: AUTO }, true);
|
|
77
|
+
|
|
78
|
+
expect(style.left).toBe('200px'); // originFor.left - fudgeX = 200 - 0
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('chooses RIGHT when there is more room on the right', () => {
|
|
82
|
+
// Trigger at x=900: gapIf.left = 1000-147-900=-47 < 0 → RIGHT
|
|
83
|
+
// style.left = originFor.right + 0 - 147 = 900 - 147 = 753
|
|
84
|
+
const style = fitOnScreen(null, makeEvent(900, 400), { positionX: AUTO, positionY: AUTO }, true);
|
|
85
|
+
|
|
86
|
+
expect(style.left).toBe('753px');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('chooses RIGHT when left gap is negative', () => {
|
|
90
|
+
// Trigger at x=50: gapIf.right = 50-147-0 = -97 (negative), gapIf.left = 1000-147-50=803 (positive)
|
|
91
|
+
// condition: gapIf.left < 0 = false, gapIf.right*1.5=-145.5 > 803? No → LEFT
|
|
92
|
+
// Wait let me recalculate: trigger at (50, 400), overlapX=true default
|
|
93
|
+
// originFor.left = trigger.left = 50, originFor.right = trigger.right = 50
|
|
94
|
+
// gapIf.left = screen.right - content.width - originFor.left = 1000 - 147 - 50 = 803
|
|
95
|
+
// 803 < 0? No. gapIf.right * 1.5 > gapIf.left → (-97)*1.5 = -145.5 > 803? No → LEFT
|
|
96
|
+
// style.left = 50 - 0 = 50px
|
|
97
|
+
const style = fitOnScreen(null, makeEvent(50, 400), { positionX: AUTO, positionY: AUTO }, true);
|
|
98
|
+
|
|
99
|
+
expect(style.left).toBe('50px');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('explicit horizontal positioning', () => {
|
|
104
|
+
it('respects explicit LEFT position', () => {
|
|
105
|
+
// Trigger at x=300 (lots of room both sides)
|
|
106
|
+
// originFor.left = 300, style.left = 300 - 0 = 300px
|
|
107
|
+
const style = fitOnScreen(null, makeEvent(300, 400), { positionX: LEFT, positionY: AUTO }, true);
|
|
108
|
+
|
|
109
|
+
expect(style.left).toBe('300px');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('respects explicit RIGHT position', () => {
|
|
113
|
+
// Trigger at x=300, content.width=147: style.left = 300 + 0 - 147 = 153px
|
|
114
|
+
const style = fitOnScreen(null, makeEvent(300, 400), { positionX: RIGHT, positionY: AUTO }, true);
|
|
115
|
+
|
|
116
|
+
expect(style.left).toBe('153px');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('uses CENTER position when there is enough room', () => {
|
|
120
|
+
// Trigger at x=500: originFor.left=500, originFor.right=500
|
|
121
|
+
// gapIf.center = min(1000 - 73.5 - 500, 500 - 73.5 - 0) = min(426.5, 426.5) = 426.5 >= 0 → stays CENTER
|
|
122
|
+
// style.left = (500+500)/2 - 147/2 - 0 = 500 - 73.5 = 426.5px
|
|
123
|
+
const style = fitOnScreen(null, makeEvent(500, 400), { positionX: CENTER, positionY: AUTO }, true);
|
|
124
|
+
|
|
125
|
+
expect(style.left).toBe('426.5px');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('falls back from CENTER to AUTO when center gap is negative', () => {
|
|
129
|
+
// Trigger at x=50: gapIf.center = min(1000-73.5-50, 50-73.5-0) = min(876.5, -23.5) = -23.5 < 0 → AUTO
|
|
130
|
+
// From AUTO: gapIf.left=803, gapIf.right=-97; gapIf.right*1.5=-145.5 > 803? No → LEFT
|
|
131
|
+
// style.left = 50px
|
|
132
|
+
const style = fitOnScreen(null, makeEvent(50, 400), { positionX: CENTER, positionY: AUTO }, true);
|
|
133
|
+
|
|
134
|
+
expect(style.left).toBe('50px');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('vertical AUTO positioning', () => {
|
|
139
|
+
it('chooses BOTTOM when the trigger is near the top', () => {
|
|
140
|
+
// Trigger at y=10: gapIf.top = 10 - 80 - 0 = -70 < 0 → BOTTOM
|
|
141
|
+
// style.top = originFor.bottom - 0 = 10px
|
|
142
|
+
const style = fitOnScreen(null, makeEvent(200, 10), { positionX: AUTO, positionY: AUTO }, true);
|
|
143
|
+
|
|
144
|
+
expect(style.top).toBe('10px');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('chooses TOP when the trigger is far from the top and bottom gap is small', () => {
|
|
148
|
+
// Trigger at y=700: gapIf.top=700-80-0=620, gapIf.bottom=800-80-700=20
|
|
149
|
+
// gapIf.top < 0? No. gapIf.bottom * 1.5 = 30 > 620? No → TOP
|
|
150
|
+
// style.top = originFor.top + 0 - 80 = 700 - 80 = 620px
|
|
151
|
+
const style = fitOnScreen(null, makeEvent(200, 700), { positionX: AUTO, positionY: AUTO }, true);
|
|
152
|
+
|
|
153
|
+
expect(style.top).toBe('620px');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('chooses BOTTOM when bottom gap is more than 1.5x top gap', () => {
|
|
157
|
+
// Trigger at y=50: gapIf.top = 50-80-0=-30 < 0 → BOTTOM
|
|
158
|
+
// style.top = 50px
|
|
159
|
+
const style = fitOnScreen(null, makeEvent(200, 50), { positionX: AUTO, positionY: AUTO }, true);
|
|
160
|
+
|
|
161
|
+
expect(style.top).toBe('50px');
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('explicit vertical positioning', () => {
|
|
166
|
+
it('respects explicit TOP position', () => {
|
|
167
|
+
// Trigger at y=400: originFor.top = 400, style.top = 400 + 0 - 80 = 320px
|
|
168
|
+
const style = fitOnScreen(null, makeEvent(200, 400), { positionX: AUTO, positionY: TOP }, true);
|
|
169
|
+
|
|
170
|
+
expect(style.top).toBe('320px');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('respects explicit BOTTOM position', () => {
|
|
174
|
+
// Trigger at y=400: originFor.bottom = 400, style.top = 400 - 0 = 400px
|
|
175
|
+
const style = fitOnScreen(null, makeEvent(200, 400), { positionX: AUTO, positionY: BOTTOM }, true);
|
|
176
|
+
|
|
177
|
+
expect(style.top).toBe('400px');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('falls back from TOP to BOTTOM when top gap is negative', () => {
|
|
181
|
+
// Trigger at y=30: gapIf.top = originFor.bottom - content.height - screen.top = 30 - 80 - 0 = -50 < 0
|
|
182
|
+
// → positionY switches from TOP to BOTTOM
|
|
183
|
+
// style.top = originFor.bottom - fudgeY = 30 - 0 = 30px
|
|
184
|
+
const style = fitOnScreen(null, makeEvent(200, 30), { positionX: AUTO, positionY: TOP }, true);
|
|
185
|
+
|
|
186
|
+
expect(style.top).toBe('30px');
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('fudge offsets', () => {
|
|
191
|
+
it('applies fudgeX to left position', () => {
|
|
192
|
+
// Trigger at x=200, fudgeX=10, positionX=LEFT: style.left = 200 - 10 = 190px
|
|
193
|
+
const style = fitOnScreen(null, makeEvent(200, 400), {
|
|
194
|
+
positionX: LEFT, positionY: AUTO, fudgeX: 10
|
|
195
|
+
}, true);
|
|
196
|
+
|
|
197
|
+
expect(style.left).toBe('190px');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('applies fudgeY to top position', () => {
|
|
201
|
+
// Trigger at y=700 → TOP: style.top = 700 + 5 - 80 = 625px
|
|
202
|
+
const style = fitOnScreen(null, makeEvent(200, 700), {
|
|
203
|
+
positionX: AUTO, positionY: TOP, fudgeY: 5
|
|
204
|
+
}, true);
|
|
205
|
+
|
|
206
|
+
expect(style.top).toBe('625px');
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('mIDDLE vertical positioning', () => {
|
|
211
|
+
it('uses MIDDLE position when there is enough room', () => {
|
|
212
|
+
// Trigger at y=400: originFor.middle = 400
|
|
213
|
+
// gapIf.middle = min(400-40-0, 800-40-400) = min(360, 360) = 360 >= 0 → stays MIDDLE
|
|
214
|
+
// switch MIDDLE == CENTER case: style.top = (400+400)/2 + 0 - 80 = 400-80 = 320px
|
|
215
|
+
const style = fitOnScreen(null, makeEvent(200, 400), { positionX: AUTO, positionY: MIDDLE }, true);
|
|
216
|
+
|
|
217
|
+
expect(style.top).toBe('320px');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('falls back from MIDDLE to AUTO when middle gap is negative', () => {
|
|
221
|
+
// Trigger at y=30: originFor.middle = 30
|
|
222
|
+
// gapIf.middle = min(30-40-0, 800-40-30) = min(-10, 730) = -10 < 0 → positionY = AUTO
|
|
223
|
+
// Auto: gapIf.top = 30-80-0=-50 < 0 → BOTTOM
|
|
224
|
+
// style.top = originFor.bottom - 0 = 30px
|
|
225
|
+
const style = fitOnScreen(null, makeEvent(200, 30), { positionX: AUTO, positionY: MIDDLE }, true);
|
|
226
|
+
|
|
227
|
+
expect(style.top).toBe('30px');
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('always sets position to absolute', () => {
|
|
232
|
+
const style = fitOnScreen(null, makeEvent(200, 400), {}, true);
|
|
233
|
+
|
|
234
|
+
expect(style.position).toBe('absolute');
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getHostedProviders, isHostedProvider } from '../provider';
|
|
1
|
+
import { getHostedProviders, isHostedProvider, getCAPIProviders, isCAPIProvider } from '../provider';
|
|
2
2
|
import { ClusterProvisionerContext, IClusterProvisioner } from '@shell/core/types';
|
|
3
3
|
|
|
4
4
|
const DEFAULT_CONTEXT = {
|
|
@@ -13,6 +13,8 @@ const MOCK_PROVIDERS: IClusterProvisioner[] = [
|
|
|
13
13
|
{ id: 'EKS', group: 'hosted' } as IClusterProvisioner,
|
|
14
14
|
{ id: 'GKE', group: 'hosted' } as IClusterProvisioner,
|
|
15
15
|
{ id: 'alibaba', group: 'hosted' } as IClusterProvisioner,
|
|
16
|
+
{ id: 'capa', group: 'capi' } as IClusterProvisioner,
|
|
17
|
+
{ id: 'capv', group: 'capi' } as IClusterProvisioner,
|
|
16
18
|
{ id: 'other', group: 'other' } as IClusterProvisioner,
|
|
17
19
|
];
|
|
18
20
|
|
|
@@ -42,6 +44,22 @@ describe('utils/provider', () => {
|
|
|
42
44
|
expect(context.$extension.getProviders).toHaveBeenCalledWith(context);
|
|
43
45
|
});
|
|
44
46
|
|
|
47
|
+
it('should return capi providers when context.$extension is defined', () => {
|
|
48
|
+
const context = {
|
|
49
|
+
...DEFAULT_CONTEXT,
|
|
50
|
+
$extension: { getProviders: jest.fn().mockReturnValue(MOCK_PROVIDERS) }
|
|
51
|
+
} as unknown as ClusterProvisionerContext;
|
|
52
|
+
|
|
53
|
+
const result = getCAPIProviders(context);
|
|
54
|
+
|
|
55
|
+
expect(result).toStrictEqual([
|
|
56
|
+
{ id: 'capa', group: 'capi' },
|
|
57
|
+
{ id: 'capv', group: 'capi' },
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
expect(context.$extension.getProviders).toHaveBeenCalledWith(context);
|
|
61
|
+
});
|
|
62
|
+
|
|
45
63
|
it('should return an empty array if getProviders returns null', () => {
|
|
46
64
|
const context = {
|
|
47
65
|
...DEFAULT_CONTEXT,
|
|
@@ -85,10 +103,42 @@ describe('utils/provider', () => {
|
|
|
85
103
|
|
|
86
104
|
expect(isHostedProvider(context, 'AKS')).toBe(true);
|
|
87
105
|
expect(isHostedProvider(context, 'eks')).toBe(true);
|
|
106
|
+
expect(isHostedProvider(context, 'capa')).toBe(false);
|
|
88
107
|
expect(isHostedProvider(context, 'different')).toBe(false); // case-insensitive check
|
|
89
108
|
expect(isHostedProvider(context, 'other')).toBe(false); // case-insensitive check
|
|
90
109
|
});
|
|
91
110
|
|
|
111
|
+
it('should return false if there are no hosted providers', () => {
|
|
112
|
+
const context = { ...DEFAULT_CONTEXT, $extension: { getProviders: jest.fn().mockReturnValue([]) } } as ClusterProvisionerContext;
|
|
113
|
+
|
|
114
|
+
expect(isHostedProvider(context, 'prov1')).toBe(false);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
describe('isCAPIProvider', () => {
|
|
118
|
+
it('should return false if provisioner is not provided', () => {
|
|
119
|
+
const context = {
|
|
120
|
+
...DEFAULT_CONTEXT,
|
|
121
|
+
$extension: { getProviders: jest.fn().mockReturnValue(MOCK_PROVIDERS) }
|
|
122
|
+
} as ClusterProvisionerContext;
|
|
123
|
+
|
|
124
|
+
expect(isCAPIProvider(context, '')).toBe(false);
|
|
125
|
+
expect(isCAPIProvider(context, undefined as any)).toBe(false);
|
|
126
|
+
expect(isCAPIProvider(context, null as any)).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should return true only if provisioner is in the list of CAPI providers', () => {
|
|
130
|
+
const context = {
|
|
131
|
+
...DEFAULT_CONTEXT,
|
|
132
|
+
$extension: { getProviders: jest.fn().mockReturnValue(MOCK_PROVIDERS) }
|
|
133
|
+
} as ClusterProvisionerContext;
|
|
134
|
+
|
|
135
|
+
expect(isCAPIProvider(context, 'capa')).toBe(true);
|
|
136
|
+
expect(isCAPIProvider(context, 'capv')).toBe(true);
|
|
137
|
+
expect(isCAPIProvider(context, 'eks')).toBe(false);
|
|
138
|
+
expect(isCAPIProvider(context, 'different')).toBe(false); // case-insensitive check
|
|
139
|
+
expect(isCAPIProvider(context, 'other')).toBe(false); // case-insensitive check
|
|
140
|
+
});
|
|
141
|
+
|
|
92
142
|
it('should return false if there are no hosted providers', () => {
|
|
93
143
|
const context = { ...DEFAULT_CONTEXT, $extension: { getProviders: jest.fn().mockReturnValue([]) } } as ClusterProvisionerContext;
|
|
94
144
|
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import Queue from '@shell/utils/queue';
|
|
2
|
+
|
|
3
|
+
describe('queue', () => {
|
|
4
|
+
describe('new queue', () => {
|
|
5
|
+
it('starts with length 0', () => {
|
|
6
|
+
const q = new Queue();
|
|
7
|
+
|
|
8
|
+
expect(q.getLength()).toStrictEqual(0);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('starts empty', () => {
|
|
12
|
+
const q = new Queue();
|
|
13
|
+
|
|
14
|
+
expect(q.isEmpty()).toStrictEqual(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('peek returns undefined on empty queue', () => {
|
|
18
|
+
const q = new Queue();
|
|
19
|
+
|
|
20
|
+
expect(q.peek()).toBeUndefined();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('dequeue returns undefined on empty queue', () => {
|
|
24
|
+
const q = new Queue();
|
|
25
|
+
|
|
26
|
+
expect(q.dequeue()).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('enqueue', () => {
|
|
31
|
+
it('increases length by 1', () => {
|
|
32
|
+
const q = new Queue();
|
|
33
|
+
|
|
34
|
+
q.enqueue('a');
|
|
35
|
+
expect(q.getLength()).toStrictEqual(1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('marks queue as non-empty after first item', () => {
|
|
39
|
+
const q = new Queue();
|
|
40
|
+
|
|
41
|
+
q.enqueue('x');
|
|
42
|
+
expect(q.isEmpty()).toStrictEqual(false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('accepts arbitrary values', () => {
|
|
46
|
+
const q = new Queue();
|
|
47
|
+
|
|
48
|
+
q.enqueue(42);
|
|
49
|
+
q.enqueue(null);
|
|
50
|
+
q.enqueue({ key: 'val' });
|
|
51
|
+
expect(q.getLength()).toStrictEqual(3);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('dequeue', () => {
|
|
56
|
+
it('returns the enqueued item', () => {
|
|
57
|
+
const q = new Queue();
|
|
58
|
+
|
|
59
|
+
q.enqueue('hello');
|
|
60
|
+
expect(q.dequeue()).toStrictEqual('hello');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('decreases length by 1', () => {
|
|
64
|
+
const q = new Queue();
|
|
65
|
+
|
|
66
|
+
q.enqueue('a');
|
|
67
|
+
q.enqueue('b');
|
|
68
|
+
q.dequeue();
|
|
69
|
+
expect(q.getLength()).toStrictEqual(1);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('empties the queue when last item is removed', () => {
|
|
73
|
+
const q = new Queue();
|
|
74
|
+
|
|
75
|
+
q.enqueue('only');
|
|
76
|
+
q.dequeue();
|
|
77
|
+
expect(q.isEmpty()).toStrictEqual(true);
|
|
78
|
+
expect(q.getLength()).toStrictEqual(0);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('maintains FIFO order', () => {
|
|
82
|
+
const q = new Queue();
|
|
83
|
+
|
|
84
|
+
q.enqueue('first');
|
|
85
|
+
q.enqueue('second');
|
|
86
|
+
q.enqueue('third');
|
|
87
|
+
|
|
88
|
+
expect(q.dequeue()).toStrictEqual('first');
|
|
89
|
+
expect(q.dequeue()).toStrictEqual('second');
|
|
90
|
+
expect(q.dequeue()).toStrictEqual('third');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('returns undefined after queue is drained', () => {
|
|
94
|
+
const q = new Queue();
|
|
95
|
+
|
|
96
|
+
q.enqueue('item');
|
|
97
|
+
q.dequeue();
|
|
98
|
+
expect(q.dequeue()).toBeUndefined();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('peek', () => {
|
|
103
|
+
it('returns the front item without removing it', () => {
|
|
104
|
+
const q = new Queue();
|
|
105
|
+
|
|
106
|
+
q.enqueue('front');
|
|
107
|
+
q.enqueue('back');
|
|
108
|
+
expect(q.peek()).toStrictEqual('front');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('does not change the queue length', () => {
|
|
112
|
+
const q = new Queue();
|
|
113
|
+
|
|
114
|
+
q.enqueue('a');
|
|
115
|
+
q.peek();
|
|
116
|
+
expect(q.getLength()).toStrictEqual(1);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('reflects the new front after a dequeue', () => {
|
|
120
|
+
const q = new Queue();
|
|
121
|
+
|
|
122
|
+
q.enqueue('first');
|
|
123
|
+
q.enqueue('second');
|
|
124
|
+
q.dequeue();
|
|
125
|
+
expect(q.peek()).toStrictEqual('second');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('clear', () => {
|
|
130
|
+
it('empties a populated queue', () => {
|
|
131
|
+
const q = new Queue();
|
|
132
|
+
|
|
133
|
+
q.enqueue('a');
|
|
134
|
+
q.enqueue('b');
|
|
135
|
+
q.clear();
|
|
136
|
+
expect(q.isEmpty()).toStrictEqual(true);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('resets length to 0', () => {
|
|
140
|
+
const q = new Queue();
|
|
141
|
+
|
|
142
|
+
q.enqueue(1);
|
|
143
|
+
q.enqueue(2);
|
|
144
|
+
q.enqueue(3);
|
|
145
|
+
q.clear();
|
|
146
|
+
expect(q.getLength()).toStrictEqual(0);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('allows enqueue after clear', () => {
|
|
150
|
+
const q = new Queue();
|
|
151
|
+
|
|
152
|
+
q.enqueue('before');
|
|
153
|
+
q.clear();
|
|
154
|
+
q.enqueue('after');
|
|
155
|
+
expect(q.dequeue()).toStrictEqual('after');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('is safe to call on an already-empty queue', () => {
|
|
159
|
+
const q = new Queue();
|
|
160
|
+
|
|
161
|
+
q.clear();
|
|
162
|
+
expect(q.isEmpty()).toStrictEqual(true);
|
|
163
|
+
expect(q.getLength()).toStrictEqual(0);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('mixed operations', () => {
|
|
168
|
+
it('handles interleaved enqueue and dequeue correctly', () => {
|
|
169
|
+
const q = new Queue();
|
|
170
|
+
|
|
171
|
+
q.enqueue('a');
|
|
172
|
+
q.enqueue('b');
|
|
173
|
+
expect(q.dequeue()).toStrictEqual('a');
|
|
174
|
+
q.enqueue('c');
|
|
175
|
+
expect(q.dequeue()).toStrictEqual('b');
|
|
176
|
+
expect(q.dequeue()).toStrictEqual('c');
|
|
177
|
+
expect(q.isEmpty()).toStrictEqual(true);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('tracks length correctly through mixed operations', () => {
|
|
181
|
+
const q = new Queue();
|
|
182
|
+
|
|
183
|
+
q.enqueue(1);
|
|
184
|
+
q.enqueue(2);
|
|
185
|
+
q.enqueue(3);
|
|
186
|
+
expect(q.getLength()).toStrictEqual(3);
|
|
187
|
+
|
|
188
|
+
q.dequeue();
|
|
189
|
+
expect(q.getLength()).toStrictEqual(2);
|
|
190
|
+
|
|
191
|
+
q.enqueue(4);
|
|
192
|
+
expect(q.getLength()).toStrictEqual(3);
|
|
193
|
+
|
|
194
|
+
q.dequeue();
|
|
195
|
+
q.dequeue();
|
|
196
|
+
expect(q.getLength()).toStrictEqual(1);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('compacts the backing array when threshold is reached', () => {
|
|
200
|
+
// The implementation slices when ++offset * 2 >= queue.length.
|
|
201
|
+
// With 4 items: after the 2nd dequeue offset*2 (4) reaches length (4),
|
|
202
|
+
// triggering a compaction so the backing array is reset.
|
|
203
|
+
const q = new Queue();
|
|
204
|
+
|
|
205
|
+
q.enqueue('w');
|
|
206
|
+
q.enqueue('x');
|
|
207
|
+
q.enqueue('y');
|
|
208
|
+
q.enqueue('z');
|
|
209
|
+
|
|
210
|
+
expect(q.dequeue()).toStrictEqual('w'); // offset=1, 1*2=2 < 4 — no compaction yet
|
|
211
|
+
expect(q.dequeue()).toStrictEqual('x'); // offset=2, 2*2=4 >= 4 — compaction!
|
|
212
|
+
// After compaction: backing array = ['y','z'], offset=0
|
|
213
|
+
expect(q.getLength()).toStrictEqual(2);
|
|
214
|
+
expect(q.peek()).toStrictEqual('y');
|
|
215
|
+
expect(q.dequeue()).toStrictEqual('y');
|
|
216
|
+
expect(q.dequeue()).toStrictEqual('z');
|
|
217
|
+
expect(q.isEmpty()).toStrictEqual(true);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('enqueues object values and preserves them through FIFO', () => {
|
|
221
|
+
const q = new Queue();
|
|
222
|
+
const obj1 = { id: 1, name: 'first' };
|
|
223
|
+
const obj2 = { id: 2, name: 'second' };
|
|
224
|
+
|
|
225
|
+
q.enqueue(obj1);
|
|
226
|
+
q.enqueue(obj2);
|
|
227
|
+
|
|
228
|
+
expect(q.dequeue()).toStrictEqual({ id: 1, name: 'first' });
|
|
229
|
+
expect(q.dequeue()).toStrictEqual({ id: 2, name: 'second' });
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
});
|