@skyhook-io/k8s-ui 1.16.3 → 1.17.1

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 (309) hide show
  1. package/README.md +41 -0
  2. package/package.json +3 -3
  3. package/src/components/applications/ApplicationDetail.test.tsx +15 -0
  4. package/src/components/applications/ApplicationDetail.tsx +17 -15
  5. package/src/components/charts/AreaChart.interaction.test.tsx +118 -0
  6. package/src/components/charts/AreaChart.test.tsx +25 -0
  7. package/src/components/charts/AreaChart.tsx +141 -12
  8. package/src/components/charts/index.ts +1 -0
  9. package/src/components/checks/ChecksView.tsx +18 -1
  10. package/src/components/checks/types.ts +2 -13
  11. package/src/components/cnpg/CNPGBackupSummary.tsx +57 -9
  12. package/src/components/cnpg/CNPGClusterHASection.test.tsx +90 -0
  13. package/src/components/cnpg/CNPGClusterHASection.tsx +331 -0
  14. package/src/components/cnpg/CNPGClusterSummary.test.tsx +275 -0
  15. package/src/components/cnpg/CNPGClusterSummary.tsx +310 -96
  16. package/src/components/cnpg/CNPGConnectSection.test.tsx +64 -0
  17. package/src/components/cnpg/CNPGConnectSection.tsx +178 -0
  18. package/src/components/cnpg/CNPGDeclarativeSummary.tsx +157 -26
  19. package/src/components/cnpg/CNPGImageCatalogSummary.tsx +7 -4
  20. package/src/components/cnpg/CNPGLogicalPath.test.tsx +22 -0
  21. package/src/components/cnpg/CNPGLogicalPath.tsx +150 -0
  22. package/src/components/cnpg/CNPGObjectStoreSummary.tsx +9 -6
  23. package/src/components/cnpg/CNPGObjectSummary.test.tsx +193 -2
  24. package/src/components/cnpg/CNPGPoolerScheduling.test.tsx +36 -0
  25. package/src/components/cnpg/CNPGPoolerSummary.tsx +241 -14
  26. package/src/components/cnpg/CNPGSharedSummary.tsx +15 -8
  27. package/src/components/cnpg/CNPGWALArchivingFact.test.tsx +39 -0
  28. package/src/components/cnpg/backupRuns.test.ts +17 -0
  29. package/src/components/cnpg/backupRuns.ts +19 -0
  30. package/src/components/cnpg/connect.test.ts +124 -0
  31. package/src/components/cnpg/connect.ts +155 -0
  32. package/src/components/cnpg/databaseRole.test.ts +38 -0
  33. package/src/components/cnpg/databaseRole.ts +80 -0
  34. package/src/components/cnpg/ha.test.ts +446 -0
  35. package/src/components/cnpg/ha.ts +621 -0
  36. package/src/components/cnpg/index.ts +18 -0
  37. package/src/components/cnpg/logicalReplication.test.ts +121 -0
  38. package/src/components/cnpg/logicalReplication.ts +252 -0
  39. package/src/components/cnpg/pooler.test.ts +84 -0
  40. package/src/components/cnpg/pooler.ts +225 -0
  41. package/src/components/cnpg/primitives.tsx +21 -117
  42. package/src/components/cnpg/problems.ts +12 -0
  43. package/src/components/cnpg/relations.test.ts +63 -13
  44. package/src/components/cnpg/relations.ts +61 -52
  45. package/src/components/cnpg/schedule.ts +35 -0
  46. package/src/components/cnpg/workspace-disk.test.ts +97 -0
  47. package/src/components/cnpg/workspace-fleet-metrics.test.ts +250 -0
  48. package/src/components/cnpg/workspace-problems.test.ts +175 -0
  49. package/src/components/cnpg/workspace.test.ts +380 -9
  50. package/src/components/cnpg/workspace.ts +1137 -136
  51. package/src/components/dock/DockContext.test.tsx +32 -0
  52. package/src/components/dock/DockContext.tsx +18 -4
  53. package/src/components/dock/LocalTerminalTab.test.tsx +40 -0
  54. package/src/components/dock/LocalTerminalTab.tsx +4 -3
  55. package/src/components/dock/TerminalTab.tsx +20 -8
  56. package/src/components/facts/certainty.tsx +36 -0
  57. package/src/components/facts/facts.test.tsx +24 -0
  58. package/src/components/facts/facts.tsx +62 -0
  59. package/src/components/facts/index.ts +6 -0
  60. package/src/components/facts/managed-by.test.tsx +17 -0
  61. package/src/components/facts/managed-by.tsx +36 -0
  62. package/src/components/facts/public-exports.test.tsx +35 -0
  63. package/src/components/gitops/GitOpsGraphFilterRail.tsx +6 -2
  64. package/src/components/gitops/GitOpsHealthSourceNotice.test.tsx +38 -0
  65. package/src/components/gitops/GitOpsHealthSourceNotice.tsx +13 -3
  66. package/src/components/gitops/GitOpsTableView.tsx +9 -24
  67. package/src/components/gitops/detail-helpers.ts +1 -1
  68. package/src/components/gitops/insights/ArgoResourceDiff.test.tsx +115 -0
  69. package/src/components/gitops/insights/ArgoResourceDiff.tsx +22 -5
  70. package/src/components/gitops/insights/DriftPanel.tsx +62 -0
  71. package/src/components/gitops/insights/GitOpsInsightViews.tsx +29 -83
  72. package/src/components/gitops/tree/GitOpsTreeGraph.test.ts +26 -1
  73. package/src/components/gitops/tree/GitOpsTreeGraph.tsx +40 -60
  74. package/src/components/gitops/tree/layout.test.ts +111 -0
  75. package/src/components/gitops/tree/layout.ts +128 -0
  76. package/src/components/gitops/tree/tree-helpers.test.ts +17 -0
  77. package/src/components/gitops/tree/tree-helpers.ts +7 -1
  78. package/src/components/issues/ConditionalCleanup.test.tsx +82 -0
  79. package/src/components/issues/IssuesView.tsx +40 -5
  80. package/src/components/issues/ResourceIssuesSection.test.tsx +28 -0
  81. package/src/components/issues/ResourceIssuesSection.tsx +3 -0
  82. package/src/components/issues/index.ts +3 -1
  83. package/src/components/issues/issue-timing.ts +14 -3
  84. package/src/components/issues/issues.test.ts +50 -2
  85. package/src/components/issues/severity.ts +21 -0
  86. package/src/components/issues/types.ts +23 -0
  87. package/src/components/logs/LogCore.theme.test.tsx +81 -3
  88. package/src/components/logs/LogCore.tsx +25 -17
  89. package/src/components/logs/LogToolbarSelects.tsx +3 -1
  90. package/src/components/logs/LogsViewer.test.tsx +291 -0
  91. package/src/components/logs/LogsViewer.tsx +72 -11
  92. package/src/components/logs/StructuredLogLine.test.tsx +76 -0
  93. package/src/components/logs/StructuredLogLine.tsx +22 -30
  94. package/src/components/logs/WorkloadLogsViewer.notice.test.tsx +59 -0
  95. package/src/components/logs/WorkloadLogsViewer.test.tsx +65 -0
  96. package/src/components/logs/WorkloadLogsViewer.tsx +44 -10
  97. package/src/components/logs/useLogBuffer.test.ts +8 -0
  98. package/src/components/logs/useLogStream.ts +28 -1
  99. package/src/components/problems/index.ts +1 -0
  100. package/src/components/problems/problems.test.tsx +63 -0
  101. package/src/components/problems/problems.tsx +165 -0
  102. package/src/components/resources/ActiveFilterBar.test.tsx +78 -0
  103. package/src/components/resources/ActiveFilterBar.tsx +86 -0
  104. package/src/components/resources/ResourcesSidebar.keyboard.test.tsx +42 -0
  105. package/src/components/resources/ResourcesSidebar.test.tsx +36 -2
  106. package/src/components/resources/ResourcesSidebar.tsx +92 -41
  107. package/src/components/resources/ResourcesView.kind-filter-memory.test.ts +52 -0
  108. package/src/components/resources/ResourcesView.kind-view.test.tsx +149 -0
  109. package/src/components/resources/ResourcesView.per-container.test.ts +56 -1
  110. package/src/components/resources/ResourcesView.tsx +309 -117
  111. package/src/components/resources/curated-column-ownership.test.ts +3 -3
  112. package/src/components/resources/renderers/AnalysisRunRenderer.tsx +3 -3
  113. package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +6 -5
  114. package/src/components/resources/renderers/AutoscalingReferences.test.tsx +286 -0
  115. package/src/components/resources/renderers/CAPIClusterRenderer.tsx +5 -4
  116. package/src/components/resources/renderers/CAPIExactReferenceNavigation.test.tsx +27 -0
  117. package/src/components/resources/renderers/CAPIKubeadmControlPlaneRenderer.tsx +2 -2
  118. package/src/components/resources/renderers/CAPIMachineDeploymentRenderer.tsx +2 -2
  119. package/src/components/resources/renderers/CAPIMachinePoolRenderer.tsx +2 -2
  120. package/src/components/resources/renderers/CAPIMachineRenderer.tsx +2 -2
  121. package/src/components/resources/renderers/CAPIMachineSetRenderer.tsx +2 -2
  122. package/src/components/resources/renderers/CNPGBackupRenderer.test.tsx +23 -0
  123. package/src/components/resources/renderers/CNPGBackupRenderer.tsx +8 -19
  124. package/src/components/resources/renderers/CNPGClusterRenderer.tsx +7 -8
  125. package/src/components/resources/renderers/CNPGDeclarativeRenderer.tsx +25 -0
  126. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.test.tsx +32 -0
  127. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +9 -17
  128. package/src/components/resources/renderers/CNPGShared.tsx +2 -2
  129. package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +2 -2
  130. package/src/components/resources/renderers/CompositeRenderer.navigation.test.tsx +35 -0
  131. package/src/components/resources/renderers/CompositeRenderer.tsx +3 -3
  132. package/src/components/resources/renderers/CompositionRenderer.tsx +3 -3
  133. package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +2 -2
  134. package/src/components/resources/renderers/ContourExactReferenceNavigation.test.tsx +34 -0
  135. package/src/components/resources/renderers/ContourHTTPProxyRenderer.tsx +11 -10
  136. package/src/components/resources/renderers/CronJobRenderer.tsx +6 -4
  137. package/src/components/resources/renderers/CronWorkflowRenderer.tsx +3 -2
  138. package/src/components/resources/renderers/EndpointSliceRenderer.test.tsx +62 -0
  139. package/src/components/resources/renderers/EndpointSliceRenderer.tsx +15 -14
  140. package/src/components/resources/renderers/EventRenderer.tsx +3 -3
  141. package/src/components/resources/renderers/ExperimentRenderer.tsx +2 -2
  142. package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +2 -2
  143. package/src/components/resources/renderers/ExternalSecretRenderer.tsx +2 -2
  144. package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +4 -4
  145. package/src/components/resources/renderers/GRPCRouteRenderer.tsx +11 -24
  146. package/src/components/resources/renderers/GatewayRouteReferences.test.tsx +63 -0
  147. package/src/components/resources/renderers/GitRepositoryRenderer.tsx +3 -3
  148. package/src/components/resources/renderers/HPARenderer.tsx +57 -17
  149. package/src/components/resources/renderers/HTTPRouteRenderer.tsx +12 -25
  150. package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +3 -3
  151. package/src/components/resources/renderers/IngressClassReferences.test.tsx +52 -0
  152. package/src/components/resources/renderers/IngressClassRenderer.tsx +10 -3
  153. package/src/components/resources/renderers/IngressRenderer.defaultBackend.test.tsx +33 -0
  154. package/src/components/resources/renderers/IngressRenderer.tsx +36 -5
  155. package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +9 -9
  156. package/src/components/resources/renderers/IstioExactReferenceNavigation.test.tsx +34 -0
  157. package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +5 -8
  158. package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +2 -14
  159. package/src/components/resources/renderers/KedaCredentials.test.tsx +85 -0
  160. package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +15 -13
  161. package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +6 -3
  162. package/src/components/resources/renderers/KnativeEventingRenderer.tsx +24 -31
  163. package/src/components/resources/renderers/KnativeExactReferenceNavigation.test.tsx +47 -0
  164. package/src/components/resources/renderers/KnativeFlowRenderer.tsx +6 -7
  165. package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +7 -9
  166. package/src/components/resources/renderers/KnativeReferenceLink.tsx +14 -0
  167. package/src/components/resources/renderers/KnativeReferenceLinks.test.tsx +60 -0
  168. package/src/components/resources/renderers/KnativeSourceRenderer.tsx +9 -11
  169. package/src/components/resources/renderers/KustomizationRenderer.tsx +2 -2
  170. package/src/components/resources/renderers/KyvernoGlobalContextRenderer.tsx +3 -2
  171. package/src/components/resources/renderers/KyvernoQueueRenderers.tsx +4 -3
  172. package/src/components/resources/renderers/LeaseRenderer.tsx +3 -15
  173. package/src/components/resources/renderers/ManagedResourceRenderer.tsx +2 -2
  174. package/src/components/resources/renderers/MetricsUnavailableNotice.tsx +3 -2
  175. package/src/components/resources/renderers/NamespaceRenderer.test.ts +69 -2
  176. package/src/components/resources/renderers/NamespaceRenderer.tsx +6 -1
  177. package/src/components/resources/renderers/NodeRenderer.test.tsx +81 -1
  178. package/src/components/resources/renderers/NodeRenderer.tsx +56 -15
  179. package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +3 -3
  180. package/src/components/resources/renderers/PodClassReferences.test.tsx +42 -0
  181. package/src/components/resources/renderers/PodNomination.test.tsx +57 -0
  182. package/src/components/resources/renderers/PodRenderer.test.tsx +32 -0
  183. package/src/components/resources/renderers/PodRenderer.tsx +62 -36
  184. package/src/components/resources/renderers/RolloutRenderer.test.tsx +1 -1
  185. package/src/components/resources/renderers/RolloutRenderer.tsx +3 -2
  186. package/src/components/resources/renderers/SbomReportRenderer.tsx +2 -2
  187. package/src/components/resources/renderers/SecretRenderer.test.tsx +88 -0
  188. package/src/components/resources/renderers/SecretRenderer.tsx +34 -5
  189. package/src/components/resources/renderers/ServiceRenderer.test.tsx +43 -0
  190. package/src/components/resources/renderers/ServiceRenderer.tsx +11 -3
  191. package/src/components/resources/renderers/SimpleRouteRenderer.tsx +9 -22
  192. package/src/components/resources/renderers/VPARenderer.tsx +17 -11
  193. package/src/components/resources/renderers/VeleroBSLRenderer.tsx +5 -7
  194. package/src/components/resources/renderers/VeleroBackupRenderer.tsx +3 -3
  195. package/src/components/resources/renderers/VeleroBackupRepositoryRenderer.tsx +2 -2
  196. package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +3 -3
  197. package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +2 -2
  198. package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +2 -2
  199. package/src/components/resources/renderers/WorkflowRenderer.tsx +4 -3
  200. package/src/components/resources/renderers/kyverno-modern-cells.tsx +1 -1
  201. package/src/components/resources/renderers/rollout/ReplicaSetProgression.tsx +2 -2
  202. package/src/components/resources/resource-utils-capi.ts +1 -1
  203. package/src/components/resources/resource-utils-cnpg.test.ts +35 -10
  204. package/src/components/resources/resource-utils-cnpg.ts +51 -24
  205. package/src/components/resources/resource-utils-contour.test.ts +44 -0
  206. package/src/components/resources/resource-utils-contour.ts +16 -0
  207. package/src/components/resources/resource-utils-istio.ts +12 -8
  208. package/src/components/resources/resource-utils-keda.ts +1 -7
  209. package/src/components/resources/resource-utils-knative.ts +18 -0
  210. package/src/components/resources/resource-utils-kyverno.ts +1 -7
  211. package/src/components/resources/resource-utils-velero.ts +0 -12
  212. package/src/components/resources/resource-utils.ts +99 -39
  213. package/src/components/resources/rules-column-width.test.ts +1 -1
  214. package/src/components/resources/summarize-scheduler-message.test.ts +30 -0
  215. package/src/components/shared/ActionConfirmDialog.test.tsx +154 -0
  216. package/src/components/shared/ActionConfirmDialog.tsx +246 -0
  217. package/src/components/shared/CreateResourceDialog.test.tsx +330 -0
  218. package/src/components/shared/CreateResourceDialog.tsx +126 -80
  219. package/src/components/shared/DetailShell.test.tsx +80 -0
  220. package/src/components/shared/DetailShell.tsx +85 -10
  221. package/src/components/shared/DrainPlanDialog.tsx +1 -0
  222. package/src/components/shared/EditableYamlView.test.tsx +7 -0
  223. package/src/components/shared/EditableYamlView.tsx +1 -1
  224. package/src/components/shared/GitOpsWriteWarning.test.tsx +86 -0
  225. package/src/components/shared/GitOpsWriteWarning.tsx +135 -0
  226. package/src/components/shared/ResourceActionsBar.tsx +20 -24
  227. package/src/components/shared/ResourceRendererDispatch.tsx +13 -10
  228. package/src/components/shared/SetImageDialog.test.ts +75 -6
  229. package/src/components/shared/SetImageDialog.tsx +92 -84
  230. package/src/components/shared/index.ts +4 -1
  231. package/src/components/timeline/TimelineList.tsx +2 -15
  232. package/src/components/timeline/TimelineSwimlanes.tsx +3 -11
  233. package/src/components/timeline/timeline-filters.ts +1 -0
  234. package/src/components/topology/K8sResourceNode.test.ts +18 -1
  235. package/src/components/topology/K8sResourceNode.tsx +44 -2
  236. package/src/components/topology/TopologyFilterSidebar.tsx +1 -0
  237. package/src/components/topology/TopologyGraph.tsx +8 -74
  238. package/src/components/topology/TopologySearch.tsx +1 -0
  239. package/src/components/topology/internet-groups.test.ts +35 -0
  240. package/src/components/topology/internet-groups.ts +55 -0
  241. package/src/components/topology/layout.ts +1 -1
  242. package/src/components/ui/Badge.tsx +1 -0
  243. package/src/components/ui/FoldSection.tsx +68 -0
  244. package/src/components/ui/ForceDeleteConfirmDialog.test.tsx +62 -0
  245. package/src/components/ui/ForceDeleteConfirmDialog.tsx +24 -12
  246. package/src/components/ui/RefLink.tsx +20 -0
  247. package/src/components/ui/RowActionMenu.tsx +8 -3
  248. package/src/components/ui/ScheduleValue.test.tsx +5 -0
  249. package/src/components/ui/ScheduleValue.tsx +3 -2
  250. package/src/components/ui/crd-definition-navigation.test.tsx +160 -0
  251. package/src/components/ui/drawer-components.test.tsx +175 -1
  252. package/src/components/ui/drawer-components.tsx +103 -36
  253. package/src/components/ui/index.ts +3 -1
  254. package/src/components/ui/monacoEditorFeatures.ts +77 -0
  255. package/src/components/ui/monacoRuntime.ts +14 -19
  256. package/src/components/ui/status-tone.test.ts +10 -1
  257. package/src/components/ui/status-tone.tsx +29 -0
  258. package/src/components/workload/WorkloadView.deleted.test.tsx +101 -0
  259. package/src/components/workload/WorkloadView.deletedDialog.test.tsx +45 -0
  260. package/src/components/workload/WorkloadView.relationships.test.tsx +37 -0
  261. package/src/components/workload/WorkloadView.secretSave.test.tsx +62 -0
  262. package/src/components/workload/WorkloadView.spec.test.tsx +149 -0
  263. package/src/components/workload/WorkloadView.tsx +297 -104
  264. package/src/index.ts +14 -1
  265. package/src/monaco-deep.d.ts +19 -12
  266. package/src/theme/components.css +13 -0
  267. package/src/types/capacity.ts +21 -2
  268. package/src/types/core.ts +60 -2
  269. package/src/types/gitops-flux-status.test.ts +32 -1
  270. package/src/types/gitops-insights.ts +19 -2
  271. package/src/types/gitops-tree.ts +10 -0
  272. package/src/types/gitops.ts +26 -6
  273. package/src/utils/api-resources.ts +7 -0
  274. package/src/utils/application-history.ts +1 -0
  275. package/src/utils/cnpg-backup.test.ts +26 -0
  276. package/src/utils/cnpg-backup.ts +70 -0
  277. package/src/utils/format.test.ts +49 -2
  278. package/src/utils/format.ts +9 -1
  279. package/src/utils/gateway-references.test.ts +17 -0
  280. package/src/utils/gateway-references.ts +29 -0
  281. package/src/utils/gitops-owner.ts +3 -2
  282. package/src/utils/gitops-write-guard.test.ts +401 -0
  283. package/src/utils/gitops-write-guard.ts +509 -0
  284. package/src/utils/grant.test.ts +19 -0
  285. package/src/utils/grant.ts +35 -0
  286. package/src/utils/index.ts +6 -0
  287. package/src/utils/ingress-class-references.ts +22 -0
  288. package/src/utils/k8s-errors.ts +7 -7
  289. package/src/utils/log-format.test.ts +41 -0
  290. package/src/utils/log-format.ts +71 -15
  291. package/src/utils/log-level.ts +12 -8
  292. package/src/utils/navigation.test.ts +70 -0
  293. package/src/utils/navigation.ts +91 -8
  294. package/src/utils/node-lifecycle.test.ts +36 -0
  295. package/src/utils/node-lifecycle.ts +77 -0
  296. package/src/utils/object-reference-navigation.test.ts +43 -0
  297. package/src/utils/resource-hierarchy.test.ts +69 -0
  298. package/src/utils/resource-hierarchy.ts +41 -7
  299. package/src/utils/resource-icons.ts +2 -0
  300. package/src/utils/resource-refs.test.ts +23 -0
  301. package/src/utils/resource-refs.ts +30 -0
  302. package/src/utils/skeleton-yaml.test.ts +21 -0
  303. package/src/utils/skeleton-yaml.ts +16 -0
  304. package/src/utils/url-path.test.ts +35 -0
  305. package/src/utils/url-path.ts +21 -0
  306. package/src/utils/validators.test.ts +4 -13
  307. package/src/utils/validators.ts +5 -19
  308. package/src/utils/view-transition.test.ts +35 -1
  309. package/src/utils/view-transition.ts +16 -1
