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

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 (376) hide show
  1. package/apis/impl/apis.ts +6 -0
  2. package/apis/index.ts +26 -0
  3. package/apis/intf/resources-api/cluster-api.ts +18 -0
  4. package/apis/intf/resources-api/mgmt-api.ts +15 -0
  5. package/apis/intf/resources-api/resource-base.ts +107 -0
  6. package/apis/intf/resources-api/resource-constants.ts +147 -0
  7. package/apis/intf/resources-api/resources-api.ts +143 -0
  8. package/apis/intf/resources.ts +49 -0
  9. package/apis/intf/{modal.ts → shell-api/modal.ts} +21 -26
  10. package/apis/intf/shell-api/proxy.ts +216 -0
  11. package/apis/intf/{slide-in.ts → shell-api/slide-in.ts} +4 -3
  12. package/apis/intf/{system.ts → shell-api/system.ts} +4 -1
  13. package/apis/intf/shell.ts +12 -6
  14. package/apis/resources/__tests__/resources-api-class.test.ts +550 -0
  15. package/apis/resources/index.ts +22 -0
  16. package/apis/resources/resources-api-class.ts +187 -0
  17. package/apis/shell/__tests__/proxy.test.ts +369 -0
  18. package/apis/shell/index.ts +8 -1
  19. package/apis/shell/modal.ts +4 -1
  20. package/apis/shell/notifications.ts +9 -6
  21. package/apis/shell/proxy.ts +256 -0
  22. package/apis/shell/slide-in.ts +4 -1
  23. package/apis/vue-shim.d.ts +2 -1
  24. package/assets/data/aws-regions.json +4 -0
  25. package/assets/fonts/lato/LatoLatin-Black.woff +0 -0
  26. package/assets/fonts/lato/LatoLatin-Black.woff2 +0 -0
  27. package/assets/fonts/lato/LatoLatin-BlackItalic.woff +0 -0
  28. package/assets/fonts/lato/LatoLatin-BlackItalic.woff2 +0 -0
  29. package/assets/fonts/lato/LatoLatin-Bold.woff +0 -0
  30. package/assets/fonts/lato/LatoLatin-Bold.woff2 +0 -0
  31. package/assets/fonts/lato/LatoLatin-BoldItalic.woff +0 -0
  32. package/assets/fonts/lato/LatoLatin-BoldItalic.woff2 +0 -0
  33. package/assets/fonts/lato/LatoLatin-Heavy.woff +0 -0
  34. package/assets/fonts/lato/LatoLatin-Heavy.woff2 +0 -0
  35. package/assets/fonts/lato/LatoLatin-HeavyItalic.woff +0 -0
  36. package/assets/fonts/lato/LatoLatin-HeavyItalic.woff2 +0 -0
  37. package/assets/fonts/lato/LatoLatin-Italic.woff +0 -0
  38. package/assets/fonts/lato/LatoLatin-Italic.woff2 +0 -0
  39. package/assets/fonts/lato/LatoLatin-Light.woff +0 -0
  40. package/assets/fonts/lato/LatoLatin-Light.woff2 +0 -0
  41. package/assets/fonts/lato/LatoLatin-LightItalic.woff +0 -0
  42. package/assets/fonts/lato/LatoLatin-LightItalic.woff2 +0 -0
  43. package/assets/fonts/lato/LatoLatin-Medium.woff +0 -0
  44. package/assets/fonts/lato/LatoLatin-Medium.woff2 +0 -0
  45. package/assets/fonts/lato/LatoLatin-MediumItalic.woff +0 -0
  46. package/assets/fonts/lato/LatoLatin-MediumItalic.woff2 +0 -0
  47. package/assets/fonts/lato/LatoLatin-Regular.woff +0 -0
  48. package/assets/fonts/lato/LatoLatin-Regular.woff2 +0 -0
  49. package/assets/fonts/lato/LatoLatin-Semibold.woff +0 -0
  50. package/assets/fonts/lato/LatoLatin-Semibold.woff2 +0 -0
  51. package/assets/fonts/lato/LatoLatin-SemiboldItalic.woff +0 -0
  52. package/assets/fonts/lato/LatoLatin-SemiboldItalic.woff2 +0 -0
  53. package/assets/images/providers/entraid-black.svg +4 -0
  54. package/assets/images/providers/entraid.svg +9 -0
  55. package/assets/images/vendor/entraid.svg +9 -0
  56. package/assets/styles/app.scss +0 -1
  57. package/assets/styles/base/_variables.scss +2 -0
  58. package/assets/styles/fonts/_fontstack.scss +132 -8
  59. package/assets/translations/en-us.yaml +41 -22
  60. package/assets/translations/zh-hans.yaml +4 -8
  61. package/chart/__tests__/S3.test.ts +10 -3
  62. package/chart/monitoring/index.vue +10 -1
  63. package/components/ActionDropdownShell.vue +2 -1
  64. package/components/CountBox.vue +20 -0
  65. package/components/CreateDriver.vue +0 -12
  66. package/components/CruResourceFooter.vue +9 -5
  67. package/components/DetailText.vue +12 -3
  68. package/components/ExplorerProjectsNamespaces.vue +1 -1
  69. package/components/InstallHelmCharts.vue +2 -2
  70. package/components/LandingPagePreference.vue +14 -5
  71. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +15 -1
  72. package/components/Resource/Detail/Metadata/index.vue +6 -0
  73. package/components/Resource/Detail/ResourcePopover/index.vue +12 -1
  74. package/components/Resource/Detail/SpacedRow.vue +3 -1
  75. package/components/Resource/Detail/TitleBar/index.vue +10 -11
  76. package/components/ResourceList/Masthead.vue +12 -8
  77. package/components/SelectIconGrid.vue +5 -10
  78. package/components/SingleClusterInfo.vue +1 -0
  79. package/components/SortableTable/__tests__/sorting.test.ts +126 -0
  80. package/components/SortableTable/index.vue +6 -9
  81. package/components/SortableTable/selection.js +23 -5
  82. package/components/SortableTable/sorting.js +6 -3
  83. package/components/Wizard.vue +14 -13
  84. package/components/__tests__/CountBox.test.ts +72 -0
  85. package/components/__tests__/DetailText.test.ts +113 -0
  86. package/components/fleet/FleetBundles.vue +100 -12
  87. package/components/fleet/FleetClusterTargets/index.vue +54 -15
  88. package/components/fleet/__tests__/FleetClusterTargets.test.ts +149 -115
  89. package/components/fleet/__tests__/FleetClusters.test.ts +12 -12
  90. package/components/form/InputWithSelect.vue +18 -10
  91. package/components/form/KeyValue.vue +17 -1
  92. package/components/form/LabeledSelect.vue +101 -26
  93. package/components/form/NameNsDescription.vue +11 -0
  94. package/components/form/Security.vue +6 -2
  95. package/components/form/Select.vue +73 -56
  96. package/components/form/ServiceNameSelect.vue +13 -11
  97. package/components/form/WorkloadPorts.vue +2 -7
  98. package/components/form/__tests__/KeyValue.test.ts +66 -0
  99. package/components/form/__tests__/NodeScheduling.test.ts +9 -0
  100. package/components/form/__tests__/Security.test.ts +76 -0
  101. package/components/form/labeled-select-utils/useLabeledSelectPagination.ts +138 -0
  102. package/components/formatter/Autoscaler.vue +4 -4
  103. package/components/formatter/ClusterKubeVersion.vue +27 -0
  104. package/components/formatter/ClusterLink.vue +1 -7
  105. package/components/formatter/ClusterProvider.vue +6 -10
  106. package/components/formatter/FleetSummaryGraph.vue +0 -3
  107. package/components/formatter/MachineSummaryGraph.vue +1 -1
  108. package/components/formatter/PodsUsage.vue +2 -2
  109. package/components/formatter/__tests__/Autoscaler.test.ts +19 -22
  110. package/components/formatter/__tests__/FleetSummaryGraph.test.ts +216 -0
  111. package/components/formatter/__tests__/PodsUsage.test.ts +6 -10
  112. package/components/nav/Group.vue +7 -6
  113. package/components/nav/Header.vue +24 -3
  114. package/components/nav/NamespaceFilter.vue +2 -2
  115. package/components/nav/NotificationCenter/Notification.vue +4 -1
  116. package/components/nav/NotificationCenter/NotificationHeader.vue +20 -8
  117. package/components/nav/NotificationCenter/__tests__/NotificationHeader.test.ts +80 -0
  118. package/components/nav/TopLevelMenu.helper.ts +15 -3
  119. package/components/nav/TopLevelMenu.vue +16 -5
  120. package/components/nav/Type.vue +8 -7
  121. package/components/nav/WindowManager/index.vue +2 -1
  122. package/components/nav/WorkspaceSwitcher.vue +13 -0
  123. package/components/nav/__tests__/Group.test.ts +67 -0
  124. package/components/nav/__tests__/Header.test.ts +235 -0
  125. package/components/nav/__tests__/TopLevelMenu.test.ts +145 -21
  126. package/components/nav/__tests__/Type.test.ts +20 -3
  127. package/components/templates/default.vue +34 -4
  128. package/components/templates/home.vue +30 -25
  129. package/components/templates/plain.vue +31 -26
  130. package/components/templates/standalone.vue +17 -0
  131. package/composables/useFormValidation.ts +93 -0
  132. package/composables/useLabeledFormElement.ts +10 -2
  133. package/composables/useLabeledSelect.ts +60 -0
  134. package/composables/useUserRetentionValidation.ts +1 -49
  135. package/composables/useVeeValidateField.test.ts +159 -0
  136. package/composables/useVeeValidateField.ts +67 -0
  137. package/config/cookies.js +0 -1
  138. package/config/labels-annotations.js +1 -0
  139. package/config/pagination-table-headers.js +18 -1
  140. package/config/product/manager.js +82 -21
  141. package/config/query-params.js +1 -0
  142. package/config/router/routes.js +6 -8
  143. package/config/table-headers.js +20 -1
  144. package/config/types.js +2 -1
  145. package/core/__tests__/plugin-products.test.ts +1505 -30
  146. package/core/plugin-products-base.ts +137 -20
  147. package/core/plugin-products-helpers.ts +5 -4
  148. package/core/plugin-products.ts +4 -0
  149. package/core/plugin-types.ts +129 -4
  150. package/core/plugin.ts +15 -7
  151. package/core/productDebugger.js +9 -4
  152. package/core/types-provisioning.ts +43 -30
  153. package/core/types.ts +58 -19
  154. package/detail/__tests__/management.cattle.io.fleetworkspace.test.ts +128 -0
  155. package/detail/__tests__/pod.test.ts +41 -0
  156. package/detail/harvesterhci.io.management.cluster.vue +6 -2
  157. package/detail/management.cattle.io.fleetworkspace.vue +49 -0
  158. package/detail/pod.vue +1 -1
  159. package/detail/provisioning.cattle.io.cluster.vue +4 -10
  160. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +9 -0
  161. package/edit/__tests__/kontainerDriver.test.ts +0 -13
  162. package/edit/__tests__/nodeDriver.test.ts +5 -11
  163. package/edit/__tests__/resources.cattle.io.restore.test.ts +9 -0
  164. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
  165. package/edit/auth/__tests__/azuread.test.ts +217 -34
  166. package/edit/auth/__tests__/oidc.test.ts +54 -0
  167. package/edit/auth/azuread.vue +123 -15
  168. package/edit/auth/oidc.vue +10 -2
  169. package/edit/kontainerDriver.vue +1 -2
  170. package/edit/networking.k8s.io.ingress/DefaultBackend.vue +13 -4
  171. package/edit/networking.k8s.io.ingress/RulePath.vue +8 -4
  172. package/edit/networking.k8s.io.ingress/index.vue +75 -20
  173. package/edit/nodeDriver.vue +0 -2
  174. package/edit/provisioning.cattle.io.cluster/AgentEnv.vue +1 -0
  175. package/edit/provisioning.cattle.io.cluster/__tests__/AgentEnv.test.ts +25 -0
  176. package/edit/provisioning.cattle.io.cluster/__tests__/MachinePool.test.ts +104 -0
  177. package/edit/provisioning.cattle.io.cluster/index.vue +81 -106
  178. package/edit/provisioning.cattle.io.cluster/rke2.vue +8 -4
  179. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
  180. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +37 -4
  181. package/edit/provisioning.cattle.io.cluster/tabs/registries/__tests__/RegistryConfigs.test.ts +132 -7
  182. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +2 -1
  183. package/edit/secret/__tests__/ssh.test.ts +5 -6
  184. package/edit/secret/basic.vue +31 -0
  185. package/edit/secret/index.vue +68 -17
  186. package/edit/secret/registry.vue +38 -0
  187. package/edit/secret/ssh.vue +29 -0
  188. package/edit/secret/tls.vue +30 -0
  189. package/edit/service.vue +4 -4
  190. package/edit/workload/Upgrading.vue +3 -3
  191. package/edit/workload/__tests__/Upgrading.test.ts +6 -9
  192. package/edit/workload/mixins/workload.js +2 -1
  193. package/initialize/App.vue +29 -2
  194. package/initialize/install-plugins.js +0 -2
  195. package/list/__tests__/management.cattle.io.feature.test.ts +105 -0
  196. package/list/catalog.cattle.io.app.vue +25 -5
  197. package/list/fleet.cattle.io.bundle.vue +7 -104
  198. package/list/fleet.cattle.io.clusterregistrationtoken.vue +20 -0
  199. package/list/management.cattle.io.feature.vue +1 -1
  200. package/list/management.cattle.io.fleetworkspace.vue +8 -0
  201. package/list/provisioning.cattle.io.cluster.vue +262 -180
  202. package/list/utils/management.cattle.io.cluster.utils.ts +128 -0
  203. package/machine-config/amazonec2.vue +1 -0
  204. package/mixins/__tests__/chart.test.ts +112 -0
  205. package/mixins/brand.js +2 -1
  206. package/mixins/chart.js +50 -15
  207. package/mixins/resource-fetch-api-pagination.js +41 -5
  208. package/models/__tests__/catalog.cattle.io.app.test.ts +15 -1
  209. package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +84 -0
  210. package/models/__tests__/chart.test.ts +99 -6
  211. package/models/__tests__/ext.cattle.io.kubeconfig.test.ts +67 -67
  212. package/models/__tests__/management.cattle.io.cluster.test.ts +1 -1
  213. package/models/__tests__/management.cattle.io.feature.test.ts +131 -0
  214. package/models/__tests__/management.cattle.io.node.ts +6 -5
  215. package/models/__tests__/management.cattle.io.nodepool.ts +5 -4
  216. package/models/__tests__/monitoring.coreos.com.alertmanagerconfig.test.ts +98 -0
  217. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +32 -11
  218. package/models/base-cluster.x-k8s.io.js +26 -0
  219. package/models/catalog.cattle.io.app.js +21 -17
  220. package/models/catalog.cattle.io.clusterrepo.js +39 -11
  221. package/models/chart.js +33 -19
  222. package/models/cluster.js +1 -1
  223. package/models/cluster.x-k8s.io.machine.js +4 -22
  224. package/models/cluster.x-k8s.io.machinedeployment.js +2 -20
  225. package/models/cluster.x-k8s.io.machineset.js +2 -20
  226. package/models/compliance.cattle.io.clusterscan.js +130 -2
  227. package/models/ext.cattle.io.kubeconfig.ts +4 -7
  228. package/models/fleet-application.js +4 -2
  229. package/models/fleet.cattle.io.bundle.js +1 -1
  230. package/models/kontainerdriver.js +11 -0
  231. package/models/management.cattle.io.authconfig.js +5 -1
  232. package/models/management.cattle.io.cluster.js +402 -78
  233. package/models/management.cattle.io.feature.js +3 -3
  234. package/models/management.cattle.io.kontainerdriver.js +1 -26
  235. package/models/management.cattle.io.node.js +6 -4
  236. package/models/management.cattle.io.nodepool.js +1 -1
  237. package/models/monitoring.coreos.com.alertmanagerconfig.js +31 -17
  238. package/models/networking.k8s.io.ingress.js +12 -4
  239. package/models/nodedriver.js +7 -0
  240. package/models/provisioning.cattle.io.cluster.js +47 -330
  241. package/models/rke.cattle.io.etcdsnapshot.js +1 -2
  242. package/package.json +20 -37
  243. package/pages/__tests__/readme.test.ts +49 -0
  244. package/pages/auth/setup.vue +2 -3
  245. package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +265 -0
  246. package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +55 -0
  247. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +53 -0
  248. package/pages/c/_cluster/apps/charts/chart.vue +275 -39
  249. package/pages/c/_cluster/apps/charts/index.vue +2 -2
  250. package/pages/c/_cluster/apps/charts/install.vue +18 -10
  251. package/pages/c/_cluster/auth/user.retention/index.vue +55 -22
  252. package/pages/c/_cluster/explorer/__tests__/index.test.ts +23 -25
  253. package/pages/c/_cluster/explorer/index.vue +5 -49
  254. package/pages/c/_cluster/istio/__tests__/istio.index.test.ts +194 -0
  255. package/pages/c/_cluster/istio/index.vue +21 -6
  256. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -7
  257. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +40 -2
  258. package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +61 -0
  259. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +735 -13
  260. package/pages/c/_cluster/uiplugins/index.vue +226 -222
  261. package/pages/diagnostic.vue +13 -17
  262. package/pages/fail-whale.vue +18 -0
  263. package/pages/home.vue +77 -260
  264. package/pages/readme.vue +88 -0
  265. package/plugins/dashboard-store/__tests__/resource-class.test.ts +88 -0
  266. package/plugins/dashboard-store/actions.js +40 -18
  267. package/plugins/dashboard-store/resource-class.js +5 -2
  268. package/plugins/steve/__tests__/subscribe.spec.ts +6 -3
  269. package/plugins/steve/steve-pagination-utils.ts +11 -3
  270. package/plugins/steve/subscribe.js +35 -5
  271. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +211 -1
  272. package/rancher-components/Form/LabeledInput/LabeledInput.vue +37 -4
  273. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +1 -1
  274. package/rancher-components/RcButton/RcButton.test.ts +37 -1
  275. package/rancher-components/RcButton/RcButton.vue +38 -8
  276. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +10 -8
  277. package/scripts/test-plugins-build.sh +5 -2
  278. package/server/server-middleware.js +2 -2
  279. package/static/humans.txt +1 -0
  280. package/static/robots.txt +34 -0
  281. package/static/welcome-cow.svg +18 -0
  282. package/store/__tests__/catalog.test.ts +276 -12
  283. package/store/__tests__/type-map.test.ts +556 -1
  284. package/store/action-menu.js +8 -3
  285. package/store/auth.js +1 -4
  286. package/store/aws.js +27 -16
  287. package/store/catalog.js +87 -11
  288. package/store/digitalocean.js +20 -38
  289. package/store/index.js +2 -0
  290. package/store/linode.js +25 -40
  291. package/store/pnap.js +1 -0
  292. package/store/type-map.js +111 -29
  293. package/tsconfig.paths.json +8 -8
  294. package/types/kube/kube-api.ts +14 -1
  295. package/types/rancher/steve.api.ts +12 -12
  296. package/types/resources/settings.d.ts +2 -1
  297. package/types/shell/index.d.ts +128 -24
  298. package/types/store/dashboard-store.types.ts +108 -11
  299. package/types/store/pagination.types.ts +6 -3
  300. package/utils/__tests__/alertmanagerconfig.test.ts +117 -0
  301. package/utils/__tests__/async.test.ts +87 -0
  302. package/utils/__tests__/aws.test.ts +140 -0
  303. package/utils/__tests__/banners.test.ts +176 -0
  304. package/utils/__tests__/chart.test.ts +64 -1
  305. package/utils/__tests__/color.test.ts +226 -0
  306. package/utils/__tests__/duration.test.ts +140 -0
  307. package/utils/__tests__/fleet.test.ts +340 -0
  308. package/utils/__tests__/git.test.ts +270 -0
  309. package/utils/__tests__/inactivity.test.ts +316 -0
  310. package/utils/__tests__/ingress.test.ts +553 -0
  311. package/utils/__tests__/kube.test.ts +68 -0
  312. package/utils/__tests__/namespace-filter.test.ts +109 -0
  313. package/utils/__tests__/object.test.ts +77 -0
  314. package/utils/__tests__/pagination-utils.test.ts +361 -0
  315. package/utils/__tests__/parse-externalid.test.ts +137 -0
  316. package/utils/__tests__/perf-setting.utils.test.ts +98 -0
  317. package/utils/__tests__/poller-sequential.test.ts +177 -0
  318. package/utils/__tests__/poller.test.ts +170 -0
  319. package/utils/__tests__/promise.test.ts +346 -0
  320. package/utils/__tests__/settings.test.ts +140 -0
  321. package/utils/__tests__/sort-utils.test.ts +301 -0
  322. package/utils/__tests__/string-utils.test.ts +798 -0
  323. package/utils/__tests__/string.test.ts +23 -1
  324. package/utils/__tests__/style.test.ts +154 -0
  325. package/utils/__tests__/svg-filter.test.ts +184 -0
  326. package/utils/__tests__/time.test.ts +14 -1
  327. package/utils/__tests__/units.test.ts +417 -0
  328. package/utils/__tests__/url.test.ts +246 -0
  329. package/utils/__tests__/versions.test.ts +128 -0
  330. package/utils/__tests__/xccdf.test.ts +391 -0
  331. package/utils/chart.js +36 -0
  332. package/utils/fleet.ts +13 -3
  333. package/utils/gatekeeper/__tests__/util.test.ts +174 -0
  334. package/utils/gc/__tests__/gc-interval.test.ts +119 -0
  335. package/utils/gc/__tests__/gc-root-store.test.ts +225 -0
  336. package/utils/gc/__tests__/gc-route-changed.test.ts +96 -0
  337. package/utils/gc/__tests__/gc.test.ts +487 -0
  338. package/utils/ingress.ts +9 -1
  339. package/utils/object.js +33 -2
  340. package/utils/pagination-utils.ts +2 -1
  341. package/utils/string.js +25 -2
  342. package/utils/time.ts +5 -0
  343. package/utils/uiplugins.ts +5 -5
  344. package/utils/validators/__tests__/cluster-name.test.ts +110 -0
  345. package/utils/validators/__tests__/cron-schedule.test.ts +79 -0
  346. package/utils/validators/__tests__/index.test.ts +481 -0
  347. package/utils/validators/__tests__/kubernetes-name.test.ts +163 -0
  348. package/utils/validators/__tests__/misc-validators.test.ts +246 -0
  349. package/utils/validators/__tests__/pod-affinity.test.ts +382 -0
  350. package/utils/validators/__tests__/prometheusrule.test.ts +211 -0
  351. package/utils/validators/__tests__/role-template.test.ts +149 -0
  352. package/utils/validators/__tests__/service.test.ts +283 -0
  353. package/utils/validators/__tests__/setting.test.js +32 -0
  354. package/utils/validators/formRules/__tests__/index.test.ts +50 -0
  355. package/utils/validators/formRules/index.ts +5 -5
  356. package/utils/validators/machine-pool.ts +1 -1
  357. package/utils/validators/setting.js +18 -3
  358. package/utils/xccdf.ts +418 -0
  359. package/vue.config.js +0 -9
  360. package/assets/fonts/lato/lato-v17-latin-700.woff +0 -0
  361. package/assets/fonts/lato/lato-v17-latin-700.woff2 +0 -0
  362. package/assets/fonts/lato/lato-v17-latin-regular.woff +0 -0
  363. package/assets/fonts/lato/lato-v17-latin-regular.woff2 +0 -0
  364. package/assets/images/providers/azuread-black.svg +0 -22
  365. package/assets/images/providers/azuread.svg +0 -25
  366. package/assets/images/vendor/azuread.svg +0 -18
  367. package/assets/styles/fonts/_dots.scss +0 -18
  368. package/components/EmberPage.vue +0 -622
  369. package/components/EmberPageView.vue +0 -39
  370. package/components/form/labeled-select-utils/labeled-select-pagination.ts +0 -116
  371. package/mixins/labeled-form-element.ts +0 -225
  372. package/pages/c/_cluster/explorer/tools/pages/_page.vue +0 -28
  373. package/pages/c/_cluster/manager/pages/_page.vue +0 -22
  374. package/pages/c/_cluster/mcapps/pages/_page.vue +0 -22
  375. package/plugins/ember-cookie.js +0 -17
  376. package/utils/ember-page.js +0 -30
