@rancher/shell 3.0.12-rc.2 → 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 (272) 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/styles/base/_variables.scss +2 -0
  54. package/assets/styles/fonts/_fontstack.scss +132 -8
  55. package/assets/translations/en-us.yaml +22 -5
  56. package/chart/monitoring/index.vue +10 -1
  57. package/components/ActionDropdownShell.vue +2 -1
  58. package/components/CruResourceFooter.vue +9 -5
  59. package/components/ExplorerProjectsNamespaces.vue +1 -1
  60. package/components/InstallHelmCharts.vue +2 -2
  61. package/components/LandingPagePreference.vue +14 -5
  62. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +15 -1
  63. package/components/Resource/Detail/Metadata/index.vue +6 -0
  64. package/components/Resource/Detail/ResourcePopover/index.vue +12 -1
  65. package/components/Resource/Detail/SpacedRow.vue +3 -1
  66. package/components/Resource/Detail/TitleBar/index.vue +10 -11
  67. package/components/ResourceList/Masthead.vue +12 -8
  68. package/components/SelectIconGrid.vue +0 -10
  69. package/components/SingleClusterInfo.vue +1 -0
  70. package/components/SortableTable/__tests__/sorting.test.ts +126 -0
  71. package/components/SortableTable/index.vue +6 -9
  72. package/components/SortableTable/selection.js +23 -5
  73. package/components/SortableTable/sorting.js +6 -3
  74. package/components/Wizard.vue +14 -13
  75. package/components/fleet/FleetBundles.vue +100 -12
  76. package/components/fleet/FleetClusterTargets/index.vue +37 -15
  77. package/components/fleet/__tests__/FleetClusterTargets.test.ts +149 -115
  78. package/components/fleet/__tests__/FleetClusters.test.ts +12 -12
  79. package/components/form/LabeledSelect.vue +20 -3
  80. package/components/form/NameNsDescription.vue +11 -0
  81. package/components/form/Security.vue +6 -2
  82. package/components/form/WorkloadPorts.vue +2 -7
  83. package/components/form/__tests__/Security.test.ts +76 -0
  84. package/components/formatter/Autoscaler.vue +4 -4
  85. package/components/formatter/ClusterKubeVersion.vue +27 -0
  86. package/components/formatter/ClusterLink.vue +1 -7
  87. package/components/formatter/ClusterProvider.vue +6 -10
  88. package/components/formatter/FleetSummaryGraph.vue +0 -3
  89. package/components/formatter/MachineSummaryGraph.vue +1 -1
  90. package/components/formatter/PodsUsage.vue +2 -2
  91. package/components/formatter/__tests__/Autoscaler.test.ts +19 -22
  92. package/components/formatter/__tests__/FleetSummaryGraph.test.ts +216 -0
  93. package/components/formatter/__tests__/PodsUsage.test.ts +6 -10
  94. package/components/nav/NamespaceFilter.vue +2 -2
  95. package/components/nav/TopLevelMenu.helper.ts +15 -3
  96. package/components/nav/TopLevelMenu.vue +16 -5
  97. package/components/nav/__tests__/TopLevelMenu.test.ts +145 -21
  98. package/components/templates/home.vue +18 -0
  99. package/components/templates/plain.vue +18 -0
  100. package/components/templates/standalone.vue +17 -0
  101. package/composables/useFormValidation.ts +93 -0
  102. package/composables/useVeeValidateField.test.ts +159 -0
  103. package/composables/useVeeValidateField.ts +67 -0
  104. package/config/pagination-table-headers.js +18 -1
  105. package/config/product/manager.js +82 -21
  106. package/config/router/routes.js +6 -0
  107. package/config/table-headers.js +20 -1
  108. package/config/types.js +2 -1
  109. package/core/__tests__/plugin-products.test.ts +904 -20
  110. package/core/plugin-products-base.ts +107 -7
  111. package/core/plugin-products.ts +4 -0
  112. package/core/plugin-types.ts +111 -1
  113. package/core/plugin.ts +15 -7
  114. package/core/productDebugger.js +9 -4
  115. package/core/types-provisioning.ts +43 -30
  116. package/core/types.ts +57 -20
  117. package/detail/__tests__/pod.test.ts +41 -0
  118. package/detail/harvesterhci.io.management.cluster.vue +6 -2
  119. package/detail/pod.vue +1 -1
  120. package/detail/provisioning.cattle.io.cluster.vue +4 -10
  121. package/edit/auth/__tests__/azuread.test.ts +217 -34
  122. package/edit/auth/azuread.vue +122 -14
  123. package/edit/auth/oidc.vue +2 -2
  124. package/edit/networking.k8s.io.ingress/DefaultBackend.vue +13 -4
  125. package/edit/networking.k8s.io.ingress/RulePath.vue +8 -4
  126. package/edit/networking.k8s.io.ingress/index.vue +75 -20
  127. package/edit/provisioning.cattle.io.cluster/__tests__/MachinePool.test.ts +104 -0
  128. package/edit/provisioning.cattle.io.cluster/index.vue +11 -7
  129. package/edit/provisioning.cattle.io.cluster/rke2.vue +8 -4
  130. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
  131. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +37 -4
  132. package/edit/provisioning.cattle.io.cluster/tabs/registries/__tests__/RegistryConfigs.test.ts +132 -7
  133. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +2 -1
  134. package/edit/secret/__tests__/ssh.test.ts +5 -6
  135. package/edit/secret/basic.vue +31 -0
  136. package/edit/secret/index.vue +68 -17
  137. package/edit/secret/registry.vue +38 -0
  138. package/edit/secret/ssh.vue +29 -0
  139. package/edit/secret/tls.vue +30 -0
  140. package/edit/service.vue +4 -4
  141. package/edit/workload/Upgrading.vue +3 -3
  142. package/edit/workload/__tests__/Upgrading.test.ts +6 -9
  143. package/edit/workload/mixins/workload.js +2 -1
  144. package/list/fleet.cattle.io.bundle.vue +7 -104
  145. package/list/fleet.cattle.io.clusterregistrationtoken.vue +20 -0
  146. package/list/provisioning.cattle.io.cluster.vue +262 -180
  147. package/list/utils/management.cattle.io.cluster.utils.ts +128 -0
  148. package/mixins/__tests__/chart.test.ts +112 -0
  149. package/mixins/brand.js +2 -1
  150. package/mixins/chart.js +12 -8
  151. package/mixins/resource-fetch-api-pagination.js +41 -5
  152. package/models/__tests__/ext.cattle.io.kubeconfig.test.ts +67 -67
  153. package/models/__tests__/management.cattle.io.cluster.test.ts +1 -1
  154. package/models/__tests__/management.cattle.io.node.ts +6 -5
  155. package/models/__tests__/management.cattle.io.nodepool.ts +5 -4
  156. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +32 -11
  157. package/models/base-cluster.x-k8s.io.js +26 -0
  158. package/models/cluster.js +1 -1
  159. package/models/cluster.x-k8s.io.machine.js +4 -22
  160. package/models/cluster.x-k8s.io.machinedeployment.js +2 -20
  161. package/models/cluster.x-k8s.io.machineset.js +2 -20
  162. package/models/compliance.cattle.io.clusterscan.js +130 -2
  163. package/models/ext.cattle.io.kubeconfig.ts +4 -7
  164. package/models/fleet-application.js +3 -1
  165. package/models/management.cattle.io.cluster.js +417 -40
  166. package/models/management.cattle.io.node.js +6 -4
  167. package/models/management.cattle.io.nodepool.js +1 -1
  168. package/models/networking.k8s.io.ingress.js +12 -4
  169. package/models/provisioning.cattle.io.cluster.js +47 -330
  170. package/models/rke.cattle.io.etcdsnapshot.js +1 -2
  171. package/package.json +11 -29
  172. package/pages/__tests__/readme.test.ts +49 -0
  173. package/pages/auth/setup.vue +2 -3
  174. package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +76 -0
  175. package/pages/c/_cluster/apps/charts/chart.vue +60 -8
  176. package/pages/c/_cluster/apps/charts/install.vue +10 -7
  177. package/pages/c/_cluster/explorer/__tests__/index.test.ts +23 -25
  178. package/pages/c/_cluster/explorer/index.vue +5 -49
  179. package/pages/c/_cluster/istio/__tests__/istio.index.test.ts +194 -0
  180. package/pages/c/_cluster/istio/index.vue +21 -6
  181. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -0
  182. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +719 -2
  183. package/pages/c/_cluster/uiplugins/index.vue +203 -197
  184. package/pages/diagnostic.vue +13 -17
  185. package/pages/fail-whale.vue +18 -0
  186. package/pages/home.vue +77 -260
  187. package/pages/readme.vue +88 -0
  188. package/plugins/dashboard-store/__tests__/resource-class.test.ts +88 -0
  189. package/plugins/dashboard-store/actions.js +40 -18
  190. package/plugins/dashboard-store/resource-class.js +5 -2
  191. package/plugins/steve/__tests__/subscribe.spec.ts +6 -3
  192. package/plugins/steve/steve-pagination-utils.ts +11 -3
  193. package/plugins/steve/subscribe.js +35 -5
  194. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +10 -4
  195. package/rancher-components/Form/LabeledInput/LabeledInput.vue +7 -52
  196. package/rancher-components/RcButton/RcButton.test.ts +37 -1
  197. package/rancher-components/RcButton/RcButton.vue +38 -8
  198. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +10 -8
  199. package/store/__tests__/catalog.test.ts +115 -1
  200. package/store/__tests__/type-map.test.ts +556 -1
  201. package/store/action-menu.js +8 -3
  202. package/store/auth.js +1 -1
  203. package/store/aws.js +27 -16
  204. package/store/catalog.js +27 -3
  205. package/store/digitalocean.js +20 -38
  206. package/store/index.js +2 -0
  207. package/store/linode.js +25 -40
  208. package/store/pnap.js +1 -0
  209. package/store/type-map.js +111 -29
  210. package/tsconfig.paths.json +8 -8
  211. package/types/kube/kube-api.ts +14 -1
  212. package/types/rancher/steve.api.ts +12 -12
  213. package/types/resources/settings.d.ts +2 -1
  214. package/types/shell/index.d.ts +102 -2
  215. package/types/store/dashboard-store.types.ts +108 -11
  216. package/types/store/pagination.types.ts +6 -3
  217. package/utils/__tests__/alertmanagerconfig.test.ts +117 -0
  218. package/utils/__tests__/async.test.ts +87 -0
  219. package/utils/__tests__/aws.test.ts +140 -0
  220. package/utils/__tests__/banners.test.ts +176 -0
  221. package/utils/__tests__/chart.test.ts +64 -1
  222. package/utils/__tests__/color.test.ts +226 -0
  223. package/utils/__tests__/duration.test.ts +140 -0
  224. package/utils/__tests__/fleet.test.ts +340 -0
  225. package/utils/__tests__/ingress.test.ts +553 -0
  226. package/utils/__tests__/kube.test.ts +68 -0
  227. package/utils/__tests__/namespace-filter.test.ts +109 -0
  228. package/utils/__tests__/pagination-utils.test.ts +361 -0
  229. package/utils/__tests__/parse-externalid.test.ts +137 -0
  230. package/utils/__tests__/perf-setting.utils.test.ts +98 -0
  231. package/utils/__tests__/poller-sequential.test.ts +177 -0
  232. package/utils/__tests__/poller.test.ts +170 -0
  233. package/utils/__tests__/promise.test.ts +346 -0
  234. package/utils/__tests__/settings.test.ts +140 -0
  235. package/utils/__tests__/sort-utils.test.ts +301 -0
  236. package/utils/__tests__/string-utils.test.ts +798 -0
  237. package/utils/__tests__/string.test.ts +23 -1
  238. package/utils/__tests__/style.test.ts +154 -0
  239. package/utils/__tests__/svg-filter.test.ts +184 -0
  240. package/utils/__tests__/units.test.ts +417 -0
  241. package/utils/__tests__/versions.test.ts +128 -0
  242. package/utils/__tests__/xccdf.test.ts +391 -0
  243. package/utils/chart.js +36 -0
  244. package/utils/fleet.ts +13 -3
  245. package/utils/gatekeeper/__tests__/util.test.ts +174 -0
  246. package/utils/gc/__tests__/gc-interval.test.ts +119 -0
  247. package/utils/gc/__tests__/gc-root-store.test.ts +225 -0
  248. package/utils/gc/__tests__/gc-route-changed.test.ts +96 -0
  249. package/utils/gc/__tests__/gc.test.ts +487 -0
  250. package/utils/ingress.ts +9 -1
  251. package/utils/pagination-utils.ts +2 -1
  252. package/utils/string.js +25 -2
  253. package/utils/uiplugins.ts +5 -5
  254. package/utils/validators/__tests__/cluster-name.test.ts +110 -0
  255. package/utils/validators/__tests__/cron-schedule.test.ts +79 -0
  256. package/utils/validators/__tests__/index.test.ts +481 -0
  257. package/utils/validators/__tests__/kubernetes-name.test.ts +163 -0
  258. package/utils/validators/__tests__/misc-validators.test.ts +246 -0
  259. package/utils/validators/__tests__/pod-affinity.test.ts +382 -0
  260. package/utils/validators/__tests__/prometheusrule.test.ts +211 -0
  261. package/utils/validators/__tests__/role-template.test.ts +149 -0
  262. package/utils/validators/__tests__/service.test.ts +283 -0
  263. package/utils/validators/__tests__/setting.test.js +32 -0
  264. package/utils/validators/formRules/__tests__/index.test.ts +50 -0
  265. package/utils/validators/formRules/index.ts +5 -5
  266. package/utils/validators/machine-pool.ts +1 -1
  267. package/utils/validators/setting.js +18 -3
  268. package/utils/xccdf.ts +418 -0
  269. package/assets/fonts/lato/lato-v17-latin-700.woff +0 -0
  270. package/assets/fonts/lato/lato-v17-latin-700.woff2 +0 -0
  271. package/assets/fonts/lato/lato-v17-latin-regular.woff +0 -0
  272. package/assets/fonts/lato/lato-v17-latin-regular.woff2 +0 -0