@@ -0,0 +1,330 @@
1
+ // @vitest-environment jsdom
2
+ import { act } from 'react'
3
+ import { createRoot } from 'react-dom/client'
4
+ import { afterEach, describe, expect, it, vi } from 'vitest'
5
+
6
+ vi.mock('../ui/YamlEditor', () => ({
7
+ YamlEditor: ({ value, onChange }: { value: string; onChange: (value: string) => void }) => <textarea value={value} onChange={(event) => onChange(event.target.value)} />,
8
+ }))
9
+ vi.mock('../ui/YamlReview', () => ({
10
+ reviewedResourceVersionsForPreview: () => ({}),
11
+ YamlReview: ({ onApply, onBack, applyLabel }: { onApply: () => void; onBack: () => void; applyLabel: string }) => (
12
+ <><button type="button" onClick={onApply}>{applyLabel}</button><button type="button" onClick={onBack}>Back to YAML</button></>
13
+ ),
14
+ }))
15
+
16
+ const { CreateResourceDialog } = await import('./CreateResourceDialog')
17
+
18
+ ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
19
+
20
+ const button = (text: string) => [...document.querySelectorAll('button')].find((b) => b.textContent?.trim() === text)
21
+
22
+ afterEach(() => {
23
+ document.body.innerHTML = ''
24
+ })
25
+
26
+ describe('CreateResourceDialog lockMode', () => {
27
+ it('returns the edited draft after backing out of review, without canceling the parent flow', async () => {
28
+ const onBack = vi.fn()
29
+ const onClose = vi.fn()
30
+ const onPreview = vi.fn(async () => ({ documents: [], nonAtomic: false }))
31
+ const host = document.createElement('div')
32
+ document.body.appendChild(host)
33
+ const root = createRoot(host)
34
+ await act(async () => root.render(<CreateResourceDialog open onClose={onClose} onBack={onBack} backLabel="Back to setup" initialYaml="kind: ConfigMap" initialMode="create" lockMode onApply={vi.fn()} isApplying={false} onPreview={onPreview} />))
35
+ const draft = 'kind: ConfigMap\nspec:\n storage:\n pvcTemplate:\n volumeMode: Block\n'
36
+ act(() => {
37
+ const editor = document.querySelector('textarea')!
38
+ Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')!.set!.call(editor, draft)
39
+ editor.dispatchEvent(new Event('input', { bubbles: true }))
40
+ })
41
+ await act(async () => button('Review')!.click())
42
+ expect(onPreview).toHaveBeenCalledWith({ yaml: draft, mode: 'create', force: false })
43
+ act(() => button('Back to YAML')!.click())
44
+ expect(document.querySelector('textarea')!.value).toBe(draft)
45
+ act(() => button('Back to setup')!.click())
46
+ expect(onBack).toHaveBeenCalledWith(draft)
47
+ expect(onClose).not.toHaveBeenCalled()
48
+ await act(async () => root.render(<CreateResourceDialog open onClose={onClose} onBack={onBack} backLabel="Back to setup" initialYaml="kind: ConfigMap" onApply={vi.fn()} isApplying />))
49
+ expect(button('Back to setup')!.disabled).toBe(true)
50
+ act(() => root.unmount())
51
+ })
52
+
53
+ it('stays strict create after a partial create, with no Apply or Force', async () => {
54
+ const onPreview = vi.fn(async () => ({ documents: [], nonAtomic: true }))
55
+ const onApply = vi.fn(async () => {
56
+ throw Object.assign(new Error('document 2: failed to create resource: configmaps "second" already exists'), { appliedResults: [{ kind: 'ConfigMap', name: 'settings' }] })
57
+ })
58
+ const host = document.createElement('div')
59
+ document.body.appendChild(host)
60
+ const root = createRoot(host)
61
+ await act(async () => {
62
+ root.render(
63
+ <CreateResourceDialog open onClose={() => {}} initialYaml={'kind: ConfigMap\n---\nkind: Service\n'} initialMode="create" lockMode onApply={onApply} isApplying={false} onPreview={onPreview} />,
64
+ )
65
+ })
66
+ expect(document.querySelector('[aria-label="Apply mode"]')).toBeNull()
67
+
68
+ await act(async () => button('Review')!.click())
69
+ await act(async () => button('Create reviewed resources')!.click())
70
+
71
+ expect(document.body.textContent).toContain('This dialog only creates')
72
+ expect(document.body.textContent).toContain('ConfigMap settings')
73
+ expect(document.querySelector('[aria-label="Apply mode"]')).toBeNull()
74
+ expect(document.body.textContent).not.toContain('Force')
75
+ expect(document.body.textContent).not.toContain('Switch to Apply mode')
76
+ const recovery = [...document.querySelectorAll('p')].find(p => p.textContent?.includes('Created before it stopped'))!
77
+ expect(recovery.closest('[inert]')).toBeNull()
78
+
79
+ await act(async () => button('Review')!.click())
80
+ expect(onPreview).toHaveBeenLastCalledWith(expect.objectContaining({ mode: 'create', force: false }))
81
+ act(() => root.unmount())
82
+ })
83
+ })
84
+
85
+
86
+ it.each([false, true])('returns the exact submitted YAML after a successful write (review=%s)', async (reviewed) => {
87
+ const result = { kind: 'ConfigMap', name: 'settings', namespace: 'app', created: true }
88
+ const onCreated = vi.fn()
89
+ const onApply = vi.fn(async () => [result])
90
+ const onPreview = reviewed ? vi.fn(async () => ({ documents: [], nonAtomic: false, context: 'demo' })) : undefined
91
+ const host = document.createElement('div')
92
+ document.body.appendChild(host)
93
+ const root = createRoot(host)
94
+ const submitted = 'kind: ConfigMap\nmetadata:\n name: settings\n'
95
+ await act(async () => root.render(<CreateResourceDialog open onClose={() => {}} initialYaml={submitted} initialMode="create" lockMode onApply={onApply} isApplying={false} onPreview={onPreview} onCreated={onCreated} />))
96
+ await act(async () => button(reviewed ? 'Review' : 'Create')!.click())
97
+ if (reviewed) await act(async () => button('Create reviewed resources')!.click())
98
+ expect(onApply).toHaveBeenCalledWith(expect.objectContaining({ yaml: submitted, mode: 'create', dryRun: false }))
99
+ expect(onCreated).toHaveBeenCalledWith(result, submitted)
100
+ act(() => root.unmount())
101
+ })
102
+
103
+
104
+ it('keeps ordinary Apply, Force and unlocked partial-create recovery', async () => {
105
+ const onPreview = vi.fn(async () => ({ documents: [], nonAtomic: true }))
106
+ const onApply = vi.fn(async () => { throw Object.assign(new Error('document 2: failed to create resource: configmaps "second" already exists'), { appliedResults: [{ kind: 'ConfigMap', name: 'first' }] }) })
107
+ const host = document.createElement('div'); document.body.appendChild(host)
108
+ const root = createRoot(host)
109
+ await act(async () => root.render(<CreateResourceDialog open onClose={() => {}} initialYaml="kind: ConfigMap" onApply={onApply} isApplying={false} onPreview={onPreview} />))
110
+ expect(document.querySelector('[role="radio"][aria-checked="true"]')?.textContent).toBe('apply')
111
+ const force = document.querySelector<HTMLInputElement>('input[type="checkbox"]')!
112
+ await act(async () => force.click())
113
+ await act(async () => button('Review')!.click())
114
+ expect(onPreview).toHaveBeenLastCalledWith(expect.objectContaining({ mode: 'apply', force: true }))
115
+ await act(async () => button('Back to YAML')!.click())
116
+ await act(async () => button('create')!.click())
117
+ await act(async () => button('Review')!.click())
118
+ await act(async () => button('Create reviewed resources')!.click())
119
+ expect(document.querySelector('[role="radio"][aria-checked="true"]')?.textContent).toBe('apply')
120
+ expect(document.body.textContent).toContain('Radar switched to Apply mode')
121
+ expect(document.body.textContent).not.toContain('Change the name to create a new resource')
122
+ expect(document.body.textContent).not.toContain('remove this document if it was already created')
123
+ expect(document.body.textContent).not.toContain('Switch to Apply mode')
124
+ expect(document.body.textContent).toContain('Force')
125
+ await act(async () => root.unmount())
126
+ })
127
+
128
+ it('does not close or notify the parent for a dry run', async () => {
129
+ const onClose = vi.fn(), onCreated = vi.fn(), onApply = vi.fn(async () => [{ kind: 'ConfigMap', namespace: 'app', name: 'settings', created: true }])
130
+ const host = document.createElement('div'); document.body.appendChild(host)
131
+ const root = createRoot(host)
132
+ await act(async () => root.render(<CreateResourceDialog open onClose={onClose} initialYaml="kind: ConfigMap" onApply={onApply} isApplying={false} onCreated={onCreated} />))
133
+ await act(async () => document.querySelector<HTMLInputElement>('input[type="checkbox"]')!.click())
134
+ await act(async () => button('Apply')!.click())
135
+ expect(onApply).toHaveBeenCalledWith(expect.objectContaining({ dryRun: true }))
136
+ expect(onClose).not.toHaveBeenCalled(); expect(onCreated).not.toHaveBeenCalled()
137
+ expect(document.body.textContent).toContain('(dry run)')
138
+ await act(async () => root.unmount())
139
+ })
140
+
141
+ it('closes before delivering the actual submitted draft and retains newer editor text only locally', async () => {
142
+ const calls: string[] = []
143
+ let complete!: (value: { kind: string; namespace: string; name: string; created: boolean; uid: string }[]) => void
144
+ const onApply = vi.fn(() => new Promise<{ kind: string; namespace: string; name: string; created: boolean; uid: string }[]>(resolve => { complete = resolve }))
145
+ const onCreated = vi.fn(() => calls.push('created'))
146
+ const host = document.createElement('div'); document.body.appendChild(host)
147
+ const root = createRoot(host)
148
+ const original = 'kind: ConfigMap\nmetadata:\n name: original\n'
149
+ await act(async () => root.render(<CreateResourceDialog open onClose={() => calls.push('closed')} initialYaml={original} onApply={onApply} isApplying={false} onCreated={onCreated} />))
150
+ await act(async () => button('Apply')!.click())
151
+ const editor = document.querySelector('textarea')!
152
+ await act(async () => {
153
+ Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')!.set!.call(editor, 'kind: Service')
154
+ editor.dispatchEvent(new Event('input', { bubbles: true }))
155
+ })
156
+ const result = { kind: 'ConfigMap', namespace: 'app', name: 'original', created: true, uid: 'persisted-uid' }
157
+ await act(async () => complete([result]))
158
+ expect(calls).toEqual(['closed', 'created']); expect(onCreated).toHaveBeenCalledWith(result, original)
159
+ await act(async () => root.unmount())
160
+ })
161
+
162
+ it('resets to the selected initial mode on reopen and cancel deliberately abandons the dialog', async () => {
163
+ const onBack = vi.fn(), onClose = vi.fn()
164
+ const host = document.createElement('div'); document.body.appendChild(host)
165
+ const root = createRoot(host)
166
+ const props = { open: true, initialYaml: 'kind: ConfigMap', onClose, onBack, onApply: vi.fn(), isApplying: false }
167
+ await act(async () => root.render(<CreateResourceDialog {...props} />))
168
+ await act(async () => root.render(<CreateResourceDialog {...props} initialMode="create" lockMode />))
169
+ expect(button('Create')).toBeDefined(); expect(document.querySelector('[aria-label="Apply mode"]')).toBeNull()
170
+ await act(async () => button('Cancel')!.click())
171
+ expect(onClose).toHaveBeenCalledOnce(); expect(onBack).not.toHaveBeenCalled()
172
+ await act(async () => root.unmount())
173
+ })
174
+
175
+
176
+ it.each(['back', 'reopen', 'cancel'])('retires a successful-write close timer when the user chooses %s', async choice => {
177
+ vi.useFakeTimers()
178
+ const onClose = vi.fn(), onBack = vi.fn()
179
+ const host = document.createElement('div'); document.body.appendChild(host); const root = createRoot(host)
180
+ const props = { open: true, initialYaml: 'kind: ConfigMap', onClose, onBack, onApply: vi.fn(async () => [{ kind: 'ConfigMap', namespace: 'app', name: 'settings', created: true }]), isApplying: false }
181
+ try {
182
+ await act(async () => root.render(<CreateResourceDialog {...props} />))
183
+ await act(async () => button('Apply')!.click())
184
+ if (choice === 'back') await act(async () => button('Back')!.click())
185
+ else if (choice === 'cancel') await act(async () => button('Cancel')!.click())
186
+ else {
187
+ await act(async () => root.render(<CreateResourceDialog {...props} open={false} />))
188
+ await act(async () => root.render(<CreateResourceDialog {...props} />))
189
+ }
190
+ await act(async () => vi.advanceTimersByTime(1200))
191
+ expect(onClose).toHaveBeenCalledTimes(choice === 'cancel' ? 1 : 0)
192
+ expect(onBack).toHaveBeenCalledTimes(choice === 'back' ? 1 : 0)
193
+ } finally { await act(async () => root.unmount()); host.remove(); vi.useRealTimers() }
194
+ })
195
+
196
+
197
+ it.each([false, true])('shows real partial-create recovery without a preview (locked=%s)', async lockMode => {
198
+ const onApply = vi.fn(async () => { throw Object.assign(new Error('document 2: configmaps "second" already exists'), { appliedResults: [{ kind: 'ConfigMap', namespace: 'app', name: 'first', uid: 'real-first-uid' }] }) })
199
+ const onCreated = vi.fn()
200
+ const host = document.createElement('div'); document.body.appendChild(host); const root = createRoot(host)
201
+ try {
202
+ await act(async () => root.render(<CreateResourceDialog open onClose={() => {}} initialYaml="kind: ConfigMap" initialMode="create" lockMode={lockMode} onApply={onApply} isApplying={false} onCreated={onCreated} />))
203
+ await act(async () => button('Create')!.click())
204
+ expect(document.body.textContent).toContain('Created before it stopped: ConfigMap app/first.')
205
+ expect(onCreated).not.toHaveBeenCalled()
206
+ expect(document.body.textContent).not.toContain('Switch to Apply mode')
207
+ expect(document.body.textContent).not.toContain('Radar switched to Apply mode')
208
+ expect(document.body.textContent).toContain('Remove the created objects from the manifest before retrying Create.')
209
+ expect(document.body.textContent).toContain('Change the name to create a new resource')
210
+ if (lockMode) {
211
+ expect(document.querySelector('[aria-label="Apply mode"]')).toBeNull()
212
+ expect(document.body.textContent).toContain('This dialog only creates')
213
+ } else {
214
+ expect(document.querySelector('[role="radio"][aria-checked="true"]')?.textContent).toBe('create')
215
+ }
216
+ await act(async () => button('Create')!.click())
217
+ expect(onApply).toHaveBeenLastCalledWith(expect.objectContaining({ mode: 'create', dryRun: false }))
218
+ if (!lockMode) {
219
+ await act(async () => button('apply')!.click())
220
+ await act(async () => button('Apply')!.click())
221
+ expect(onApply).toHaveBeenLastCalledWith(expect.objectContaining({ mode: 'apply', dryRun: false }))
222
+ }
223
+ } finally { await act(async () => root.unmount()); host.remove() }
224
+ })
225
+
226
+ it.each([false, true])('retains and accumulates real partial-create results across retries and review Back (review=%s)', async reviewed => {
227
+ const first = { kind: 'ConfigMap', namespace: 'app', name: 'first', uid: 'first-uid', created: true }
228
+ const later = { kind: 'Service', namespace: 'app', name: 'later', uid: 'later-uid', created: true }
229
+ const onApply = vi.fn()
230
+ .mockRejectedValueOnce(Object.assign(new Error('document 2: configmaps "collision" already exists'), { appliedResults: [first] }))
231
+ .mockRejectedValueOnce(new Error('document 1: configmaps "first" already exists'))
232
+ .mockRejectedValueOnce(Object.assign(new Error('document 2: configmaps "collision" already exists'), { appliedResults: [later] }))
233
+ const onPreview = reviewed ? vi.fn(async () => ({ documents: [], nonAtomic: true })) : undefined
234
+ const host = document.createElement('div'); document.body.appendChild(host); const root = createRoot(host)
235
+ try {
236
+ await act(async () => root.render(<CreateResourceDialog open onClose={() => {}} initialYaml="kind: ConfigMap" initialMode="create" lockMode onApply={onApply} isApplying={false} onPreview={onPreview} />))
237
+ const submit = async () => {
238
+ await act(async () => button(reviewed ? 'Review' : 'Create')!.click())
239
+ if (reviewed) await act(async () => button('Create reviewed resources')!.click())
240
+ }
241
+ await submit()
242
+ expect(document.body.textContent).toContain('Created before it stopped: ConfigMap app/first.')
243
+ if (reviewed) {
244
+ await act(async () => button('Review')!.click())
245
+ await act(async () => button('Back to YAML')!.click())
246
+ expect(document.body.textContent).toContain('Created before it stopped: ConfigMap app/first.')
247
+ expect(document.body.textContent).not.toContain('Preview failed')
248
+ }
249
+ await submit()
250
+ if (reviewed) await act(async () => button('Back to YAML')!.click())
251
+ expect(document.body.textContent).toContain('Created before it stopped: ConfigMap app/first.')
252
+ await submit()
253
+ expect(document.body.textContent).toContain('Created before it stopped: ConfigMap app/first, Service app/later.')
254
+ expect(onApply).toHaveBeenCalledTimes(3)
255
+ } finally { await act(async () => root.unmount()); host.remove() }
256
+ })
257
+
258
+ it('keeps partial-create history after dry-run success and clears it after a completed write', async () => {
259
+ const created = { kind: 'ConfigMap', namespace: 'app', name: 'first', uid: 'first-uid', created: true }
260
+ const onApply = vi.fn()
261
+ .mockRejectedValueOnce(Object.assign(new Error('document 2: configmaps "collision" already exists'), { appliedResults: [created] }))
262
+ .mockResolvedValueOnce([created])
263
+ .mockResolvedValueOnce([created])
264
+ const host = document.createElement('div'); document.body.appendChild(host); const root = createRoot(host)
265
+ try {
266
+ await act(async () => root.render(<CreateResourceDialog open onClose={() => {}} initialYaml="kind: ConfigMap" initialMode="create" lockMode onApply={onApply} isApplying={false} />))
267
+ await act(async () => button('Create')!.click())
268
+ const dryRun = document.querySelector<HTMLInputElement>('input[type="checkbox"]')!
269
+ await act(async () => dryRun.click())
270
+ await act(async () => button('Create')!.click())
271
+ expect(onApply).toHaveBeenLastCalledWith(expect.objectContaining({ dryRun: true }))
272
+ expect(document.body.textContent).toContain('(dry run)')
273
+ expect(document.body.textContent).toContain('Created before it stopped: ConfigMap app/first.')
274
+ await act(async () => dryRun.click())
275
+ await act(async () => button('Create')!.click())
276
+ expect(onApply).toHaveBeenLastCalledWith(expect.objectContaining({ dryRun: false }))
277
+ expect(document.body.textContent).not.toContain('Created before it stopped')
278
+ } finally { await act(async () => root.unmount()); host.remove() }
279
+ })
280
+
281
+ it('adds only confirmed creations from an Apply recovery attempt to earlier partial-create facts', async () => {
282
+ const first = { kind: 'ConfigMap', namespace: 'app', name: 'first', uid: 'first-uid', created: true }
283
+ const updated = { kind: 'Service', namespace: 'app', name: 'updated', uid: 'updated-uid', created: false }
284
+ const later = { kind: 'Service', namespace: 'app', name: 'later', uid: 'later-uid', created: true }
285
+ const onApply = vi.fn()
286
+ .mockRejectedValueOnce(Object.assign(new Error('document 2 failed'), { appliedResults: [first] }))
287
+ .mockRejectedValueOnce(Object.assign(new Error('document 3 failed'), { appliedResults: [updated, later] }))
288
+ const onPreview = vi.fn(async () => ({ documents: [], nonAtomic: true }))
289
+ const host = document.createElement('div'); document.body.appendChild(host); const root = createRoot(host)
290
+ try {
291
+ await act(async () => root.render(<CreateResourceDialog open onClose={() => {}} initialYaml="kind: ConfigMap" initialMode="create" onApply={onApply} isApplying={false} onPreview={onPreview} />))
292
+ await act(async () => button('Review')!.click())
293
+ await act(async () => button('Create reviewed resources')!.click())
294
+ expect(document.querySelector('[role="radio"][aria-checked="true"]')?.textContent).toBe('apply')
295
+ await act(async () => button('Review')!.click())
296
+ await act(async () => button('Apply reviewed changes')!.click())
297
+ expect(onApply).toHaveBeenLastCalledWith(expect.objectContaining({ mode: 'apply' }))
298
+ expect(document.body.textContent).toContain('Created before it stopped: ConfigMap app/first, Service app/later.')
299
+ expect(document.body.textContent).not.toContain('Service app/updated')
300
+ } finally { await act(async () => root.unmount()); host.remove() }
301
+ })
302
+
303
+ it('keeps created-object facts and the edited draft when canceling a file replacement', async () => {
304
+ const first = { kind: 'ConfigMap', namespace: 'app', name: 'first', uid: 'first-uid', created: true }
305
+ const onApply = vi.fn(async () => { throw Object.assign(new Error('document 2 failed'), { appliedResults: [first] }) })
306
+ const onClose = vi.fn()
307
+ const host = document.createElement('div'); document.body.appendChild(host); const root = createRoot(host)
308
+ const edited = 'kind: ConfigMap\nmetadata:\n name: first\n'
309
+ try {
310
+ await act(async () => root.render(<CreateResourceDialog open onClose={onClose} initialYaml="kind: ConfigMap" initialMode="create" lockMode onApply={onApply} isApplying={false} />))
311
+ const editor = document.querySelector('textarea')!
312
+ await act(async () => {
313
+ Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')!.set!.call(editor, edited)
314
+ editor.dispatchEvent(new Event('input', { bubbles: true }))
315
+ })
316
+ await act(async () => button('Create')!.click())
317
+ expect(document.body.textContent).toContain('Created before it stopped: ConfigMap app/first.')
318
+ const file = new File(['kind: Service'], 'replacement.yaml', { type: 'text/yaml' })
319
+ Object.defineProperty(file, 'text', { value: async () => 'kind: Service' })
320
+ const input = document.querySelector<HTMLInputElement>('input[type="file"]')!
321
+ Object.defineProperty(input, 'files', { value: [file], configurable: true })
322
+ await act(async () => input.dispatchEvent(new Event('change', { bubbles: true })))
323
+ const confirmation = [...document.querySelectorAll('h3')].find(heading => heading.textContent === 'Replace editor contents?')!.closest('[role="dialog"]')!
324
+ const cancel = [...confirmation.querySelectorAll('button')].find(control => control.textContent?.trim() === 'Cancel')!
325
+ await act(async () => cancel.click())
326
+ expect(document.querySelector('textarea')!.value).toBe(edited)
327
+ expect(document.body.textContent).toContain('Created before it stopped: ConfigMap app/first.')
328
+ expect(onClose).not.toHaveBeenCalled()
329
+ } finally { await act(async () => root.unmount()); host.remove() }
330
+ })