@@ -0,0 +1,417 @@
1
+ import {
2
+ exponentNeeded,
3
+ formatSi,
4
+ parseSi,
5
+ createMemoryFormat,
6
+ createMemoryValues,
7
+ UNITS,
8
+ FRACTIONAL,
9
+ MEMORY_PARSE_RULES,
10
+ } from '@shell/utils/units';
11
+
12
+ describe('units', () => {
13
+ describe('exported constants', () => {
14
+ it('exports expected UNITS prefixes', () => {
15
+ expect(UNITS).toStrictEqual(['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']);
16
+ });
17
+
18
+ it('exports expected FRACTIONAL prefixes', () => {
19
+ expect(FRACTIONAL).toStrictEqual(['', 'm', 'u', 'n', 'p', 'f', 'a', 'z', 'y']);
20
+ });
21
+ });
22
+
23
+ describe('exponentNeeded', () => {
24
+ it.each([
25
+ {
26
+ desc: 'returns 0 for value below increment',
27
+ value: 999,
28
+ expected: 0,
29
+ },
30
+ {
31
+ desc: 'returns 1 for value equal to increment',
32
+ value: 1000,
33
+ expected: 1,
34
+ },
35
+ {
36
+ desc: 'returns 2 for value 1_000_000',
37
+ value: 1_000_000,
38
+ expected: 2,
39
+ },
40
+ {
41
+ desc: 'returns 3 for value 1_000_000_000',
42
+ value: 1_000_000_000,
43
+ expected: 3,
44
+ },
45
+ {
46
+ desc: 'uses custom increment when provided',
47
+ value: 1024,
48
+ increment: 1024,
49
+ expected: 1,
50
+ },
51
+ {
52
+ desc: 'returns 0 for value 0',
53
+ value: 0,
54
+ expected: 0,
55
+ },
56
+ {
57
+ desc: 'returns 0 for value 1',
58
+ value: 1,
59
+ expected: 0,
60
+ },
61
+ ])('$desc', ({ value, increment, expected }) => {
62
+ expect(exponentNeeded(value, increment)).toStrictEqual(expected);
63
+ });
64
+ });
65
+
66
+ describe('formatSi', () => {
67
+ describe('default options (increment=1000)', () => {
68
+ it.each([
69
+ {
70
+ desc: 'formats 0',
71
+ input: 0,
72
+ expected: '0 ',
73
+ },
74
+ {
75
+ desc: 'formats 999',
76
+ input: 999,
77
+ expected: '999 ',
78
+ },
79
+ {
80
+ desc: 'formats 1000 as 1 K',
81
+ input: 1000,
82
+ expected: '1 K',
83
+ },
84
+ {
85
+ desc: 'formats 1500 as 1.5 K',
86
+ input: 1500,
87
+ expected: '1.5 K',
88
+ },
89
+ {
90
+ desc: 'formats 1_000_000 as 1 M',
91
+ input: 1_000_000,
92
+ expected: '1 M',
93
+ },
94
+ {
95
+ desc: 'formats 1_500_000 as 1.5 M',
96
+ input: 1_500_000,
97
+ expected: '1.5 M',
98
+ },
99
+ {
100
+ desc: 'formats 1_000_000_000 as 1 G',
101
+ input: 1_000_000_000,
102
+ expected: '1 G',
103
+ },
104
+ {
105
+ desc: 'formats 1_000_000_000_000 as 1 T',
106
+ input: 1_000_000_000_000,
107
+ expected: '1 T',
108
+ },
109
+ {
110
+ desc: 'formats with 2 decimal precision for values < 10',
111
+ input: 1234,
112
+ expected: '1.23 K',
113
+ },
114
+ ])('$desc', ({ input, expected }) => {
115
+ expect(formatSi(input)).toStrictEqual(expected);
116
+ });
117
+ });
118
+
119
+ describe('suffix options', () => {
120
+ it.each([
121
+ {
122
+ desc: 'appends suffix',
123
+ input: 1000,
124
+ options: { suffix: 'B' },
125
+ expected: '1 KB',
126
+ },
127
+ {
128
+ desc: 'uses firstSuffix for exponent 0',
129
+ input: 500,
130
+ options: { suffix: 'B', firstSuffix: 'B' },
131
+ expected: '500 B',
132
+ },
133
+ {
134
+ desc: 'does not use firstSuffix when exponent > 0',
135
+ input: 1000,
136
+ options: { suffix: 'B', firstSuffix: 'B' },
137
+ expected: '1 KB',
138
+ },
139
+ {
140
+ desc: 'omits suffix space when addSuffixSpace=false',
141
+ input: 1000,
142
+ options: { addSuffixSpace: false, suffix: 'B' },
143
+ expected: '1KB',
144
+ },
145
+ {
146
+ desc: 'omits suffix entirely when addSuffix=false',
147
+ input: 1000,
148
+ options: { addSuffix: false },
149
+ expected: '1',
150
+ },
151
+ ])('$desc', ({ input, options, expected }) => {
152
+ expect(formatSi(input, options)).toStrictEqual(expected);
153
+ });
154
+ });
155
+
156
+ describe('increment=1024', () => {
157
+ it.each([
158
+ {
159
+ desc: 'formats 1024 as 1 KiB',
160
+ input: 1024,
161
+ options: {
162
+ increment: 1024,
163
+ suffix: 'iB',
164
+ firstSuffix: 'B',
165
+ },
166
+ expected: '1 KiB',
167
+ },
168
+ {
169
+ desc: 'formats 1048576 as 1 MiB',
170
+ input: 1048576,
171
+ options: { increment: 1024, suffix: 'iB' },
172
+ expected: '1 MiB',
173
+ },
174
+ ])('$desc', ({ input, options, expected }) => {
175
+ expect(formatSi(input, options)).toStrictEqual(expected);
176
+ });
177
+ });
178
+
179
+ describe('minExponent / maxExponent', () => {
180
+ it.each([
181
+ {
182
+ desc: 'respects minExponent to force higher unit',
183
+ input: 500,
184
+ options: { minExponent: 1, addSuffix: false },
185
+ expected: '0.5',
186
+ },
187
+ {
188
+ desc: 'respects maxExponent to cap scaling',
189
+ input: 1_000_000,
190
+ options: { maxExponent: 1, addSuffix: false },
191
+ expected: '1000',
192
+ },
193
+ ])('$desc', ({ input, options, expected }) => {
194
+ expect(formatSi(input, options)).toStrictEqual(expected);
195
+ });
196
+ });
197
+
198
+ describe('maxPrecision', () => {
199
+ it.each([
200
+ {
201
+ desc: 'uses integer for maxPrecision=0',
202
+ input: 1500,
203
+ options: { maxPrecision: 0 },
204
+ expected: '2 K',
205
+ },
206
+ {
207
+ desc: 'uses 1 decimal for maxPrecision=1',
208
+ input: 1500,
209
+ options: { maxPrecision: 1 },
210
+ expected: '1.5 K',
211
+ },
212
+ ])('$desc', ({ input, options, expected }) => {
213
+ expect(formatSi(input, options)).toStrictEqual(expected);
214
+ });
215
+ });
216
+
217
+ describe('canRoundToZero=false', () => {
218
+ it('avoids returning "0" by adjusting exponent', () => {
219
+ // 1 byte formatted as KiB would be 0, but canRoundToZero=false forces min representation
220
+ const result = formatSi(1, {
221
+ increment: 1024,
222
+ canRoundToZero: false,
223
+ suffix: 'iB',
224
+ firstSuffix: 'B',
225
+ });
226
+
227
+ expect(result).not.toStrictEqual('0 ');
228
+ expect(result).toStrictEqual('1 B');
229
+ });
230
+ });
231
+
232
+ describe('startingExponent', () => {
233
+ it('starts from specified exponent', () => {
234
+ // startingExponent=1 means value is already in K
235
+ expect(formatSi(1, { startingExponent: 1 })).toStrictEqual('1 K');
236
+ });
237
+ });
238
+ });
239
+
240
+ describe('parseSi', () => {
241
+ it.each([
242
+ {
243
+ desc: 'parses 1K to 1000',
244
+ input: '1K',
245
+ expected: 1000,
246
+ },
247
+ {
248
+ desc: 'parses 1M to 1_000_000',
249
+ input: '1M',
250
+ expected: 1_000_000,
251
+ },
252
+ {
253
+ desc: 'parses 1G to 1_000_000_000',
254
+ input: '1G',
255
+ expected: 1_000_000_000,
256
+ },
257
+ {
258
+ desc: 'parses 1T to 1_000_000_000_000',
259
+ input: '1T',
260
+ expected: 1_000_000_000_000,
261
+ },
262
+ {
263
+ desc: 'parses plain number string',
264
+ input: '500',
265
+ expected: 500,
266
+ },
267
+ {
268
+ desc: 'parses decimal number string',
269
+ input: '1.5K',
270
+ expected: 1500,
271
+ },
272
+ {
273
+ desc: 'parses KiB with increment=1024',
274
+ input: '1Ki',
275
+ expected: 1024,
276
+ },
277
+ {
278
+ desc: 'parses MiB with increment=1024',
279
+ input: '1Mi',
280
+ expected: 1048576,
281
+ },
282
+ {
283
+ desc: 'strips commas from numeric strings',
284
+ input: '1,000',
285
+ expected: 1000,
286
+ },
287
+ {
288
+ desc: 'ignores unrecognized unit suffix',
289
+ input: '100X',
290
+ expected: 100,
291
+ },
292
+ ])('$desc', ({ input, expected }) => {
293
+ expect(parseSi(input)).toStrictEqual(expected);
294
+ });
295
+
296
+ it.each([
297
+ {
298
+ desc: 'empty string',
299
+ input: '',
300
+ },
301
+ {
302
+ desc: 'null',
303
+ input: null as any,
304
+ },
305
+ {
306
+ desc: 'undefined',
307
+ input: undefined as any,
308
+ },
309
+ {
310
+ desc: 'non-string input',
311
+ input: 123 as any,
312
+ },
313
+ ])('returns NaN for $desc', ({ input }) => {
314
+ expect(parseSi(input)).toStrictEqual(NaN);
315
+ });
316
+
317
+ it('parses fractional milli unit (m)', () => {
318
+ // 1m = 1 * 1/1000 = 0.001
319
+ expect(parseSi('1m')).toBeCloseTo(0.001);
320
+ });
321
+
322
+ it('falls back to multiply path when allowFractional=false and unit matches UNITS uppercase', () => {
323
+ // 'm'.toUpperCase() === 'M' is in UNITS (Mega), so parsed as 1 * 1000^2 = 1_000_000
324
+ expect(parseSi('1m', { allowFractional: false })).toStrictEqual(1_000_000);
325
+ });
326
+
327
+ it('uses custom increment option', () => {
328
+ expect(parseSi('1K', { increment: 1024 })).toStrictEqual(1024);
329
+ });
330
+
331
+ it('handles micro symbol (µ) as u', () => {
332
+ // charCode 181 is µ, should be treated as 'u' (micro)
333
+ const mu = String.fromCharCode(181);
334
+
335
+ expect(parseSi(`1${ mu }`)).toBeCloseTo(0.000001);
336
+ });
337
+ });
338
+
339
+ describe('createMemoryFormat', () => {
340
+ it.each([
341
+ {
342
+ desc: 'locks exponent for 1 KiB',
343
+ value: 1024,
344
+ expected: 1,
345
+ },
346
+ {
347
+ desc: 'sets GiB exponent for 1 GiB',
348
+ value: 1024 * 1024 * 1024,
349
+ expected: 3,
350
+ },
351
+ {
352
+ desc: 'sets byte exponent for 0',
353
+ value: 0,
354
+ expected: 0,
355
+ },
356
+ ])('$desc', ({ value, expected }) => {
357
+ const fmt = createMemoryFormat(value);
358
+
359
+ expect(fmt.maxExponent).toStrictEqual(expected);
360
+ expect(fmt.minExponent).toStrictEqual(expected);
361
+ });
362
+
363
+ it('includes all MEMORY_PARSE_RULES format properties', () => {
364
+ const base = MEMORY_PARSE_RULES.memory.format;
365
+ const fmt = createMemoryFormat(1024);
366
+
367
+ expect(fmt.increment).toStrictEqual(base.increment);
368
+ expect(fmt.addSuffix).toStrictEqual(base.addSuffix);
369
+ expect(fmt.firstSuffix).toStrictEqual(base.firstSuffix);
370
+ expect(fmt.suffix).toStrictEqual(base.suffix);
371
+ expect(fmt.maxPrecision).toStrictEqual(base.maxPrecision);
372
+ });
373
+ });
374
+
375
+ describe('createMemoryValues', () => {
376
+ it('converts bytes total and useful to same unit', () => {
377
+ const result = createMemoryValues('1073741824', '536870912'); // 1GiB total, 512MiB useful
378
+
379
+ expect(result.total).toStrictEqual(1);
380
+ expect(result.useful).toStrictEqual(0.5);
381
+ expect(result.units).toStrictEqual('GiB');
382
+ });
383
+
384
+ it('handles 0 total and useful', () => {
385
+ const result = createMemoryValues('0', '0');
386
+
387
+ expect(result.total).toStrictEqual(0);
388
+ expect(result.useful).toStrictEqual(0);
389
+ // UNITS[0] = '' + suffix 'iB' = 'iB'
390
+ expect(result.units).toStrictEqual('iB');
391
+ });
392
+
393
+ it('handles null/undefined by treating as 0', () => {
394
+ const result = createMemoryValues(null as any, undefined as any);
395
+
396
+ expect(result.total).toStrictEqual(0);
397
+ expect(result.useful).toStrictEqual(0);
398
+ expect(result.units).toStrictEqual('iB');
399
+ });
400
+
401
+ it('accepts numeric strings with Si notation', () => {
402
+ const result = createMemoryValues('1Ki', '512');
403
+
404
+ expect(result.units).toStrictEqual('KiB');
405
+ expect(result.total).toStrictEqual(1);
406
+ expect(result.useful).toStrictEqual(0.5);
407
+ });
408
+
409
+ it('rounds to 2 decimal places', () => {
410
+ // 1.005 * 1024 = 1029.12 bytes, expressed in KiB = ~1.005
411
+ const result = createMemoryValues('1049600', '524800'); // slightly over 1024*1024 and 512*1024
412
+
413
+ expect(result.total).toStrictEqual(Math.round((1049600 / 1048576) * 100) / 100);
414
+ expect(result.useful).toStrictEqual(Math.round((524800 / 1048576) * 100) / 100);
415
+ });
416
+ });
417
+ });
@@ -0,0 +1,246 @@
1
+ import {
2
+ addParam, addParams, removeParam, parseLinkHeader, isMaybeSecure, portMatch, parse, stringify
3
+ } from '@shell/utils/url';
4
+
5
+ describe('fx: addParam', () => {
6
+ it('should add a query parameter to a URL without existing params', () => {
7
+ expect(addParam('https://example.com/path', 'foo', 'bar')).toStrictEqual('https://example.com/path?foo=bar');
8
+ });
9
+
10
+ it('should append a query parameter to a URL with existing params', () => {
11
+ expect(addParam('https://example.com/path?a=1', 'b', '2')).toStrictEqual('https://example.com/path?a=1&b=2');
12
+ });
13
+
14
+ it('should encode special characters in key and value', () => {
15
+ expect(addParam('https://example.com', 'my key', 'hello world')).toStrictEqual('https://example.com?my%20key=hello%20world');
16
+ });
17
+
18
+ it('should add multiple values from an array', () => {
19
+ expect(addParam('https://example.com', 'tag', ['a', 'b'])).toStrictEqual('https://example.com?tag=a&tag=b');
20
+ });
21
+
22
+ it('should add a key-only param when value is null', () => {
23
+ expect(addParam('https://example.com', 'flag', null)).toStrictEqual('https://example.com?flag');
24
+ });
25
+
26
+ it('should handle an array with a null value', () => {
27
+ expect(addParam('https://example.com', 'flag', [null])).toStrictEqual('https://example.com?flag');
28
+ });
29
+
30
+ it('should add a param with an empty string value', () => {
31
+ expect(addParam('https://example.com', 'key', '')).toStrictEqual('https://example.com?key=');
32
+ });
33
+
34
+ it('should add a duplicate key as an additional param', () => {
35
+ expect(addParam('https://example.com?a=1', 'a', '2')).toStrictEqual('https://example.com?a=1&a=2');
36
+ });
37
+ });
38
+
39
+ describe('fx: addParams', () => {
40
+ it('should add multiple parameters to a URL', () => {
41
+ expect(addParams('https://example.com', { a: '1', b: '2' })).toStrictEqual('https://example.com?a=1&b=2');
42
+ });
43
+
44
+ it('should return the URL unchanged if params is empty', () => {
45
+ expect(addParams('https://example.com', {})).toStrictEqual('https://example.com');
46
+ });
47
+
48
+ it('should return the URL unchanged if params is null', () => {
49
+ expect(addParams('https://example.com', null)).toStrictEqual('https://example.com');
50
+ });
51
+
52
+ it('should return the URL unchanged if params is a non-object value', () => {
53
+ expect(addParams('https://example.com', 'not-an-object')).toStrictEqual('https://example.com');
54
+ });
55
+ });
56
+
57
+ describe('fx: removeParam', () => {
58
+ it('should remove a query parameter from a URL', () => {
59
+ expect(removeParam('https://example.com?foo=bar&baz=qux', 'foo')).toStrictEqual('https://example.com/?baz=qux');
60
+ });
61
+
62
+ it('should return a normalized URL if the param does not exist', () => {
63
+ expect(removeParam('https://example.com?a=1', 'nonexistent')).toStrictEqual('https://example.com/?a=1');
64
+ });
65
+
66
+ it('should remove the only query parameter', () => {
67
+ expect(removeParam('https://example.com?only=param', 'only')).toStrictEqual('https://example.com/');
68
+ });
69
+
70
+ it('should normalize a key-only query parameter to key= (parser treats it as empty value)', () => {
71
+ expect(removeParam('https://example.com?flag', 'flag')).toStrictEqual('https://example.com/?flag=');
72
+ });
73
+ });
74
+
75
+ describe('fx: parseLinkHeader', () => {
76
+ it('should parse a single link header entry', () => {
77
+ expect(parseLinkHeader('<https://example.com/page2>; rel="next"')).toStrictEqual({ next: 'https://example.com/page2' });
78
+ });
79
+
80
+ it('should parse multiple link header entries', () => {
81
+ const header = '<https://example.com/page2>; rel="next", <https://example.com/page1>; rel="prev"';
82
+
83
+ expect(parseLinkHeader(header)).toStrictEqual({
84
+ next: 'https://example.com/page2',
85
+ prev: 'https://example.com/page1',
86
+ });
87
+ });
88
+
89
+ it('should return an empty object for an empty string', () => {
90
+ expect(parseLinkHeader('')).toStrictEqual({});
91
+ });
92
+
93
+ it('should return an empty object for a malformed header', () => {
94
+ expect(parseLinkHeader('not a valid link header')).toStrictEqual({});
95
+ });
96
+
97
+ it('should lowercase the rel value', () => {
98
+ expect(parseLinkHeader('<https://example.com>; rel="Next"')).toStrictEqual({ next: 'https://example.com' });
99
+ });
100
+ });
101
+
102
+ describe('fx: portMatch', () => {
103
+ it.each([
104
+ {
105
+ ports: [443], equals: [443, 8443], endsWith: [], expected: true, desc: 'port is in the equals list'
106
+ },
107
+ {
108
+ ports: [8080], equals: [443, 8443], endsWith: ['443'], expected: false, desc: 'port is not in equals or endsWith lists'
109
+ },
110
+ {
111
+ ports: [8443], equals: [], endsWith: ['443'], expected: true, desc: 'port string ends with the given suffix'
112
+ },
113
+ {
114
+ ports: [443], equals: [], endsWith: ['443'], expected: false, desc: 'port equals the suffix exactly (endsWith excludes exact match)'
115
+ },
116
+ {
117
+ ports: [], equals: [443], endsWith: ['443'], expected: false, desc: 'ports array is empty'
118
+ },
119
+ {
120
+ ports: [80, 443], equals: [443], endsWith: [], expected: true, desc: 'any port in the array matches equals'
121
+ },
122
+ {
123
+ ports: [18443], equals: [], endsWith: ['443'], expected: true, desc: 'multi-digit port ending with suffix'
124
+ },
125
+ ])('should return $expected when $desc', ({
126
+ ports, equals, endsWith, expected
127
+ }) => {
128
+ expect(portMatch(ports, equals, endsWith)).toBe(expected);
129
+ });
130
+ });
131
+
132
+ describe('fx: isMaybeSecure', () => {
133
+ it.each([
134
+ {
135
+ port: 80, proto: 'https', expected: true, desc: 'https protocol'
136
+ },
137
+ {
138
+ port: 80, proto: 'HTTPS', expected: true, desc: 'HTTPS protocol (case-insensitive)'
139
+ },
140
+ {
141
+ port: 443, proto: 'http', expected: true, desc: 'port 443'
142
+ },
143
+ {
144
+ port: 8443, proto: 'http', expected: true, desc: 'port 8443'
145
+ },
146
+ {
147
+ port: 18443, proto: 'http', expected: true, desc: 'port 18443 (endsWith 443)'
148
+ },
149
+ {
150
+ port: 80, proto: 'http', expected: false, desc: 'http on non-secure port'
151
+ },
152
+ ])('should return $expected for $desc', ({ port, proto, expected }) => {
153
+ expect(isMaybeSecure(port, proto)).toBe(expected);
154
+ });
155
+ });
156
+
157
+ describe('fx: parse', () => {
158
+ it('should parse a simple URL', () => {
159
+ const result = parse('https://example.com/path?foo=bar');
160
+
161
+ expect(result.protocol).toStrictEqual('https');
162
+ expect(result.host).toStrictEqual('example.com');
163
+ expect(result.path).toStrictEqual('/path');
164
+ expect(result.query).toStrictEqual({ foo: 'bar' });
165
+ });
166
+
167
+ it('should parse a URL with port', () => {
168
+ const result = parse('https://example.com:8080/');
169
+
170
+ expect(result.host).toStrictEqual('example.com');
171
+ expect(result.port).toStrictEqual('8080');
172
+ });
173
+
174
+ it('should parse a URL with user credentials', () => {
175
+ const result = parse('https://user:pass@example.com/');
176
+
177
+ expect(result.user).toStrictEqual('user');
178
+ expect(result.password).toStrictEqual('pass');
179
+ });
180
+
181
+ it('should parse a URL with anchor', () => {
182
+ const result = parse('https://example.com/page#section1');
183
+
184
+ expect(result.anchor).toStrictEqual('section1');
185
+ });
186
+
187
+ it('should parse a URL with multiple query params', () => {
188
+ expect(parse('https://example.com?a=1&b=2').query).toStrictEqual({ a: '1', b: '2' });
189
+ });
190
+
191
+ it('should parse a URL with user only (no password)', () => {
192
+ const result = parse('https://admin@example.com/');
193
+
194
+ expect(result.user).toStrictEqual('admin');
195
+ expect(result.password).toStrictEqual('');
196
+ });
197
+
198
+ it('should set empty strings for missing optional fields', () => {
199
+ const result = parse('https://example.com/path');
200
+
201
+ expect(result.port).toStrictEqual('');
202
+ expect(result.anchor).toStrictEqual('');
203
+ expect(result.user).toStrictEqual('');
204
+ expect(result.password).toStrictEqual('');
205
+ });
206
+ });
207
+
208
+ describe('fx: stringify', () => {
209
+ it('should reconstruct a simple URL', () => {
210
+ expect(stringify(parse('https://example.com/path'))).toStrictEqual('https://example.com/path');
211
+ });
212
+
213
+ it('should include user and password when both present', () => {
214
+ expect(stringify(parse('https://user:pass@example.com/'))).toStrictEqual('https://user:pass@example.com/');
215
+ });
216
+
217
+ it('should include user only when password is absent', () => {
218
+ expect(stringify(parse('https://admin@example.com/'))).toStrictEqual('https://admin@example.com/');
219
+ });
220
+
221
+ it('should include port when present', () => {
222
+ expect(stringify(parse('https://example.com:9090/'))).toStrictEqual('https://example.com:9090/');
223
+ });
224
+
225
+ it('should include anchor when present', () => {
226
+ expect(stringify(parse('https://example.com/page#section'))).toStrictEqual('https://example.com/page#section');
227
+ });
228
+
229
+ it('should default path to / when path is empty', () => {
230
+ const parsed = parse('https://example.com');
231
+
232
+ parsed.path = '';
233
+
234
+ expect(stringify(parsed)).toStrictEqual('https://example.com/');
235
+ });
236
+
237
+ it('should include query parameters', () => {
238
+ expect(stringify(parse('https://example.com/path?a=1&b=2'))).toStrictEqual('https://example.com/path?a=1&b=2');
239
+ });
240
+
241
+ it('should round-trip a complex URL', () => {
242
+ const url = 'https://user:pass@example.com:8080/some/path?key=value&other=test#anchor';
243
+
244
+ expect(stringify(parse(url))).toStrictEqual(url);
245
+ });
246
+ });