From c5d53fad39b8af216db40f37741b0b918984b38f Mon Sep 17 00:00:00 2001 From: Nadav Erell Date: Wed, 7 Oct 2026 07:44:17 +0300 Subject: [PATCH 1/3] Preserve Gateway route backend, mirror and parent reference identities --- .../resources/renderers/GRPCRouteRenderer.tsx | 29 +++++------------ .../renderers/GatewayRouteReferences.test.tsx | 26 ++++++++++++++++ .../resources/renderers/HTTPRouteRenderer.tsx | 31 ++++++------------- .../renderers/SimpleRouteRenderer.tsx | 23 +++----------- .../src/utils/gateway-references.test.ts | 12 +++++++ .../k8s-ui/src/utils/gateway-references.ts | 29 +++++++++++++++++ 6 files changed, 89 insertions(+), 61 deletions(-) create mode 100644 packages/k8s-ui/src/components/resources/renderers/GatewayRouteReferences.test.tsx create mode 100644 packages/k8s-ui/src/utils/gateway-references.test.ts create mode 100644 packages/k8s-ui/src/utils/gateway-references.ts diff --git a/packages/k8s-ui/src/components/resources/renderers/GRPCRouteRenderer.tsx b/packages/k8s-ui/src/components/resources/renderers/GRPCRouteRenderer.tsx index 08f2d1fa99..203c771c34 100644 --- a/packages/k8s-ui/src/components/resources/renderers/GRPCRouteRenderer.tsx +++ b/packages/k8s-ui/src/components/resources/renderers/GRPCRouteRenderer.tsx @@ -2,6 +2,7 @@ import { Globe, ArrowRight, Network, Filter } from 'lucide-react' import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceRefBadge, useOperationalIssuesShown } from '../../ui/drawer-components' import { Badge } from '../../ui/Badge' import type { ResourceRef } from '../../../types' +import { gatewayBackendResourceRef, gatewayParentResourceRef } from '../../../utils/gateway-references' interface GRPCRouteRendererProps { data: any @@ -32,22 +33,8 @@ export function GRPCRouteRenderer({ data, onNavigate }: GRPCRouteRendererProps) ? parentStatuses[0].conditions : undefined - function toGatewayRef(ref: any): ResourceRef { - return { - kind: 'Gateway', - namespace: ref.namespace || routeNs, - name: ref.name, - group: 'gateway.networking.k8s.io', - } - } - - function toServiceRef(backend: any): ResourceRef { - return { - kind: 'Service', - namespace: backend.namespace || routeNs, - name: backend.name, - } - } + const toParentRef = (ref: any) => gatewayParentResourceRef(ref, routeNs) + const toBackendRef = (ref: any) => gatewayBackendResourceRef(ref, routeNs) return ( <> @@ -95,7 +82,7 @@ export function GRPCRouteRenderer({ data, onNavigate }: GRPCRouteRendererProps) parentRefs.length > 0 ? (
{parentRefs.map((ref: any, i: number) => ( - + ))}
) : 'None' @@ -129,7 +116,7 @@ export function GRPCRouteRenderer({ data, onNavigate }: GRPCRouteRendererProps)
all - + {backendRefs[0].port && :{backendRefs[0].port}}
)} @@ -141,7 +128,7 @@ export function GRPCRouteRenderer({ data, onNavigate }: GRPCRouteRendererProps) return (
- + {b.port && :{b.port}} {pct !== null && {pct}%}
@@ -176,7 +163,7 @@ export function GRPCRouteRenderer({ data, onNavigate }: GRPCRouteRendererProps) {matchIdx === 0 && backendRefs.length === 1 && ( <> - + {backendRefs[0].port && :{backendRefs[0].port}} )} @@ -189,7 +176,7 @@ export function GRPCRouteRenderer({ data, onNavigate }: GRPCRouteRendererProps) return (
- + {b.port && :{b.port}} {pct !== null && {pct}%}
diff --git a/packages/k8s-ui/src/components/resources/renderers/GatewayRouteReferences.test.tsx b/packages/k8s-ui/src/components/resources/renderers/GatewayRouteReferences.test.tsx new file mode 100644 index 0000000000..23c97a1022 --- /dev/null +++ b/packages/k8s-ui/src/components/resources/renderers/GatewayRouteReferences.test.tsx @@ -0,0 +1,26 @@ +// @vitest-environment jsdom +import { act } from 'react' +import { createRoot } from 'react-dom/client' +import { expect, it, vi } from 'vitest' +import { HTTPRouteRenderer } from './HTTPRouteRenderer' +import { GRPCRouteRenderer } from './GRPCRouteRenderer' +import { SimpleRouteRenderer } from './SimpleRouteRenderer' + +it.each(['HTTPRoute','GRPCRoute','TCPRoute','TLSRoute'] as const)('preserves backend and parent identities in %s click paths', async (kind) => { + const onNavigate=vi.fn() + const backend={name:'custom-backend',kind:'Widget',group:'relationships.radar.test',namespace:'backends'} + const parent={name:'mesh-parent',kind:'Service',group:'',namespace:'mesh'} + const data={metadata:{name:'route',namespace:'app'},spec:{parentRefs:[parent],rules:[{backendRefs:[backend,{name:'default-service',port:8080}], ...(kind==='HTTPRoute'?{filters:[{type:'RequestMirror',requestMirror:{backendRef:{...backend,name:'mirror'}}}]}:{})}]}} + const node=document.createElement('div');document.body.append(node);const root=createRoot(node) + try { + await act(async()=>{root.render(kind==='HTTPRoute'?:kind==='GRPCRoute'?:)}) + for(const [name,ref] of [['custom-backend',backend],['default-service',{kind:'Service',group:'',namespace:'app',name:'default-service'}],['mesh-parent',parent]] as const){ + const button=Array.from(node.querySelectorAll('button')).find(b=>b.textContent?.includes(name));expect(button,`button ${name}`).toBeDefined() + await act(async()=>{button!.click()}) + expect(onNavigate).toHaveBeenLastCalledWith({kind:ref.kind,group:ref.group,namespace:ref.namespace,name:ref.name}) + } + if(kind==='HTTPRoute'){ + const button=Array.from(node.querySelectorAll('button')).find(b=>b.textContent?.includes('mirror'));expect(button).toBeDefined();await act(async()=>{button!.click()});expect(onNavigate).toHaveBeenLastCalledWith({...backend,name:'mirror'}) + } + } finally {await act(async()=>{root.unmount()});node.remove()} +}) diff --git a/packages/k8s-ui/src/components/resources/renderers/HTTPRouteRenderer.tsx b/packages/k8s-ui/src/components/resources/renderers/HTTPRouteRenderer.tsx index 40826e0ed4..5da4b63f4e 100644 --- a/packages/k8s-ui/src/components/resources/renderers/HTTPRouteRenderer.tsx +++ b/packages/k8s-ui/src/components/resources/renderers/HTTPRouteRenderer.tsx @@ -2,6 +2,7 @@ import { Globe, ArrowRight, Network, Filter } from 'lucide-react' import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceRefBadge, useOperationalIssuesShown } from '../../ui/drawer-components' import { Badge } from '../../ui/Badge' import type { ResourceRef } from '../../../types' +import { gatewayBackendResourceRef, gatewayParentResourceRef } from '../../../utils/gateway-references' interface HTTPRouteRendererProps { data: any @@ -32,22 +33,8 @@ export function HTTPRouteRenderer({ data, onNavigate }: HTTPRouteRendererProps) ? parentStatuses[0].conditions : undefined - function toGatewayRef(ref: any): ResourceRef { - return { - kind: 'Gateway', - namespace: ref.namespace || routeNs, - name: ref.name, - group: 'gateway.networking.k8s.io', - } - } - - function toServiceRef(backend: any): ResourceRef { - return { - kind: 'Service', - namespace: backend.namespace || routeNs, - name: backend.name, - } - } + const toParentRef = (ref: any) => gatewayParentResourceRef(ref, routeNs) + const toBackendRef = (ref: any) => gatewayBackendResourceRef(ref, routeNs) return ( <> @@ -95,7 +82,7 @@ export function HTTPRouteRenderer({ data, onNavigate }: HTTPRouteRendererProps) parentRefs.length > 0 ? (
{parentRefs.map((ref: any, i: number) => ( - + ))}
) : 'None' @@ -129,7 +116,7 @@ export function HTTPRouteRenderer({ data, onNavigate }: HTTPRouteRendererProps)
all - + {backendRefs[0].port && :{backendRefs[0].port}}
)} @@ -141,7 +128,7 @@ export function HTTPRouteRenderer({ data, onNavigate }: HTTPRouteRendererProps) return (
- + {b.port && :{b.port}} {pct !== null && {pct}%}
@@ -176,7 +163,7 @@ export function HTTPRouteRenderer({ data, onNavigate }: HTTPRouteRendererProps) {matchIdx === 0 && backendRefs.length === 1 && ( <> - + {backendRefs[0].port && :{backendRefs[0].port}} )} @@ -189,7 +176,7 @@ export function HTTPRouteRenderer({ data, onNavigate }: HTTPRouteRendererProps) return (
- + {b.port && :{b.port}} {pct !== null && {pct}%}
@@ -234,7 +221,7 @@ export function HTTPRouteRenderer({ data, onNavigate }: HTTPRouteRendererProps) )} {filter.type === 'RequestMirror' && filter.requestMirror && ( - {filter.requestMirror.backendRef?.name || 'unknown'} + {filter.requestMirror.backendRef && } {filter.requestMirror.backendRef?.port ? `:${filter.requestMirror.backendRef.port}` : ''} )} diff --git a/packages/k8s-ui/src/components/resources/renderers/SimpleRouteRenderer.tsx b/packages/k8s-ui/src/components/resources/renderers/SimpleRouteRenderer.tsx index 940db77376..48bb15f1c9 100644 --- a/packages/k8s-ui/src/components/resources/renderers/SimpleRouteRenderer.tsx +++ b/packages/k8s-ui/src/components/resources/renderers/SimpleRouteRenderer.tsx @@ -2,6 +2,7 @@ import { Globe, ArrowRight, Network } from 'lucide-react' import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceRefBadge, useOperationalIssuesShown } from '../../ui/drawer-components' import { Badge } from '../../ui/Badge' import type { ResourceRef } from '../../../types' +import { gatewayBackendResourceRef, gatewayParentResourceRef } from '../../../utils/gateway-references' interface SimpleRouteRendererProps { data: any @@ -35,22 +36,8 @@ export function SimpleRouteRenderer({ data, kind, onNavigate }: SimpleRouteRende ? parentStatuses[0].conditions : undefined - function toGatewayRef(ref: any): ResourceRef { - return { - kind: 'Gateway', - namespace: ref.namespace || routeNs, - name: ref.name, - group: 'gateway.networking.k8s.io', - } - } - - function toServiceRef(backend: any): ResourceRef { - return { - kind: 'Service', - namespace: backend.namespace || routeNs, - name: backend.name, - } - } + const toParentRef = (ref: any) => gatewayParentResourceRef(ref, routeNs) + const toBackendRef = (ref: any) => gatewayBackendResourceRef(ref, routeNs) return ( <> @@ -100,7 +87,7 @@ export function SimpleRouteRenderer({ data, kind, onNavigate }: SimpleRouteRende parentRefs.length > 0 ? (
{parentRefs.map((ref: any, i: number) => ( - + ))}
) : 'None' @@ -131,7 +118,7 @@ export function SimpleRouteRenderer({ data, kind, onNavigate }: SimpleRouteRende const pct = hasWeights ? Math.round(((b.weight ?? 1) / totalWeight) * 100) : null return ( - + {b.port && :{b.port}} {pct !== null && ( {pct}% diff --git a/packages/k8s-ui/src/utils/gateway-references.test.ts b/packages/k8s-ui/src/utils/gateway-references.test.ts new file mode 100644 index 0000000000..ea48234fa1 --- /dev/null +++ b/packages/k8s-ui/src/utils/gateway-references.test.ts @@ -0,0 +1,12 @@ +import { expect, it } from 'vitest' +import { gatewayBackendResourceRef, gatewayParentResourceRef } from './gateway-references' + +it('uses distinct backend and parent API defaults', () => { + expect(gatewayBackendResourceRef({ name: 'api' }, 'app')).toEqual({kind:'Service',group:'',namespace:'app',name:'api'}) + expect(gatewayParentResourceRef({ name: 'edge' }, 'app')).toEqual({kind:'Gateway',group:'gateway.networking.k8s.io',namespace:'app',name:'edge'}) +}) +it('preserves custom kinds, exact groups and explicit namespaces', () => { + expect(gatewayBackendResourceRef({name:'api',kind:'Widget',group:'custom.example.io',namespace:'backends'},'app')).toEqual({kind:'Widget',group:'custom.example.io',namespace:'backends',name:'api'}) + expect(gatewayBackendResourceRef({name:'api',kind:'Service',group:'serving.knative.dev'},'app').group).toBe('serving.knative.dev') + expect(gatewayParentResourceRef({name:'mesh',kind:'Service',group:'',namespace:'front'},'app')).toEqual({kind:'Service',group:'',namespace:'front',name:'mesh'}) +}) diff --git a/packages/k8s-ui/src/utils/gateway-references.ts b/packages/k8s-ui/src/utils/gateway-references.ts new file mode 100644 index 0000000000..a9def532b1 --- /dev/null +++ b/packages/k8s-ui/src/utils/gateway-references.ts @@ -0,0 +1,29 @@ +import type { ResourceRef } from '../types' + +/** Gateway references name namespaced objects. Unlike ObjectReference, their + * namespace defaults to the referring Route and their group/kind have defaults. + */ +interface GatewayObjectReference { + name: string + group?: string + kind?: string + namespace?: string +} + +export function gatewayBackendResourceRef(ref: GatewayObjectReference, routeNamespace: string): ResourceRef { + return { + kind: ref.kind ?? 'Service', + group: ref.group ?? '', + namespace: ref.namespace ?? routeNamespace, + name: ref.name, + } +} + +export function gatewayParentResourceRef(ref: GatewayObjectReference, routeNamespace: string): ResourceRef { + return { + kind: ref.kind ?? 'Gateway', + group: ref.group ?? 'gateway.networking.k8s.io', + namespace: ref.namespace ?? routeNamespace, + name: ref.name, + } +} From f5a5fe705bbdc69458116396d86dd817befc901f Mon Sep 17 00:00:00 2001 From: Nadav Erell Date: Wed, 7 Oct 2026 07:47:14 +0300 Subject: [PATCH 2/3] Describe route parents without assuming every parent is a Gateway --- .../components/resources/renderers/GRPCRouteRenderer.tsx | 6 +++--- .../resources/renderers/GatewayRouteReferences.test.tsx | 5 ++++- .../components/resources/renderers/HTTPRouteRenderer.tsx | 6 +++--- .../components/resources/renderers/SimpleRouteRenderer.tsx | 6 +++--- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/k8s-ui/src/components/resources/renderers/GRPCRouteRenderer.tsx b/packages/k8s-ui/src/components/resources/renderers/GRPCRouteRenderer.tsx index 203c771c34..b1d8b7bdf6 100644 --- a/packages/k8s-ui/src/components/resources/renderers/GRPCRouteRenderer.tsx +++ b/packages/k8s-ui/src/components/resources/renderers/GRPCRouteRenderer.tsx @@ -46,8 +46,8 @@ export function GRPCRouteRenderer({ data, onNavigate }: GRPCRouteRendererProps) const cond = (p.conditions || []).find((c: any) => c.type === 'Accepted' && c.status === 'False') const gwName = p.parentRef?.name || 'unknown' return cond?.reason - ? `Gateway "${gwName}": ${cond.reason}${cond.message ? ' — ' + cond.message : ''}` - : `Gateway "${gwName}" has not accepted this route.` + ? `Parent "${gwName}": ${cond.reason}${cond.message ? ' — ' + cond.message : ''}` + : `Parent "${gwName}" has not accepted this route.` }).join('; ')} /> )} @@ -77,7 +77,7 @@ export function GRPCRouteRenderer({ data, onNavigate }: GRPCRouteRendererProps) } /> 0 ? (
diff --git a/packages/k8s-ui/src/components/resources/renderers/GatewayRouteReferences.test.tsx b/packages/k8s-ui/src/components/resources/renderers/GatewayRouteReferences.test.tsx index 23c97a1022..a657d0166c 100644 --- a/packages/k8s-ui/src/components/resources/renderers/GatewayRouteReferences.test.tsx +++ b/packages/k8s-ui/src/components/resources/renderers/GatewayRouteReferences.test.tsx @@ -10,10 +10,13 @@ it.each(['HTTPRoute','GRPCRoute','TCPRoute','TLSRoute'] as const)('preserves bac const onNavigate=vi.fn() const backend={name:'custom-backend',kind:'Widget',group:'relationships.radar.test',namespace:'backends'} const parent={name:'mesh-parent',kind:'Service',group:'',namespace:'mesh'} - const data={metadata:{name:'route',namespace:'app'},spec:{parentRefs:[parent],rules:[{backendRefs:[backend,{name:'default-service',port:8080}], ...(kind==='HTTPRoute'?{filters:[{type:'RequestMirror',requestMirror:{backendRef:{...backend,name:'mirror'}}}]}:{})}]}} + const data={status:{parents:[{parentRef:parent,conditions:[{type:'Accepted',status:'False',reason:'Rejected'}]}]},metadata:{name:'route',namespace:'app'},spec:{parentRefs:[parent],rules:[{backendRefs:[backend,{name:'default-service',port:8080}], ...(kind==='HTTPRoute'?{filters:[{type:'RequestMirror',requestMirror:{backendRef:{...backend,name:'mirror'}}}]}:{})}]}} const node=document.createElement('div');document.body.append(node);const root=createRoot(node) try { await act(async()=>{root.render(kind==='HTTPRoute'?:kind==='GRPCRoute'?:)}) + expect(node.textContent).toContain('Parents') + expect(node.textContent).toContain('Parent "mesh-parent"') + expect(node.textContent).not.toContain('Gateway "mesh-parent"') for(const [name,ref] of [['custom-backend',backend],['default-service',{kind:'Service',group:'',namespace:'app',name:'default-service'}],['mesh-parent',parent]] as const){ const button=Array.from(node.querySelectorAll('button')).find(b=>b.textContent?.includes(name));expect(button,`button ${name}`).toBeDefined() await act(async()=>{button!.click()}) diff --git a/packages/k8s-ui/src/components/resources/renderers/HTTPRouteRenderer.tsx b/packages/k8s-ui/src/components/resources/renderers/HTTPRouteRenderer.tsx index 5da4b63f4e..3f55ae60af 100644 --- a/packages/k8s-ui/src/components/resources/renderers/HTTPRouteRenderer.tsx +++ b/packages/k8s-ui/src/components/resources/renderers/HTTPRouteRenderer.tsx @@ -46,8 +46,8 @@ export function HTTPRouteRenderer({ data, onNavigate }: HTTPRouteRendererProps) const cond = (p.conditions || []).find((c: any) => c.type === 'Accepted' && c.status === 'False') const gwName = p.parentRef?.name || 'unknown' return cond?.reason - ? `Gateway "${gwName}": ${cond.reason}${cond.message ? ' — ' + cond.message : ''}` - : `Gateway "${gwName}" has not accepted this route.` + ? `Parent "${gwName}": ${cond.reason}${cond.message ? ' — ' + cond.message : ''}` + : `Parent "${gwName}" has not accepted this route.` }).join('; ')} /> )} @@ -77,7 +77,7 @@ export function HTTPRouteRenderer({ data, onNavigate }: HTTPRouteRendererProps) } /> 0 ? (
diff --git a/packages/k8s-ui/src/components/resources/renderers/SimpleRouteRenderer.tsx b/packages/k8s-ui/src/components/resources/renderers/SimpleRouteRenderer.tsx index 48bb15f1c9..e2ea709f06 100644 --- a/packages/k8s-ui/src/components/resources/renderers/SimpleRouteRenderer.tsx +++ b/packages/k8s-ui/src/components/resources/renderers/SimpleRouteRenderer.tsx @@ -49,8 +49,8 @@ export function SimpleRouteRenderer({ data, kind, onNavigate }: SimpleRouteRende const cond = (p.conditions || []).find((c: any) => c.type === 'Accepted' && c.status === 'False') const gwName = p.parentRef?.name || 'unknown' return cond?.reason - ? `Gateway "${gwName}": ${cond.reason}${cond.message ? ' — ' + cond.message : ''}` - : `Gateway "${gwName}" has not accepted this route.` + ? `Parent "${gwName}": ${cond.reason}${cond.message ? ' — ' + cond.message : ''}` + : `Parent "${gwName}" has not accepted this route.` }).join('; ')} /> )} @@ -82,7 +82,7 @@ export function SimpleRouteRenderer({ data, kind, onNavigate }: SimpleRouteRende /> )} 0 ? (
From 0688c31acde6f6c910387a258d1ddfcb74cd6206 Mon Sep 17 00:00:00 2001 From: Nadav Erell Date: Thu, 8 Oct 2026 02:16:16 +0300 Subject: [PATCH 3/3] Rewrite Gateway reference tests in renderer-test style --- .../renderers/GatewayRouteReferences.test.tsx | 72 ++++++++++++++----- .../src/utils/gateway-references.test.ts | 23 +++--- 2 files changed, 67 insertions(+), 28 deletions(-) diff --git a/packages/k8s-ui/src/components/resources/renderers/GatewayRouteReferences.test.tsx b/packages/k8s-ui/src/components/resources/renderers/GatewayRouteReferences.test.tsx index a657d0166c..596f3e5132 100644 --- a/packages/k8s-ui/src/components/resources/renderers/GatewayRouteReferences.test.tsx +++ b/packages/k8s-ui/src/components/resources/renderers/GatewayRouteReferences.test.tsx @@ -1,29 +1,63 @@ // @vitest-environment jsdom import { act } from 'react' import { createRoot } from 'react-dom/client' -import { expect, it, vi } from 'vitest' +import { describe, expect, it, vi } from 'vitest' import { HTTPRouteRenderer } from './HTTPRouteRenderer' import { GRPCRouteRenderer } from './GRPCRouteRenderer' import { SimpleRouteRenderer } from './SimpleRouteRenderer' -it.each(['HTTPRoute','GRPCRoute','TCPRoute','TLSRoute'] as const)('preserves backend and parent identities in %s click paths', async (kind) => { - const onNavigate=vi.fn() - const backend={name:'custom-backend',kind:'Widget',group:'relationships.radar.test',namespace:'backends'} - const parent={name:'mesh-parent',kind:'Service',group:'',namespace:'mesh'} - const data={status:{parents:[{parentRef:parent,conditions:[{type:'Accepted',status:'False',reason:'Rejected'}]}]},metadata:{name:'route',namespace:'app'},spec:{parentRefs:[parent],rules:[{backendRefs:[backend,{name:'default-service',port:8080}], ...(kind==='HTTPRoute'?{filters:[{type:'RequestMirror',requestMirror:{backendRef:{...backend,name:'mirror'}}}]}:{})}]}} - const node=document.createElement('div');document.body.append(node);const root=createRoot(node) - try { - await act(async()=>{root.render(kind==='HTTPRoute'?:kind==='GRPCRoute'?:)}) - expect(node.textContent).toContain('Parents') - expect(node.textContent).toContain('Parent "mesh-parent"') - expect(node.textContent).not.toContain('Gateway "mesh-parent"') - for(const [name,ref] of [['custom-backend',backend],['default-service',{kind:'Service',group:'',namespace:'app',name:'default-service'}],['mesh-parent',parent]] as const){ - const button=Array.from(node.querySelectorAll('button')).find(b=>b.textContent?.includes(name));expect(button,`button ${name}`).toBeDefined() - await act(async()=>{button!.click()}) - expect(onNavigate).toHaveBeenLastCalledWith({kind:ref.kind,group:ref.group,namespace:ref.namespace,name:ref.name}) +const backend = { name: 'custom-backend', kind: 'Widget', group: 'relationships.radar.test', namespace: 'backends' } +const parent = { name: 'mesh-parent', kind: 'Service', group: '', namespace: 'mesh' } + +function routeData(kind: string) { + return { + metadata: { name: 'route', namespace: 'app' }, + status: { parents: [{ parentRef: parent, conditions: [{ type: 'Accepted', status: 'False', reason: 'Rejected' }] }] }, + spec: { + parentRefs: [parent], + rules: [{ + backendRefs: [backend, { name: 'default-service', port: 8080 }], + ...(kind === 'HTTPRoute' ? { filters: [{ type: 'RequestMirror', requestMirror: { backendRef: { ...backend, name: 'mirror' } } }] } : {}), + }], + }, + } +} + +describe('Gateway route reference navigation', () => { + it.each(['HTTPRoute', 'GRPCRoute', 'TCPRoute', 'TLSRoute'] as const)('preserves backend and parent identities in %s', async kind => { + Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true }) + const container = document.createElement('div') + const root = createRoot(container) + const onNavigate = vi.fn() + const data = routeData(kind) + const click = async (name: string) => { + const button = [...container.querySelectorAll('button')].find(button => button.textContent?.includes(name)) + expect(button, `button ${name}`).toBeDefined() + await act(async () => button!.click()) } - if(kind==='HTTPRoute'){ - const button=Array.from(node.querySelectorAll('button')).find(b=>b.textContent?.includes('mirror'));expect(button).toBeDefined();await act(async()=>{button!.click()});expect(onNavigate).toHaveBeenLastCalledWith({...backend,name:'mirror'}) + try { + await act(async () => root.render( + kind === 'HTTPRoute' ? + : kind === 'GRPCRoute' ? + : , + )) + expect(container.textContent).toContain('Parents') + expect(container.textContent).toContain('Parent "mesh-parent"') + expect(container.textContent).not.toContain('Gateway "mesh-parent"') + + await click('custom-backend') + expect(onNavigate).toHaveBeenLastCalledWith(backend) + await click('default-service') + expect(onNavigate).toHaveBeenLastCalledWith({ kind: 'Service', group: '', namespace: 'app', name: 'default-service' }) + await click('mesh-parent') + expect(onNavigate).toHaveBeenLastCalledWith(parent) + if (kind === 'HTTPRoute') { + await click('mirror') + expect(onNavigate).toHaveBeenLastCalledWith({ ...backend, name: 'mirror' }) + } + } finally { + await act(async () => root.unmount()) + Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: false }) } - } finally {await act(async()=>{root.unmount()});node.remove()} + }) }) diff --git a/packages/k8s-ui/src/utils/gateway-references.test.ts b/packages/k8s-ui/src/utils/gateway-references.test.ts index ea48234fa1..9d82e8d4ad 100644 --- a/packages/k8s-ui/src/utils/gateway-references.test.ts +++ b/packages/k8s-ui/src/utils/gateway-references.test.ts @@ -1,12 +1,17 @@ -import { expect, it } from 'vitest' +import { describe, expect, it } from 'vitest' import { gatewayBackendResourceRef, gatewayParentResourceRef } from './gateway-references' -it('uses distinct backend and parent API defaults', () => { - expect(gatewayBackendResourceRef({ name: 'api' }, 'app')).toEqual({kind:'Service',group:'',namespace:'app',name:'api'}) - expect(gatewayParentResourceRef({ name: 'edge' }, 'app')).toEqual({kind:'Gateway',group:'gateway.networking.k8s.io',namespace:'app',name:'edge'}) -}) -it('preserves custom kinds, exact groups and explicit namespaces', () => { - expect(gatewayBackendResourceRef({name:'api',kind:'Widget',group:'custom.example.io',namespace:'backends'},'app')).toEqual({kind:'Widget',group:'custom.example.io',namespace:'backends',name:'api'}) - expect(gatewayBackendResourceRef({name:'api',kind:'Service',group:'serving.knative.dev'},'app').group).toBe('serving.knative.dev') - expect(gatewayParentResourceRef({name:'mesh',kind:'Service',group:'',namespace:'front'},'app')).toEqual({kind:'Service',group:'',namespace:'front',name:'mesh'}) +describe('Gateway API references', () => { + it('applies the distinct backend and parent defaults', () => { + expect(gatewayBackendResourceRef({ name: 'api' }, 'app')).toEqual({ kind: 'Service', group: '', namespace: 'app', name: 'api' }) + expect(gatewayParentResourceRef({ name: 'edge' }, 'app')).toEqual({ kind: 'Gateway', group: 'gateway.networking.k8s.io', namespace: 'app', name: 'edge' }) + }) + + it('preserves custom kinds, exact groups and explicit namespaces', () => { + expect(gatewayBackendResourceRef({ name: 'api', kind: 'Widget', group: 'custom.example.io', namespace: 'backends' }, 'app')) + .toEqual({ kind: 'Widget', group: 'custom.example.io', namespace: 'backends', name: 'api' }) + expect(gatewayBackendResourceRef({ name: 'api', kind: 'Service', group: 'serving.knative.dev' }, 'app').group).toBe('serving.knative.dev') + expect(gatewayParentResourceRef({ name: 'mesh', kind: 'Service', group: '', namespace: 'front' }, 'app')) + .toEqual({ kind: 'Service', group: '', namespace: 'front', name: 'mesh' }) + }) })