@@ -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,128 @@
1
+ function makeStoreWithCalls(rancherPromise: Promise<any>, kubePromise: Promise<any>) {
2
+ let callCount = 0;
3
+
4
+ return {
5
+ dispatch: jest.fn(() => {
6
+ callCount++;
7
+
8
+ return callCount === 1 ? rancherPromise : kubePromise;
9
+ }),
10
+ };
11
+ }
12
+
13
+ describe('versions', () => {
14
+ let versions: any;
15
+ let mockSetVersionData: jest.Mock;
16
+ let mockSetKubeVersionData: jest.Mock;
17
+
18
+ beforeEach(() => {
19
+ jest.resetModules();
20
+ mockSetVersionData = jest.fn();
21
+ mockSetKubeVersionData = jest.fn();
22
+ jest.mock('@shell/config/version', () => ({
23
+ setVersionData: mockSetVersionData,
24
+ setKubeVersionData: mockSetKubeVersionData,
25
+ }));
26
+ versions = require('@shell/utils/versions').default;
27
+ });
28
+
29
+ describe('fetch', () => {
30
+ it('dispatches rancher/request for /rancherversion', async() => {
31
+ const store = { dispatch: jest.fn(() => Promise.resolve({})) };
32
+
33
+ await versions.fetch({ store });
34
+
35
+ expect(store.dispatch).toHaveBeenCalledWith('rancher/request', {
36
+ url: '/rancherversion',
37
+ method: 'get',
38
+ redirectUnauthorized: false,
39
+ });
40
+ });
41
+
42
+ it('dispatches rancher/request for /version', async() => {
43
+ const store = { dispatch: jest.fn(() => Promise.resolve({})) };
44
+
45
+ await versions.fetch({ store });
46
+
47
+ expect(store.dispatch).toHaveBeenCalledWith('rancher/request', {
48
+ url: '/version',
49
+ method: 'get',
50
+ redirectUnauthorized: false,
51
+ });
52
+ });
53
+
54
+ it('calls setVersionData with the rancher version response', async() => {
55
+ const rancherResponse = { Version: '2.9.0', GitCommit: 'abc123' };
56
+ const store = makeStoreWithCalls(Promise.resolve(rancherResponse), Promise.resolve({}));
57
+
58
+ await versions.fetch({ store });
59
+
60
+ expect(mockSetVersionData).toHaveBeenCalledWith(rancherResponse);
61
+ });
62
+
63
+ it('calls setKubeVersionData with the kube version response', async() => {
64
+ const kubeResponse = { gitVersion: 'v1.29.0' };
65
+ const store = makeStoreWithCalls(Promise.resolve({}), Promise.resolve(kubeResponse));
66
+
67
+ await versions.fetch({ store });
68
+
69
+ expect(mockSetKubeVersionData).toHaveBeenCalledWith(kubeResponse);
70
+ });
71
+
72
+ it('caches the promise — second call does not dispatch again', async() => {
73
+ const store = { dispatch: jest.fn(() => Promise.resolve({})) };
74
+
75
+ await versions.fetch({ store });
76
+ await versions.fetch({ store });
77
+
78
+ expect(store.dispatch).toHaveBeenCalledTimes(2);
79
+ });
80
+
81
+ it('returns the same promise object on repeated calls', () => {
82
+ const store = { dispatch: jest.fn(() => Promise.resolve({})) };
83
+
84
+ versions.fetch({ store });
85
+ versions.fetch({ store });
86
+
87
+ // Both initial calls should dispatch only twice total (2 endpoints, cached after that)
88
+ expect(store.dispatch).toHaveBeenCalledTimes(2);
89
+ // Third fetch should not dispatch additional times
90
+ versions.fetch({ store });
91
+ expect(store.dispatch).toHaveBeenCalledTimes(2);
92
+ });
93
+
94
+ it('does not throw when rancher request fails', async() => {
95
+ const store = makeStoreWithCalls(Promise.reject(new Error('network error')), Promise.resolve({}));
96
+
97
+ await expect(versions.fetch({ store })).resolves.not.toThrow();
98
+ });
99
+
100
+ it('does not throw when kube request fails', async() => {
101
+ const store = makeStoreWithCalls(Promise.resolve({}), Promise.reject(new Error('network error')));
102
+
103
+ await expect(versions.fetch({ store })).resolves.not.toThrow();
104
+ });
105
+
106
+ it('logs a warning when rancher request fails', async() => {
107
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
108
+ const error = new Error('rancher down');
109
+ const store = makeStoreWithCalls(Promise.reject(error), Promise.resolve({}));
110
+
111
+ await versions.fetch({ store });
112
+
113
+ expect(warnSpy).toHaveBeenCalledWith('Failed to fetch Rancher version metadata', error);
114
+ warnSpy.mockRestore();
115
+ });
116
+
117
+ it('logs a warning when kube request fails', async() => {
118
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
119
+ const error = new Error('kube down');
120
+ const store = makeStoreWithCalls(Promise.resolve({}), Promise.reject(error));
121
+
122
+ await versions.fetch({ store });
123
+
124
+ expect(warnSpy).toHaveBeenCalledWith('Failed to fetch Kube version metadata', error);
125
+ warnSpy.mockRestore();
126
+ });
127
+ });
128
+ });