markobean commented on code in PR #11582:
URL: https://github.com/apache/nifi/pull/11582#discussion_r4050630360


##########
nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/canvas/component-connections-dialog/component-connections-dialog.component.spec.ts:
##########
@@ -0,0 +1,744 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { By } from '@angular/platform-browser';
+import { of } from 'rxjs';
+import { ComponentType } from '@nifi/shared';
+
+import { ComponentConnectionsDialog, ComponentConnectionRow } from 
'./component-connections-dialog.component';
+import { ComponentConnectionsDialogRequest, ConnectionDirection, 
ConnectionEntity } from '../../../state/flow';
+import { navigateToComponent } from '../../../state/flow/flow.actions';
+import { CanvasUtils } from '../../../service/canvas-utils.service';
+
+const REQUEST_GROUP_ID = 'request-group-id';
+const SOURCE_GROUP_ID = 'source-group-id';
+const DESTINATION_GROUP_ID = 'destination-group-id';
+const UNKNOWN_GROUP_ID = 'unknown-group-id';
+
+const SOURCE_ID = 'source-id';
+const DESTINATION_ID = 'destination-id';
+const CONNECTION_ID = 'connection-id';
+
+interface ConnectableStub {
+    id: string;
+    name: string;
+}
+
+interface ConnectionOptions {
+    id?: string;
+    source?: ConnectableStub;
+    destination?: ConnectableStub;
+    sourceGroupId?: string;
+    destinationGroupId?: string;
+    sourceType?: string;
+    destinationType?: string;
+    canRead?: boolean;
+    name?: string;
+    selectedRelationships?: string[];
+    component?: any | null;
+}
+
+interface CreatedDialog {
+    component: ComponentConnectionsDialog;
+    fixture: ComponentFixture<ComponentConnectionsDialog>;
+    store: MockStore;
+    dialogRef: {
+        close: ReturnType<typeof vi.fn>;
+        keydownEvents: () => ReturnType<typeof of>;
+    };
+}
+
+function readableConnection(options: ConnectionOptions = {}): ConnectionEntity 
{
+    const source = options.source ?? { id: SOURCE_ID, name: 'GenerateFlowFile' 
};
+    const destination = options.destination ?? { id: DESTINATION_ID, name: 
'LogAttribute' };
+
+    return {
+        id: options.id ?? CONNECTION_ID,
+        permissions: { canRead: options.canRead ?? true, canWrite: true },
+        position: { x: 0, y: 0 },
+        revision: { version: 0 },
+        sourceId: source.id,
+        sourceGroupId: options.sourceGroupId ?? SOURCE_GROUP_ID,
+        sourceType: options.sourceType ?? 'PROCESSOR',
+        destinationId: destination.id,
+        destinationGroupId: options.destinationGroupId ?? DESTINATION_GROUP_ID,
+        destinationType: options.destinationType ?? 'INPUT_PORT',
+        component:
+            options.component === undefined
+                ? {
+                      id: options.id ?? CONNECTION_ID,
+                      source,
+                      destination,
+                      name: options.name,
+                      selectedRelationships: options.selectedRelationships
+                  }
+                : options.component
+    };
+}
+
+function unreadableConnection(options: ConnectionOptions = {}): 
ConnectionEntity {
+    return {
+        id: options.id ?? CONNECTION_ID,
+        permissions: { canRead: false, canWrite: false },
+        position: { x: 0, y: 0 },
+        revision: { version: 0 },
+        sourceId: options.source?.id ?? SOURCE_ID,
+        sourceGroupId: options.sourceGroupId ?? SOURCE_GROUP_ID,
+        sourceType: options.sourceType ?? 'PROCESSOR',
+        destinationId: options.destination?.id ?? DESTINATION_ID,
+        destinationGroupId: options.destinationGroupId ?? DESTINATION_GROUP_ID,
+        destinationType: options.destinationType ?? 'INPUT_PORT',
+        component: null
+    };
+}
+
+function createDialog(
+    direction: ConnectionDirection,
+    connections: ConnectionEntity[],
+    overrides: Partial<ComponentConnectionsDialogRequest> = {}
+): CreatedDialog {
+    const dialogRequest: ComponentConnectionsDialogRequest = {
+        componentName: 'Selected Component',
+        componentType: ComponentType.InputPort,
+        groupId: REQUEST_GROUP_ID,
+        direction,
+        connections,
+        groupIdToName: new Map([
+            [REQUEST_GROUP_ID, 'Current Process Group'],
+            [SOURCE_GROUP_ID, 'Source Process Group'],
+            [DESTINATION_GROUP_ID, 'Destination Process Group']
+        ]),
+        remoteProcessGroupIds: new Set(),
+        ...overrides
+    };
+
+    const dialogRef = {
+        close: vi.fn(),
+        keydownEvents: () => of()
+    };
+
+    const canvasUtils = {
+        formatConnectionName: (component: any): string => {
+            if (component?.name) {
+                return component.name;
+            }
+            if (component?.selectedRelationships) {
+                return component.selectedRelationships.join(', ');
+            }
+            return '';
+        }
+    };
+
+    TestBed.resetTestingModule();
+    TestBed.configureTestingModule({
+        imports: [ComponentConnectionsDialog],
+        providers: [
+            { provide: MAT_DIALOG_DATA, useValue: dialogRequest },
+            { provide: MatDialogRef, useValue: dialogRef },
+            { provide: CanvasUtils, useValue: canvasUtils },
+            provideMockStore({})
+        ]
+    });
+
+    const fixture = TestBed.createComponent(ComponentConnectionsDialog);
+    fixture.detectChanges();
+
+    return {
+        component: fixture.componentInstance,
+        fixture,
+        store: TestBed.inject(MockStore),
+        dialogRef
+    };
+}
+
+function textContent(fixture: ComponentFixture<ComponentConnectionsDialog>): 
string {
+    return (fixture.nativeElement.textContent as string).replace(/\s+/g, ' 
').trim();
+}
+
+function getCells(fixture: ComponentFixture<ComponentConnectionsDialog>, 
columnClass: string): HTMLElement[] {
+    return 
fixture.debugElement.queryAll(By.css(`td.${columnClass}`)).map((debugElement) 
=> debugElement.nativeElement);
+}
+
+describe('ComponentConnectionsDialog', () => {
+    it('creates the dialog', () => {
+        const { component } = createDialog('upstream', []);
+
+        expect(component).toBeTruthy();
+    });
+
+    describe('dialog metadata', () => {
+        it('sets upstream title and empty message', () => {
+            const { component, fixture } = createDialog('upstream', []);
+
+            expect(component.title).toBe('Upstream Connections');
+            expect(component.emptyMessage).toBe('No upstream connections were 
found.');
+            expect(textContent(fixture)).toContain('Upstream Connections');
+            expect(textContent(fixture)).toContain('No upstream connections 
were found.');
+        });
+
+        it('sets downstream title and empty message', () => {
+            const { component, fixture } = createDialog('downstream', []);
+
+            expect(component.title).toBe('Downstream Connections');
+            expect(component.emptyMessage).toBe('No downstream connections 
were found.');
+            expect(textContent(fixture)).toContain('Downstream Connections');
+            expect(textContent(fixture)).toContain('No downstream connections 
were found.');
+        });
+
+        it('renders the selected component name and icon', () => {
+            const { fixture } = createDialog('upstream', [], {
+                componentName: 'Input Port A',
+                componentType: ComponentType.InputPort
+            });
+
+            expect(textContent(fixture)).toContain('Selected Component');
+            expect(textContent(fixture)).toContain('Input Port A');
+            
expect(fixture.debugElement.query(By.css('.icon-port-in'))).not.toBeNull();
+        });
+    });
+
+    describe('row construction', () => {
+        it('builds a row for a readable connection with a connection name', () 
=> {
+            const connection = readableConnection({
+                id: 'named-connection-id',
+                name: 'Named Connection',
+                source: { id: 'processor-id', name: 'GenerateFlowFile' },
+                destination: { id: 'input-port-id', name: 'Input Port' }
+            });
+
+            const { component } = createDialog('upstream', [connection]);
+
+            expect(component.rows).toEqual<ComponentConnectionRow[]>([
+                {
+                    id: 'named-connection-id',
+                    name: 'Named Connection',
+                    source: {
+                        id: 'processor-id',
+                        groupId: SOURCE_GROUP_ID,
+                        type: ComponentType.Processor,
+                        name: 'GenerateFlowFile'
+                    },
+                    destination: {
+                        id: 'input-port-id',
+                        groupId: DESTINATION_GROUP_ID,
+                        type: ComponentType.InputPort,
+                        name: 'Input Port'
+                    }
+                }
+            ]);
+        });
+
+        it('uses selected relationships as the connection name when no 
explicit connection name is present', () => {
+            const connection = readableConnection({
+                selectedRelationships: ['success', 'retry']
+            });
+
+            const { component, fixture } = createDialog('upstream', 
[connection]);
+
+            expect(component.rows[0].name).toBe('success, retry');
+            expect(textContent(fixture)).toContain('success, retry');
+        });
+
+        it('uses a null connection name when the formatted name is empty', () 
=> {
+            const connection = readableConnection();
+
+            const { component } = createDialog('upstream', [connection]);
+
+            expect(component.rows[0].name).toBeNull();
+        });
+
+        it('keeps unreadable connections using top-level endpoint identifiers 
and null endpoint names', () => {
+            const connection = unreadableConnection({
+                id: 'unreadable-connection-id',
+                source: { id: 'hidden-source-id', name: 'Hidden Source' },
+                destination: { id: 'hidden-destination-id', name: 'Hidden 
Destination' }
+            });
+
+            const { component, fixture } = createDialog('upstream', 
[connection]);
+
+            expect(component.rows).toEqual<ComponentConnectionRow[]>([
+                {
+                    id: 'unreadable-connection-id',
+                    name: null,
+                    source: {
+                        id: 'hidden-source-id',
+                        groupId: SOURCE_GROUP_ID,
+                        type: ComponentType.Processor,
+                        name: null
+                    },
+                    destination: {
+                        id: 'hidden-destination-id',
+                        groupId: DESTINATION_GROUP_ID,
+                        type: ComponentType.InputPort,
+                        name: null
+                    }
+                }
+            ]);
+
+            expect(textContent(fixture)).toContain('Unauthorized');
+        });
+
+        it('maps remote input and output port endpoint types to 
RemoteProcessGroup', () => {
+            const remoteInputConnection = readableConnection({
+                id: 'remote-input-connection-id',
+                sourceType: 'REMOTE_INPUT_PORT',
+                destinationType: 'REMOTE_OUTPUT_PORT'
+            });
+
+            const { component } = createDialog('downstream', 
[remoteInputConnection]);
+
+            
expect(component.rows[0].source.type).toBe(ComponentType.RemoteProcessGroup);
+            
expect(component.rows[0].destination.type).toBe(ComponentType.RemoteProcessGroup);
+        });
+
+        it('maps unknown endpoint types to Connector', () => {
+            const unknownTypeConnection = readableConnection({
+                sourceType: 'UNKNOWN_SOURCE_TYPE',
+                destinationType: 'UNKNOWN_DESTINATION_TYPE'
+            });
+
+            const { component } = createDialog('downstream', 
[unknownTypeConnection]);
+
+            
expect(component.rows[0].source.type).toBe(ComponentType.Connector);
+            
expect(component.rows[0].destination.type).toBe(ComponentType.Connector);
+        });
+    });
+
+    describe('rendering', () => {
+        it('renders the expected table columns', () => {
+            const { component, fixture } = createDialog('upstream', 
[readableConnection()]);
+
+            expect(component.displayedColumns).toEqual([
+                'sourceProcessGroup',
+                'sourceComponent',
+                'connection',
+                'destinationProcessGroup',
+                'destinationComponent'
+            ]);
+
+            const renderedText = textContent(fixture);
+            expect(renderedText).toContain('Source Process Group');
+            expect(renderedText).toContain('Source Component');
+            expect(renderedText).toContain('Connection');
+            expect(renderedText).toContain('Destination Process Group');
+            expect(renderedText).toContain('Destination Component');
+        });
+
+        it('renders process group names resolved from the request map', () => {
+            const { fixture } = createDialog('upstream', 
[readableConnection()]);
+
+            expect(textContent(fixture)).toContain('Source Process Group');
+            expect(textContent(fixture)).toContain('Destination Process 
Group');
+        });
+
+        it('renders unknown process group ids when no name is available', () 
=> {
+            const connection = readableConnection({
+                sourceGroupId: UNKNOWN_GROUP_ID,
+                destinationGroupId: UNKNOWN_GROUP_ID
+            });
+
+            const { fixture } = createDialog('upstream', [connection]);
+
+            expect(textContent(fixture)).toContain(UNKNOWN_GROUP_ID);
+        });
+
+        it('renders component names and the formatted connection name', () => {
+            const connection = readableConnection({
+                name: 'Connection Name',
+                source: { id: 'source-component-id', name: 'Source Component 
Name' },
+                destination: { id: 'destination-component-id', name: 
'Destination Component Name' }
+            });
+
+            const { fixture } = createDialog('upstream', [connection]);
+
+            const renderedText = textContent(fixture);
+            expect(renderedText).toContain('Source Component Name');
+            expect(renderedText).toContain('Connection Name');
+            expect(renderedText).toContain('Destination Component Name');
+        });
+
+        it('renders "Connection" for an unnamed connection', () => {
+            const { component, fixture } = createDialog('upstream', 
[readableConnection()]);
+
+            expect(component.rows[0].name).toBeNull();
+            expect(textContent(fixture)).toContain('Connection');
+        });
+
+        it('marks the header as sticky and applies striped row classes', () => 
{
+            const { fixture } = createDialog('upstream', [
+                readableConnection({ id: 'connection-1' }),
+                readableConnection({ id: 'connection-2' })
+            ]);
+
+            
expect(fixture.debugElement.query(By.css('tr.mat-mdc-header-row'))).not.toBeNull();
+
+            const rows = 
fixture.debugElement.queryAll(By.css('tr.mat-mdc-row'));
+            expect(rows.length).toBe(2);
+            
expect(rows[0].nativeElement.classList.contains('even')).toBeTruthy();
+            
expect(rows[1].nativeElement.classList.contains('even')).toBeFalsy();
+        });
+
+        it('renders table cells using component-connection-cell wrappers for 
truncation styling', () => {
+            const { fixture } = createDialog('upstream', [readableConnection({ 
name: 'Named Connection' })]);
+
+            
expect(fixture.debugElement.queryAll(By.css('.component-connection-cell')).length).toBeGreaterThan(0);
+        });
+    });
+
+    describe('process group name resolution', () => {
+        it('resolves process group names from the dialog request map', () => {
+            const { component } = createDialog('upstream', []);
+
+            expect(component.resolveGroupName(REQUEST_GROUP_ID)).toBe('Current 
Process Group');
+            expect(component.resolveGroupName(SOURCE_GROUP_ID)).toBe('Source 
Process Group');
+            
expect(component.resolveGroupName(DESTINATION_GROUP_ID)).toBe('Destination 
Process Group');
+        });
+
+        it('falls back to the group id when no process group name is 
available', () => {
+            const { component } = createDialog('upstream', []);
+
+            
expect(component.resolveGroupName(UNKNOWN_GROUP_ID)).toBe(UNKNOWN_GROUP_ID);
+        });
+
+        it('identifies the current process group from the dialog request group 
id', () => {
+            const { component } = createDialog('upstream', []);
+
+            
expect(component.isCurrentProcessGroup(REQUEST_GROUP_ID)).toBeTruthy();
+            
expect(component.isCurrentProcessGroup(SOURCE_GROUP_ID)).toBeFalsy();
+        });
+    });
+
+    describe('navigation', () => {
+        it('dispatches navigation and closes the dialog when navigateTo is 
called', () => {
+            const { component, store, dialogRef } = createDialog('upstream', 
[]);
+            const dispatch = vi.spyOn(store, 'dispatch');
+
+            component.navigateTo('target-id', 'target-group-id', 
ComponentType.Processor);
+
+            expect(dispatch).toHaveBeenCalledWith(
+                navigateToComponent({
+                    request: {
+                        id: 'target-id',
+                        processGroupId: 'target-group-id',
+                        type: ComponentType.Processor
+                    }
+                })
+            );
+            expect(dialogRef.close).toHaveBeenCalled();
+        });
+
+        it('renders the current source process group as non-clickable', () => {
+            const connection = readableConnection({
+                sourceGroupId: REQUEST_GROUP_ID,
+                destinationGroupId: DESTINATION_GROUP_ID
+            });
+
+            const { fixture } = createDialog('upstream', [connection]);
+
+            const sourceProcessGroupCell = getCells(fixture, 
'mat-column-sourceProcessGroup')[0];
+            
expect(sourceProcessGroupCell.querySelector('span')).not.toBeNull();
+            expect(sourceProcessGroupCell.querySelector('a')).toBeNull();
+        });
+
+        it('renders a non-current source process group as clickable and 
navigates to it', () => {
+            const connection = readableConnection({
+                sourceGroupId: SOURCE_GROUP_ID
+            });
+
+            const { fixture, store, dialogRef } = createDialog('upstream', 
[connection]);
+            const dispatch = vi.spyOn(store, 'dispatch');
+
+            const sourceProcessGroupCell = getCells(fixture, 
'mat-column-sourceProcessGroup')[0];
+            const link = sourceProcessGroupCell.querySelector('a') as 
HTMLAnchorElement;
+            link.click();
+
+            expect(dispatch).toHaveBeenCalledWith(
+                navigateToComponent({
+                    request: {
+                        id: SOURCE_GROUP_ID,
+                        processGroupId: REQUEST_GROUP_ID,
+                        type: ComponentType.ProcessGroup
+                    }
+                })
+            );
+            expect(dialogRef.close).toHaveBeenCalled();
+        });
+
+        it('renders the current destination process group as non-clickable', 
() => {
+            const connection = readableConnection({
+                sourceGroupId: SOURCE_GROUP_ID,
+                destinationGroupId: REQUEST_GROUP_ID
+            });
+
+            const { fixture } = createDialog('upstream', [connection]);
+
+            const destinationProcessGroupCell = getCells(fixture, 
'mat-column-destinationProcessGroup')[0];
+            
expect(destinationProcessGroupCell.querySelector('span')).not.toBeNull();
+            expect(destinationProcessGroupCell.querySelector('a')).toBeNull();
+        });
+
+        it('navigates to the readable source component using the source 
component group id', () => {
+            const connection = readableConnection({
+                source: { id: 'source-component-id', name: 'Source Component' 
},
+                sourceGroupId: SOURCE_GROUP_ID,
+                sourceType: 'PROCESSOR'
+            });
+
+            const { fixture, store, dialogRef } = createDialog('upstream', 
[connection]);
+            const dispatch = vi.spyOn(store, 'dispatch');
+
+            const sourceComponentCell = getCells(fixture, 
'mat-column-sourceComponent')[0];
+            const link = sourceComponentCell.querySelector('a') as 
HTMLAnchorElement;
+            link.click();
+
+            expect(dispatch).toHaveBeenCalledWith(
+                navigateToComponent({
+                    request: {
+                        id: 'source-component-id',
+                        processGroupId: SOURCE_GROUP_ID,
+                        type: ComponentType.Processor
+                    }
+                })
+            );
+            expect(dialogRef.close).toHaveBeenCalled();
+        });
+
+        it('navigates to the readable destination component using the 
destination component group id', () => {
+            const connection = readableConnection({
+                destination: { id: 'destination-component-id', name: 
'Destination Component' },
+                destinationGroupId: DESTINATION_GROUP_ID,
+                destinationType: 'OUTPUT_PORT'
+            });
+
+            const { fixture, store, dialogRef } = createDialog('downstream', 
[connection]);
+            const dispatch = vi.spyOn(store, 'dispatch');
+
+            const destinationComponentCell = getCells(fixture, 
'mat-column-destinationComponent')[0];
+            const link = destinationComponentCell.querySelector('a') as 
HTMLAnchorElement;
+            link.click();
+
+            expect(dispatch).toHaveBeenCalledWith(
+                navigateToComponent({
+                    request: {
+                        id: 'destination-component-id',
+                        processGroupId: DESTINATION_GROUP_ID,
+                        type: ComponentType.OutputPort
+                    }
+                })
+            );
+            expect(dialogRef.close).toHaveBeenCalled();
+        });
+
+        it('does not render unreadable components as clickable', () => {
+            const { fixture } = createDialog('upstream', 
[unreadableConnection()]);
+
+            const sourceComponentCell = getCells(fixture, 
'mat-column-sourceComponent')[0];
+            const destinationComponentCell = getCells(fixture, 
'mat-column-destinationComponent')[0];
+
+            expect(sourceComponentCell.querySelector('a')).toBeNull();
+            expect(destinationComponentCell.querySelector('a')).toBeNull();
+            expect(sourceComponentCell.textContent).toContain('Unauthorized');
+            
expect(destinationComponentCell.textContent).toContain('Unauthorized');
+        });
+
+        it('renders a remote input port source component as non-clickable', () 
=> {
+            const connection = readableConnection({
+                source: { id: 'remote-input-port-id', name: 'Remote Input 
Port' },
+                sourceGroupId: 'remote-process-group-id',
+                sourceType: 'REMOTE_INPUT_PORT',
+                destination: { id: 'processor-id', name: 'Processor' },
+                destinationType: 'PROCESSOR'
+            });
+
+            const { fixture, store, dialogRef } = createDialog('downstream', 
[connection], {
+                remoteProcessGroupIds: new Set(['remote-process-group-id'])
+            });
+            const dispatch = vi.spyOn(store, 'dispatch');
+
+            const sourceComponentCell = getCells(fixture, 
'mat-column-sourceComponent')[0];
+
+            expect(sourceComponentCell.textContent).toContain('Remote Input 
Port');
+            expect(sourceComponentCell.querySelector('span')).not.toBeNull();
+            expect(sourceComponentCell.querySelector('a')).toBeNull();
+            expect(dispatch).not.toHaveBeenCalled();
+            expect(dialogRef.close).not.toHaveBeenCalled();
+        });
+
+        it('renders a remote output port source component as non-clickable', 
() => {
+            const connection = readableConnection({
+                source: { id: 'remote-output-port-id', name: 'Remote Output 
Port' },
+                sourceGroupId: 'remote-process-group-id',
+                sourceType: 'REMOTE_OUTPUT_PORT',
+                destination: { id: 'processor-id', name: 'Processor' },
+                destinationType: 'PROCESSOR'
+            });
+
+            const { fixture, store, dialogRef } = createDialog('downstream', 
[connection], {
+                remoteProcessGroupIds: new Set(['remote-process-group-id'])
+            });
+            const dispatch = vi.spyOn(store, 'dispatch');
+
+            const sourceComponentCell = getCells(fixture, 
'mat-column-sourceComponent')[0];
+
+            expect(sourceComponentCell.textContent).toContain('Remote Output 
Port');
+            expect(sourceComponentCell.querySelector('span')).not.toBeNull();
+            expect(sourceComponentCell.querySelector('a')).toBeNull();
+            expect(dispatch).not.toHaveBeenCalled();
+            expect(dialogRef.close).not.toHaveBeenCalled();
+        });
+
+        it('renders a remote input port destination component as 
non-clickable', () => {
+            const connection = readableConnection({
+                source: { id: 'processor-id', name: 'Processor' },
+                sourceType: 'PROCESSOR',
+                destination: { id: 'remote-input-port-id', name: 'Remote Input 
Port' },
+                destinationGroupId: 'remote-process-group-id',
+                destinationType: 'REMOTE_INPUT_PORT'
+            });
+
+            const { fixture, store, dialogRef } = createDialog('upstream', 
[connection], {
+                remoteProcessGroupIds: new Set(['remote-process-group-id'])
+            });
+            const dispatch = vi.spyOn(store, 'dispatch');
+
+            const destinationComponentCell = getCells(fixture, 
'mat-column-destinationComponent')[0];
+
+            expect(destinationComponentCell.textContent).toContain('Remote 
Input Port');
+            
expect(destinationComponentCell.querySelector('span')).not.toBeNull();
+            expect(destinationComponentCell.querySelector('a')).toBeNull();
+            expect(dispatch).not.toHaveBeenCalled();
+            expect(dialogRef.close).not.toHaveBeenCalled();
+        });
+
+        it('renders a remote output port destination component as 
non-clickable', () => {
+            const connection = readableConnection({
+                source: { id: 'processor-id', name: 'Processor' },
+                sourceType: 'PROCESSOR',
+                destination: { id: 'remote-output-port-id', name: 'Remote 
Output Port' },
+                destinationGroupId: 'remote-process-group-id',
+                destinationType: 'REMOTE_OUTPUT_PORT'
+            });
+
+            const { fixture, store, dialogRef } = createDialog('upstream', 
[connection], {
+                remoteProcessGroupIds: new Set(['remote-process-group-id'])
+            });
+            const dispatch = vi.spyOn(store, 'dispatch');
+
+            const destinationComponentCell = getCells(fixture, 
'mat-column-destinationComponent')[0];
+
+            expect(destinationComponentCell.textContent).toContain('Remote 
Output Port');
+            
expect(destinationComponentCell.querySelector('span')).not.toBeNull();
+            expect(destinationComponentCell.querySelector('a')).toBeNull();
+            expect(dispatch).not.toHaveBeenCalled();
+            expect(dialogRef.close).not.toHaveBeenCalled();
+        });
+
+        it('continues rendering standard input and output port components as 
clickable', () => {
+            const connection = readableConnection({
+                source: { id: 'output-port-id', name: 'Output Port' },
+                sourceGroupId: SOURCE_GROUP_ID,
+                sourceType: 'OUTPUT_PORT',
+                destination: { id: 'input-port-id', name: 'Input Port' },
+                destinationGroupId: DESTINATION_GROUP_ID,
+                destinationType: 'INPUT_PORT'
+            });
+
+            const { fixture } = createDialog('downstream', [connection]);
+
+            const sourceComponentCell = getCells(fixture, 
'mat-column-sourceComponent')[0];
+            const destinationComponentCell = getCells(fixture, 
'mat-column-destinationComponent')[0];
+
+            expect(sourceComponentCell.querySelector('a')).not.toBeNull();
+            expect(sourceComponentCell.querySelector('span')).toBeNull();
+            expect(destinationComponentCell.querySelector('a')).not.toBeNull();
+            expect(destinationComponentCell.querySelector('span')).toBeNull();
+        });
+
+        it('navigates to the connection in the group that defines the dialog 
request', () => {
+            const connection = readableConnection({
+                id: 'connection-to-navigate-to',
+                name: 'Connection To Navigate To'
+            });
+
+            const { fixture, store, dialogRef } = createDialog('upstream', 
[connection]);
+            const dispatch = vi.spyOn(store, 'dispatch');
+
+            const connectionCell = getCells(fixture, 
'mat-column-connection')[0];
+            const link = connectionCell.querySelector('a') as 
HTMLAnchorElement;
+            link.click();
+
+            expect(dispatch).toHaveBeenCalledWith(
+                navigateToComponent({
+                    request: {
+                        id: 'connection-to-navigate-to',
+                        processGroupId: REQUEST_GROUP_ID,
+                        type: ComponentType.Connection
+                    }
+                })
+            );
+            expect(dialogRef.close).toHaveBeenCalled();
+        });
+
+        it('navigates to the connection in the group that defines the dialog 
request', () => {
+            const connection = readableConnection({
+                id: 'connection-to-navigate-to',
+                name: 'Connection To Navigate To'
+            });
+
+            const { fixture, store, dialogRef } = createDialog('upstream', 
[connection]);
+            const dispatch = vi.spyOn(store, 'dispatch');
+
+            const connectionCell = getCells(fixture, 
'mat-column-connection')[0];
+            const link = connectionCell.querySelector('a') as 
HTMLAnchorElement;
+            link.click();
+
+            expect(dispatch).toHaveBeenCalledWith(
+                navigateToComponent({
+                    request: {
+                        id: 'connection-to-navigate-to',
+                        processGroupId: REQUEST_GROUP_ID,
+                        type: ComponentType.Connection
+                    }
+                })
+            );
+            expect(dialogRef.close).toHaveBeenCalled();
+        });

Review Comment:
   Removed duplicate.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to