@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
@@ -1,4 +1,4 @@
1
- import { decodeHtml, resourceNames, pathArrayToTree } from '@shell/utils/string';
1
+ import { decodeHtml, resourceNames, pathArrayToTree, isBase64EncodedCert } from '@shell/utils/string';
2
2
 
3
3
  describe('fx: decodeHtml', () => {
4
4
  it('should decode HTML values from escaped string into valid markup', () => {
@@ -361,3 +361,25 @@ describe('fx: pathArrayToTree', () => {
361
361
  expect(actual).toStrictEqual(expected);
362
362
  });
363
363
  });
364
+
365
+ describe('fx: isBase64EncodedCert', () => {
366
+ it.each([
367
+ ['valid base64 cert', 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t', true],
368
+ ['base64 with padding', 'c3NoIGtleQ==', false],
369
+ ['base64 with newlines', 'LS0tLS1CRUdJTiBDRVJU\nSUZJQ0FURS0tLS0t\nTUlJQmtUQ0I=', true],
370
+ ['base64 with CRLF', 'LS0tLS1CRUdJTiBDRVJU\r\nSUZJQ0FURS0tLS0t\r\nTUlJQmtUQ0I=', true],
371
+ ])('should return %p for %p', (_, value, expected) => {
372
+ expect(isBase64EncodedCert(value as string)).toBe(expected);
373
+ });
374
+
375
+ it.each([
376
+ ['empty string', ''],
377
+ ['null', null],
378
+ ['undefined', undefined],
379
+ ['short string like "test"', 'test'],
380
+ ['short valid base64', 'Zm9v'],
381
+ ['invalid characters', 'not-valid-base64!'],
382
+ ])('should return false for %p', (_, value) => {
383
+ expect(isBase64EncodedCert(value as string)).toBe(false);
384
+ });
385
+ });
@@ -0,0 +1,154 @@
1
+ import {
2
+ ALL_STATE_COLORS,
3
+ BLANK_IMAGE,
4
+ StateColor,
5
+ getHighestAlertColor,
6
+ isHigherAlert,
7
+ isTruncated,
8
+ stateColorCssVar,
9
+ toBgColor,
10
+ } from '@shell/utils/style';
11
+
12
+ describe('stateColorCssVar', () => {
13
+ it.each([
14
+ ['success', 'var(--success)'],
15
+ ['warning', 'var(--warning)'],
16
+ ['error', 'var(--error)'],
17
+ ['info', 'var(--info)'],
18
+ ['disabled', 'var(--disabled)'],
19
+ ] as [StateColor, string][])('returns css var for %s', (color, expected) => {
20
+ expect(stateColorCssVar(color)).toBe(expected);
21
+ });
22
+ });
23
+
24
+ describe('toBgColor', () => {
25
+ it('returns bg-info when no color provided', () => {
26
+ expect(toBgColor(undefined)).toBe('bg-info');
27
+ });
28
+
29
+ it.each([
30
+ ['success', 'bg-success'],
31
+ ['warning', 'bg-warning'],
32
+ ['error', 'bg-error'],
33
+ ['info', 'bg-info'],
34
+ ['disabled', 'bg-disabled'],
35
+ ] as [StateColor, string][])('returns bg-%s for color %s', (color, expected) => {
36
+ expect(toBgColor(color)).toBe(expected);
37
+ });
38
+ });
39
+
40
+ describe('isHigherAlert', () => {
41
+ it.each([
42
+ ['error', 'warning', true],
43
+ ['error', 'success', true],
44
+ ['error', 'info', true],
45
+ ['warning', 'success', true],
46
+ ['warning', 'info', true],
47
+ ['success', 'info', true],
48
+ ['warning', 'error', false],
49
+ ['success', 'error', false],
50
+ ['info', 'error', false],
51
+ ['success', 'warning', false],
52
+ ['info', 'warning', false],
53
+ ['info', 'success', false],
54
+ ['error', 'error', false],
55
+ ['warning', 'warning', false],
56
+ ['info', 'info', false],
57
+ ['disabled', 'info', false],
58
+ ['disabled', 'error', false],
59
+ ['info', 'disabled', true],
60
+ ['error', 'disabled', true],
61
+ ] as [StateColor, StateColor, boolean][])('%s vs %s → %s', (a, b, expected) => {
62
+ expect(isHigherAlert(a, b)).toBe(expected);
63
+ });
64
+ });
65
+
66
+ describe('getHighestAlertColor', () => {
67
+ it.each([
68
+ {
69
+ desc: 'info for an empty array', colors: [], expected: 'info'
70
+ },
71
+ {
72
+ desc: 'error when single error element', colors: ['error'], expected: 'error'
73
+ },
74
+ {
75
+ desc: 'warning when single warning element', colors: ['warning'], expected: 'warning'
76
+ },
77
+ {
78
+ desc: 'error when present among mixed levels', colors: ['info', 'warning', 'error', 'success'], expected: 'error'
79
+ },
80
+ {
81
+ desc: 'warning when no error is present', colors: ['info', 'success', 'warning'], expected: 'warning'
82
+ },
83
+ {
84
+ desc: 'success when only info and success are present', colors: ['info', 'success'], expected: 'success'
85
+ },
86
+ {
87
+ desc: 'info when all colors are info', colors: ['info', 'info', 'info'], expected: 'info'
88
+ },
89
+ {
90
+ desc: 'info (default) when all colors are disabled', colors: ['disabled', 'disabled'], expected: 'info'
91
+ },
92
+ ])('returns $desc', ({ colors, expected }) => {
93
+ expect(getHighestAlertColor(colors as StateColor[])).toBe(expected);
94
+ });
95
+ });
96
+
97
+ describe('isTruncated', () => {
98
+ it('returns false when element is null', () => {
99
+ expect(isTruncated(null)).toBe(false);
100
+ });
101
+
102
+ it.each([
103
+ {
104
+ desc: 'false when text fits within element bounds',
105
+ el: {
106
+ scrollWidth: 100, clientWidth: 200, scrollHeight: 20, clientHeight: 20
107
+ },
108
+ expected: false,
109
+ },
110
+ {
111
+ desc: 'true when text is horizontally truncated',
112
+ el: {
113
+ scrollWidth: 300, clientWidth: 200, scrollHeight: 20, clientHeight: 20
114
+ },
115
+ expected: true,
116
+ },
117
+ {
118
+ desc: 'true when text is vertically truncated',
119
+ el: {
120
+ scrollWidth: 100, clientWidth: 100, scrollHeight: 50, clientHeight: 40
121
+ },
122
+ expected: true,
123
+ },
124
+ {
125
+ desc: 'false for single-pixel vertical overflow (1px tolerance)',
126
+ el: {
127
+ scrollWidth: 100, clientWidth: 100, scrollHeight: 21, clientHeight: 20
128
+ },
129
+ expected: false,
130
+ },
131
+ {
132
+ desc: 'true for 2+ pixel vertical overflow',
133
+ el: {
134
+ scrollWidth: 100, clientWidth: 100, scrollHeight: 22, clientHeight: 20
135
+ },
136
+ expected: true,
137
+ },
138
+ ])('returns $desc', ({ el, expected }) => {
139
+ expect(isTruncated(el as HTMLElement)).toBe(expected);
140
+ });
141
+ });
142
+
143
+ describe('all state colors constant', () => {
144
+ it('contains all expected state colors', () => {
145
+ expect(ALL_STATE_COLORS).toStrictEqual(['success', 'warning', 'error', 'info', 'disabled']);
146
+ });
147
+ });
148
+
149
+ describe('blank image constant', () => {
150
+ it('is a valid base64 data URI', () => {
151
+ expect(BLANK_IMAGE).toMatch(/^data:image\//);
152
+ expect(BLANK_IMAGE).toContain('base64,');
153
+ });
154
+ });
@@ -0,0 +1,184 @@
1
+ import { Solver } from '@shell/utils/svg-filter';
2
+
3
+ describe('solver', () => {
4
+ describe('css', () => {
5
+ it.each([
6
+ {
7
+ desc: 'all-zero filter values produce zero percentages and zero hue-rotate',
8
+ filters: [0, 0, 0, 0, 0, 0],
9
+ expected: 'filter: invert(0%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(0%) contrast(0%);',
10
+ },
11
+ {
12
+ desc: 'integer filter values are rounded and formatted correctly',
13
+ filters: [50, 20, 3750, 50, 100, 100],
14
+ expected: 'filter: invert(50%) sepia(20%) saturate(3750%) hue-rotate(180deg) brightness(100%) contrast(100%);',
15
+ },
16
+ {
17
+ desc: 'fractional filter values are rounded to nearest integer',
18
+ filters: [10.6, 30.4, 100.5, 25.2, 80.9, 120.1],
19
+ expected: 'filter: invert(11%) sepia(30%) saturate(101%) hue-rotate(91deg) brightness(81%) contrast(120%);',
20
+ },
21
+ {
22
+ desc: 'hue-rotate index (3) uses 3.6 multiplier',
23
+ filters: [0, 0, 0, 100, 0, 0],
24
+ expected: 'filter: invert(0%) sepia(0%) saturate(0%) hue-rotate(360deg) brightness(0%) contrast(0%);',
25
+ },
26
+ ])('$desc', ({ filters, expected }) => {
27
+ const solver = new Solver({
28
+ r: 255, g: 0, b: 0
29
+ });
30
+
31
+ expect(solver.css(filters)).toStrictEqual(expected);
32
+ });
33
+ });
34
+
35
+ describe('loss', () => {
36
+ it('returns 0 for filters that reproduce the target color exactly', () => {
37
+ // Target: black (0,0,0). Starting from black and applying:
38
+ // invert(0%), sepia(0%), saturate(0%), hueRotate(0), brightness(100%), contrast(100%)
39
+ // should leave color as (0,0,0) — perfect match.
40
+ const solver = new Solver({
41
+ r: 0, g: 0, b: 0
42
+ });
43
+ const loss = solver.loss([0, 0, 0, 0, 100, 100]);
44
+
45
+ expect(loss).toStrictEqual(0);
46
+ });
47
+
48
+ it('returns a positive value when filters produce a color that differs from the target', () => {
49
+ const solver = new Solver({
50
+ r: 255, g: 0, b: 0
51
+ });
52
+ // Filters that are all zero → color stays black → far from red target
53
+ const loss = solver.loss([0, 0, 0, 0, 0, 0]);
54
+
55
+ expect(loss).toBeGreaterThan(0);
56
+ });
57
+
58
+ it('loss is smaller when color produced by filters is closer to target', () => {
59
+ const solver = new Solver({
60
+ r: 0, g: 0, b: 0
61
+ });
62
+ // filters [0,0,0,0,0,0]: contrast(0) pushes everything to mid-gray → far from black target
63
+ const highLoss = solver.loss([0, 0, 0, 0, 0, 0]);
64
+ // filters [0,0,0,0,100,100]: identity transforms → stays black → matches target
65
+ const lowerLoss = solver.loss([0, 0, 0, 0, 100, 100]);
66
+
67
+ expect(lowerLoss).toBeLessThan(highLoss);
68
+ });
69
+
70
+ it('uses the same reusedColor instance without side effects between calls', () => {
71
+ const solver = new Solver({
72
+ r: 100, g: 150, b: 200
73
+ });
74
+ const loss1 = solver.loss([10, 5, 200, 30, 90, 110]);
75
+ const loss2 = solver.loss([10, 5, 200, 30, 90, 110]);
76
+
77
+ // Same inputs must produce the same loss regardless of call order
78
+ expect(loss1).toStrictEqual(loss2);
79
+ });
80
+ });
81
+
82
+ describe('constructor', () => {
83
+ it('stores target color components', () => {
84
+ const solver = new Solver({
85
+ r: 100, g: 150, b: 200
86
+ });
87
+
88
+ expect(solver.target.r).toStrictEqual(100);
89
+ expect(solver.target.g).toStrictEqual(150);
90
+ expect(solver.target.b).toStrictEqual(200);
91
+ });
92
+
93
+ it('clamps target components above 255 to 255', () => {
94
+ const solver = new Solver({
95
+ r: 300, g: 0, b: 0
96
+ });
97
+
98
+ expect(solver.target.r).toStrictEqual(255);
99
+ });
100
+
101
+ it('clamps target components below 0 to 0', () => {
102
+ const solver = new Solver({
103
+ r: 0, g: -10, b: 0
104
+ });
105
+
106
+ expect(solver.target.g).toStrictEqual(0);
107
+ });
108
+
109
+ it('computes targetHSL from the provided color', () => {
110
+ // Pure white: hsl should be h=0, s=0, l=100
111
+ const solver = new Solver({
112
+ r: 255, g: 255, b: 255
113
+ });
114
+
115
+ expect(solver.targetHSL.h).toStrictEqual(0);
116
+ expect(solver.targetHSL.s).toStrictEqual(0);
117
+ expect(solver.targetHSL.l).toBeCloseTo(100, 1);
118
+ });
119
+
120
+ it('computes targetHSL for pure red', () => {
121
+ // Pure red: r=255, g=0, b=0 → hsl h≈0 (normalised *100 → 0), s=100, l=50
122
+ const solver = new Solver({
123
+ r: 255, g: 0, b: 0
124
+ });
125
+
126
+ expect(solver.targetHSL.h).toBeCloseTo(0, 1);
127
+ expect(solver.targetHSL.s).toBeCloseTo(100, 1);
128
+ expect(solver.targetHSL.l).toBeCloseTo(50, 1);
129
+ });
130
+ });
131
+
132
+ describe('solve', () => {
133
+ it('returns an object with values, loss, filter and filterVal properties', () => {
134
+ const solver = new Solver({
135
+ r: 255, g: 0, b: 0
136
+ });
137
+ const result = solver.solve();
138
+
139
+ expect(result).toHaveProperty('values');
140
+ expect(result).toHaveProperty('loss');
141
+ expect(result).toHaveProperty('filter');
142
+ expect(result).toHaveProperty('filterVal');
143
+ });
144
+
145
+ it('filter starts with "filter: " and ends with ";"', () => {
146
+ const solver = new Solver({
147
+ r: 0, g: 128, b: 255
148
+ });
149
+ const result = solver.solve();
150
+
151
+ expect(result.filter.startsWith('filter: ')).toBe(true);
152
+ expect(result.filter.endsWith(';')).toBe(true);
153
+ });
154
+
155
+ it('filterVal is filter without the "filter: " prefix and trailing ";"', () => {
156
+ const solver = new Solver({
157
+ r: 0, g: 200, b: 100
158
+ });
159
+ const result = solver.solve();
160
+
161
+ expect(result.filterVal).toStrictEqual(
162
+ result.filter.replace('filter: ', '').replace(';', '')
163
+ );
164
+ });
165
+
166
+ it('loss is a non-negative number', () => {
167
+ const solver = new Solver({
168
+ r: 50, g: 100, b: 150
169
+ });
170
+ const result = solver.solve();
171
+
172
+ expect(result.loss).toBeGreaterThanOrEqual(0);
173
+ });
174
+
175
+ it('values array has exactly 6 elements', () => {
176
+ const solver = new Solver({
177
+ r: 10, g: 20, b: 30
178
+ });
179
+ const result = solver.solve();
180
+
181
+ expect(result.values).toHaveLength(6);
182
+ });
183
+ });
184
+ });