@rancher/shell 3.0.12-rc.3 → 3.0.12-rc.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. package/assets/styles/global/_button.scss +1 -1
  2. package/assets/styles/global/_layout.scss +4 -0
  3. package/assets/translations/en-us.yaml +183 -51
  4. package/assets/translations/zh-hans.yaml +1 -7
  5. package/chart/monitoring/ClusterSelector.vue +0 -21
  6. package/chart/monitoring/prometheus/index.vue +6 -3
  7. package/components/ActionDropdownShell.vue +5 -3
  8. package/components/ButtonGroup.vue +26 -1
  9. package/components/CruResource.vue +212 -16
  10. package/components/ExplorerMembers.vue +8 -4
  11. package/components/ExplorerProjectsNamespaces.vue +10 -6
  12. package/components/GrowlManager.vue +4 -0
  13. package/components/MgmtNodeList.vue +184 -0
  14. package/components/PromptRestore.vue +93 -32
  15. package/components/Questions/index.vue +1 -0
  16. package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +90 -1
  17. package/components/Resource/Detail/Card/StateCard/composables.ts +57 -87
  18. package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +61 -0
  19. package/components/Resource/Detail/Card/StatusCard/index.vue +61 -15
  20. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +2 -0
  21. package/components/Resource/Detail/Metadata/KeyValue.vue +5 -2
  22. package/components/Resource/Detail/Metadata/KeyValueRow.vue +2 -6
  23. package/components/ResourceDetail/index.vue +1 -1
  24. package/components/ResourceList/Masthead.vue +7 -1
  25. package/components/ResourceList/index.vue +82 -1
  26. package/components/ResourceTable.vue +1 -0
  27. package/components/RichTranslation.vue +5 -2
  28. package/components/Setting.vue +1 -0
  29. package/components/SortableTable/index.vue +4 -3
  30. package/components/SubtleLink.vue +31 -6
  31. package/components/Tabbed/Tab.vue +29 -3
  32. package/components/Tabbed/index.vue +25 -3
  33. package/components/TableOfContents/TableOfContents.vue +109 -0
  34. package/components/TableOfContents/composables.ts +258 -0
  35. package/components/Window/ContainerShell.vue +21 -11
  36. package/components/Window/__tests__/ContainerShell.test.ts +107 -37
  37. package/components/Wizard.vue +23 -5
  38. package/components/__tests__/ButtonGroup.test.ts +56 -0
  39. package/components/__tests__/PromptRestore.test.ts +169 -19
  40. package/components/fleet/AppCoChartGrid.vue +401 -0
  41. package/components/fleet/AppCoEmptyState.vue +127 -0
  42. package/components/fleet/AppCoPageHeader.vue +119 -0
  43. package/components/fleet/AppCoVersionSelect.vue +70 -0
  44. package/components/fleet/FleetClusterTargets/ClusterSelectionFields.vue +217 -0
  45. package/components/fleet/FleetClusterTargets/TargetsList.vue +123 -35
  46. package/components/fleet/FleetClusterTargets/index.vue +189 -146
  47. package/components/fleet/FleetIntro.vue +7 -3
  48. package/components/fleet/FleetNoWorkspaces.vue +7 -3
  49. package/components/fleet/FleetSecretSelector.vue +5 -3
  50. package/components/fleet/FleetValuesFrom.vue +8 -2
  51. package/components/fleet/GitRepoAdvancedTab.vue +1 -0
  52. package/components/fleet/GitRepoMetadataTab.vue +5 -0
  53. package/components/fleet/GitRepoTargetTab.vue +0 -2
  54. package/components/fleet/HelmOpAdvancedTab.vue +19 -53
  55. package/components/fleet/HelmOpAppCoConfigTab.vue +597 -0
  56. package/components/fleet/HelmOpAppCoResourcesSection.vue +162 -0
  57. package/components/fleet/HelmOpMetadataTab.vue +5 -0
  58. package/components/fleet/HelmOpResourcesSection.vue +82 -0
  59. package/components/fleet/HelmOpTargetOptionsSection.vue +89 -0
  60. package/components/fleet/HelmOpTargetTab.vue +64 -60
  61. package/components/fleet/HelmOpValuesTab.vue +129 -105
  62. package/components/fleet/__tests__/AppCoEmptyState.test.ts +71 -0
  63. package/components/fleet/__tests__/AppCoVersionSelect.test.ts +36 -0
  64. package/components/fleet/__tests__/ClusterSelectionFields.test.ts +62 -0
  65. package/components/fleet/__tests__/FleetClusterTargets.test.ts +253 -0
  66. package/components/fleet/__tests__/FleetSecretSelector.test.ts +16 -0
  67. package/components/fleet/__tests__/FleetValuesFrom.test.ts +44 -0
  68. package/components/fleet/__tests__/HelmOpAppCoConfigTab.test.ts +59 -0
  69. package/components/fleet/__tests__/HelmOpAppCoResourcesSection.test.ts +62 -0
  70. package/components/fleet/__tests__/HelmOpResourcesSection.test.ts +43 -0
  71. package/components/fleet/__tests__/HelmOpTargetOptionsSection.test.ts +34 -0
  72. package/components/fleet/__tests__/HelmOpValuesTab.test.ts +39 -0
  73. package/components/fleet/__tests__/__snapshots__/AppCoEmptyState.test.ts.snap +97 -0
  74. package/components/fleet/__tests__/__snapshots__/AppCoVersionSelect.test.ts.snap +30 -0
  75. package/components/fleet/__tests__/__snapshots__/ClusterSelectionFields.test.ts.snap +209 -0
  76. package/components/fleet/__tests__/__snapshots__/HelmOpTargetOptionsSection.test.ts.snap +140 -0
  77. package/components/fleet/dashboard/Empty.vue +8 -4
  78. package/components/fleet/dashboard/ResourceCard.vue +28 -0
  79. package/components/fleet/dashboard/ResourceDetails.vue +28 -0
  80. package/components/fleet/dashboard/__tests__/ResourceCard.test.ts +87 -0
  81. package/components/form/ArrayList.vue +61 -4
  82. package/components/form/FileSelector.vue +39 -1
  83. package/components/form/KeyValue.vue +23 -2
  84. package/components/form/LabeledSelect.vue +39 -1
  85. package/components/form/Labels.vue +22 -3
  86. package/components/form/NameNsDescription.vue +13 -5
  87. package/components/form/PrivateRegistry.constants.ts +7 -0
  88. package/components/form/PrivateRegistry.vue +253 -18
  89. package/components/form/ResourceTabs/index.vue +1 -0
  90. package/components/form/SelectOrCreateAuthSecret.vue +140 -17
  91. package/components/form/__tests__/FileSelector.test.ts +23 -0
  92. package/components/form/__tests__/NameNsDescription.test.ts +75 -0
  93. package/components/form/__tests__/PrivateRegistry.test.ts +463 -73
  94. package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +122 -0
  95. package/components/formatter/EtcdSnapshotName.vue +73 -0
  96. package/components/formatter/InternalExternalIP.vue +10 -4
  97. package/components/formatter/ServiceTargets.vue +26 -7
  98. package/components/formatter/__tests__/InternalExternalIP.test.ts +132 -0
  99. package/components/formatter/__tests__/ServiceTargets.test.ts +412 -0
  100. package/components/nav/Header.vue +12 -1
  101. package/components/nav/TopLevelMenu.vue +7 -2
  102. package/components/nav/__tests__/Header.test.ts +15 -0
  103. package/components/nav/__tests__/TopLevelMenu.test.ts +120 -2
  104. package/components/templates/default.vue +16 -4
  105. package/components/templates/home.vue +9 -4
  106. package/components/templates/plain.vue +9 -4
  107. package/composables/useHelmOpResources.test.ts +56 -0
  108. package/composables/useHelmOpResources.ts +32 -0
  109. package/composables/useStateColor.test.ts +325 -0
  110. package/composables/useStateColor.ts +128 -0
  111. package/config/features.js +1 -0
  112. package/config/home-links.js +1 -1
  113. package/config/labels-annotations.js +3 -0
  114. package/config/product/explorer.js +17 -4
  115. package/config/product/manager.js +8 -0
  116. package/config/router/index.js +16 -0
  117. package/config/router/navigation-guards/__tests__/authentication.test.ts +130 -0
  118. package/config/router/navigation-guards/authentication.js +10 -4
  119. package/config/router/routes.js +20 -6
  120. package/config/secret.ts +10 -0
  121. package/config/settings.ts +6 -4
  122. package/config/table-headers.js +3 -4
  123. package/config/types.js +16 -0
  124. package/core/plugin-products-base.ts +3 -3
  125. package/core/plugin-types.ts +83 -30
  126. package/core/plugin.ts +3 -0
  127. package/core/types-provisioning.ts +34 -1
  128. package/core/types.ts +15 -2
  129. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +114 -0
  130. package/detail/__tests__/workload.test.ts +3 -152
  131. package/detail/catalog.cattle.io.clusterrepo.vue +1 -1
  132. package/detail/provisioning.cattle.io.cluster.vue +109 -7
  133. package/detail/workload/index.vue +12 -55
  134. package/dialog/RotateEncryptionKeyDialog.vue +33 -9
  135. package/dialog/__tests__/RotateEncryptionKeyDialog.test.ts +78 -0
  136. package/edit/__tests__/catalog.cattle.io.clusterrepo.test.ts +248 -0
  137. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +92 -0
  138. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +206 -0
  139. package/edit/__tests__/management.cattle.io.setting.test.ts +2 -1
  140. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
  141. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +1 -0
  142. package/edit/auth/__tests__/azuread.test.ts +34 -9
  143. package/edit/auth/__tests__/github.test.ts +234 -0
  144. package/edit/auth/__tests__/oidc.test.ts +26 -6
  145. package/edit/auth/__tests__/saml.test.ts +196 -0
  146. package/edit/auth/azuread.vue +128 -95
  147. package/edit/auth/github.vue +72 -13
  148. package/edit/auth/ldap/__tests__/index.test.ts +206 -0
  149. package/edit/auth/ldap/config.vue +8 -0
  150. package/edit/auth/ldap/index.vue +75 -1
  151. package/edit/auth/oidc.vue +119 -73
  152. package/edit/auth/saml.vue +76 -12
  153. package/edit/catalog.cattle.io.clusterrepo.vue +140 -32
  154. package/edit/compliance.cattle.io.clusterscanprofile.vue +39 -41
  155. package/edit/fleet.cattle.io.gitrepo.vue +70 -16
  156. package/edit/fleet.cattle.io.helmop.vue +542 -141
  157. package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
  158. package/edit/{management.cattle.io.setting.vue → management.cattle.io.setting/index.vue} +32 -9
  159. package/edit/management.cattle.io.setting/system-default-registry-pull-secrets.vue +81 -0
  160. package/edit/management.cattle.io.user.vue +5 -2
  161. package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +3 -12
  162. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +18 -0
  163. package/edit/provisioning.cattle.io.cluster/rke2.vue +89 -11
  164. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
  165. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +0 -1
  166. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +14 -55
  167. package/list/group.principal.vue +5 -4
  168. package/list/harvesterhci.io.management.cluster.vue +8 -9
  169. package/list/management.cattle.io.user.vue +12 -9
  170. package/list/provisioning.cattle.io.cluster.vue +16 -10
  171. package/mixins/__tests__/auth-config.test.ts +90 -0
  172. package/mixins/__tests__/chart.test.ts +94 -0
  173. package/mixins/__tests__/resource-fetch-api-pagination.test.ts +48 -0
  174. package/mixins/auth-config.js +7 -0
  175. package/mixins/chart.js +11 -2
  176. package/mixins/child-hook.js +12 -6
  177. package/mixins/create-edit-view/impl.js +5 -3
  178. package/mixins/resource-fetch-api-pagination.js +21 -1
  179. package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +57 -0
  180. package/models/__tests__/compliance.cattle.io.clusterscan.test.ts +144 -0
  181. package/models/__tests__/fleet-application.test.ts +175 -0
  182. package/models/__tests__/fleet.cattle.io.bundle.test.ts +169 -0
  183. package/models/__tests__/fleet.cattle.io.helmop.test.ts +84 -0
  184. package/models/__tests__/management.cattle.io.node.ts +22 -0
  185. package/models/__tests__/namespace.test.ts +36 -0
  186. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +205 -0
  187. package/models/__tests__/secret.test.ts +68 -1
  188. package/models/__tests__/workload.test.ts +401 -26
  189. package/models/catalog.cattle.io.clusterrepo.js +28 -4
  190. package/models/compliance.cattle.io.clusterscan.js +39 -4
  191. package/models/fleet-application.js +4 -0
  192. package/models/fleet.cattle.io.helmop.js +20 -1
  193. package/models/management.cattle.io.cluster.js +39 -5
  194. package/models/management.cattle.io.node.js +44 -3
  195. package/models/namespace.js +1 -1
  196. package/models/pod.js +46 -3
  197. package/models/provisioning.cattle.io.cluster.js +64 -14
  198. package/models/rke.cattle.io.etcdsnapshot.js +17 -9
  199. package/models/secret.js +19 -0
  200. package/models/workload.js +120 -20
  201. package/models/workload.service.js +5 -0
  202. package/package.json +14 -13
  203. package/pages/about.vue +5 -6
  204. package/pages/auth/login.vue +0 -35
  205. package/pages/auth/setup.vue +11 -0
  206. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +2 -2
  207. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +10 -1
  208. package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +93 -0
  209. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +485 -107
  210. package/pages/c/_cluster/apps/charts/chart.vue +2 -1
  211. package/pages/c/_cluster/apps/charts/index.vue +48 -10
  212. package/pages/c/_cluster/apps/charts/install.vue +236 -144
  213. package/pages/c/_cluster/auth/roles/index.vue +5 -4
  214. package/pages/c/_cluster/explorer/workload-dashboard/ByNamespaceSection.vue +31 -0
  215. package/pages/c/_cluster/explorer/workload-dashboard/ByStateSection.vue +138 -0
  216. package/pages/c/_cluster/explorer/workload-dashboard/ByTypeSection.vue +30 -0
  217. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadCard.vue +155 -0
  218. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadNamespaceCard.vue +142 -0
  219. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadTypeCard.vue +159 -0
  220. package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +561 -0
  221. package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +440 -0
  222. package/pages/c/_cluster/explorer/workload-dashboard/index.vue +187 -0
  223. package/pages/c/_cluster/explorer/workload-dashboard/types.ts +80 -0
  224. package/pages/c/_cluster/fleet/application/create.vue +187 -136
  225. package/pages/c/_cluster/fleet/application/index.vue +5 -3
  226. package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailBody.vue +338 -0
  227. package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailHeader.vue +121 -0
  228. package/pages/c/_cluster/fleet/application/suse-app-collection/chart.vue +369 -0
  229. package/pages/c/_cluster/fleet/application/suse-app-collection/charts.vue +248 -0
  230. package/pages/c/_cluster/fleet/application/suse-app-collection/credentials.vue +310 -0
  231. package/pages/c/_cluster/fleet/index.vue +2 -2
  232. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +96 -0
  233. package/pages/c/_cluster/uiplugins/index.vue +15 -0
  234. package/pages/fail-whale.vue +16 -11
  235. package/pages/home.vue +16 -46
  236. package/pkg/require-asset.lib.js +25 -0
  237. package/pkg/vue.config.js +7 -0
  238. package/plugins/clean-html.d.ts +9 -0
  239. package/plugins/dashboard-store/__tests__/resource-class.test.ts +177 -0
  240. package/plugins/dashboard-store/getters.js +0 -1
  241. package/plugins/dashboard-store/resource-class.js +114 -19
  242. package/plugins/steve/__tests__/actions.test.ts +212 -0
  243. package/plugins/steve/actions.js +96 -0
  244. package/plugins/steve/steve-pagination-utils.ts +1 -1
  245. package/rancher-components/Accordion/Accordion.vue +53 -9
  246. package/rancher-components/Form/Checkbox/Checkbox.vue +14 -0
  247. package/rancher-components/Form/Radio/RadioButton.vue +17 -1
  248. package/rancher-components/Form/Radio/RadioGroup.vue +10 -0
  249. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +30 -0
  250. package/rancher-components/Form/TextArea/__tests__/TextAreaAutoGrow.test.ts +95 -0
  251. package/rancher-components/Pill/RcTag/RcTag.vue +3 -2
  252. package/rancher-components/RcButton/RcButton.test.ts +103 -0
  253. package/rancher-components/RcButton/RcButton.vue +94 -15
  254. package/rancher-components/RcButton/index.ts +1 -1
  255. package/rancher-components/RcButton/types.ts +3 -0
  256. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +6 -1
  257. package/rancher-components/RcItemCard/RcItemCard.test.ts +18 -0
  258. package/rancher-components/RcItemCard/RcItemCard.vue +2 -2
  259. package/rancher-components/RcSection/RcSection.vue +28 -3
  260. package/scripts/extension/helm/package/Dockerfile +1 -1
  261. package/scripts/test-plugins-build.sh +2 -1
  262. package/store/__tests__/features.test.ts +131 -0
  263. package/store/__tests__/growl.test.ts +374 -0
  264. package/store/__tests__/modal.test.ts +131 -0
  265. package/store/__tests__/notifications.test.ts +434 -0
  266. package/store/__tests__/slideInPanel.test.ts +88 -0
  267. package/store/__tests__/type-map.utils.test.ts +433 -0
  268. package/store/catalog.js +57 -0
  269. package/store/features.js +4 -0
  270. package/store/plugins.js +7 -4
  271. package/types/components/buttonGroup.ts +5 -0
  272. package/types/shell/index.d.ts +166 -70
  273. package/utils/__tests__/auth.test.ts +273 -0
  274. package/utils/__tests__/computed.test.ts +193 -0
  275. package/utils/__tests__/cspAdaptor.test.ts +163 -0
  276. package/utils/__tests__/dom.test.ts +81 -0
  277. package/utils/__tests__/duration.test.ts +37 -1
  278. package/utils/__tests__/dynamic-importer.test.ts +102 -0
  279. package/utils/__tests__/fleet-appco.test.ts +312 -0
  280. package/utils/__tests__/monitoring.test.ts +130 -0
  281. package/utils/__tests__/object.test.ts +22 -0
  282. package/utils/__tests__/operation-cr.test.ts +34 -0
  283. package/utils/__tests__/platform.test.ts +91 -0
  284. package/utils/__tests__/position.test.ts +237 -0
  285. package/utils/__tests__/provider.test.ts +51 -1
  286. package/utils/__tests__/queue.test.ts +232 -0
  287. package/utils/__tests__/release-notes.test.ts +221 -0
  288. package/utils/__tests__/router.test.js +254 -1
  289. package/utils/__tests__/select.test.ts +208 -0
  290. package/utils/__tests__/time.test.ts +265 -1
  291. package/utils/__tests__/title.test.ts +47 -0
  292. package/utils/__tests__/width.test.ts +53 -0
  293. package/utils/__tests__/window.test.ts +158 -0
  294. package/utils/__tests__/xccdf.test.ts +126 -6
  295. package/utils/crypto/__tests__/browserHashUtils.test.ts +98 -0
  296. package/utils/crypto/__tests__/index.test.ts +144 -0
  297. package/utils/duration.ts +104 -0
  298. package/utils/dynamic-content/__tests__/notification-handler.test.ts +196 -0
  299. package/utils/dynamic-content/info.ts +2 -1
  300. package/utils/error.js +13 -0
  301. package/utils/fleet-appco.ts +323 -0
  302. package/utils/object.js +22 -2
  303. package/utils/operation-cr.js +19 -0
  304. package/utils/provider.ts +12 -0
  305. package/utils/require-asset.ts +7 -0
  306. package/utils/validators/__tests__/container-images.test.ts +104 -0
  307. package/utils/validators/__tests__/flow-output.test.ts +91 -0
  308. package/utils/validators/__tests__/logging-outputs.test.ts +58 -0
  309. package/utils/validators/__tests__/monitoring-route.test.ts +119 -0
  310. package/utils/validators/__tests__/private-registry.test.ts +27 -15
  311. package/utils/validators/private-registry.ts +15 -4
  312. package/utils/xccdf.ts +39 -42
  313. package/vue.config.js +1 -1
  314. package/pages/support/index.vue +0 -264
  315. 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
+ });