mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-30 19:36:41 +00:00
feat: add a domain icon and drop-down for multiple domain names (#23299)
* feat: add domain icon and comma-separated domain names * fix integration test id * Revamp domain render on search card * fix unit test unused props * fix integration test * nit * fix minor unused props * Fix all failing integration test * nit * Fix domain propagation test * Change font size for domain count * fix overflow count number --------- Co-authored-by: Anujkumar Yadav <anujkumaryadav@Anujkumars-MacBook-Pro.local>
This commit is contained in:
parent
9fd34c8f89
commit
76c4e371a9
@ -14,7 +14,12 @@ import { APIRequestContext, expect, Page } from '@playwright/test';
|
|||||||
import { Operation } from 'fast-json-patch';
|
import { Operation } from 'fast-json-patch';
|
||||||
import { SERVICE_TYPE } from '../../constant/service';
|
import { SERVICE_TYPE } from '../../constant/service';
|
||||||
import { ServiceTypes } from '../../constant/settings';
|
import { ServiceTypes } from '../../constant/settings';
|
||||||
import { assignDomain, removeDomain, uuid } from '../../utils/common';
|
import {
|
||||||
|
assignDomain,
|
||||||
|
removeDomain,
|
||||||
|
uuid,
|
||||||
|
verifyDomainLinkInCard,
|
||||||
|
} from '../../utils/common';
|
||||||
import {
|
import {
|
||||||
addMultiOwner,
|
addMultiOwner,
|
||||||
addOwner,
|
addOwner,
|
||||||
@ -250,17 +255,16 @@ export class DatabaseClass extends EntityClass {
|
|||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyDomainChangeInES(page: Page, domain: Domain['responseData']) {
|
async verifyDomainChangeInES(page: Page, domains: Domain['responseData'][]) {
|
||||||
// Verify domain change in ES
|
|
||||||
const searchTerm = this.tableResponseData?.['fullyQualifiedName'];
|
const searchTerm = this.tableResponseData?.['fullyQualifiedName'];
|
||||||
await page.getByTestId('searchBox').fill(searchTerm);
|
await page.getByTestId('searchBox').fill(searchTerm);
|
||||||
await page.getByTestId('searchBox').press('Enter');
|
await page.getByTestId('searchBox').press('Enter');
|
||||||
|
|
||||||
await expect(
|
const entityCard = page.getByTestId(`table-data-card_${searchTerm}`);
|
||||||
page
|
|
||||||
.getByTestId(`table-data-card_${searchTerm}`)
|
for (const domain of domains) {
|
||||||
.getByTestId('domains-link')
|
await verifyDomainLinkInCard(entityCard, domain);
|
||||||
).toContainText(domain.displayName);
|
}
|
||||||
|
|
||||||
await page.getByTestId('searchBox').clear();
|
await page.getByTestId('searchBox').clear();
|
||||||
}
|
}
|
||||||
@ -272,7 +276,7 @@ export class DatabaseClass extends EntityClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async verifyDomainPropagation(page: Page, domain: Domain['responseData']) {
|
async verifyDomainPropagation(page: Page, domain: Domain['responseData']) {
|
||||||
await this.verifyDomainChangeInES(page, domain);
|
await this.verifyDomainChangeInES(page, [domain]);
|
||||||
await this.visitEntityPage(page);
|
await this.visitEntityPage(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { Browser, expect, Page, request } from '@playwright/test';
|
import { Browser, expect, Locator, Page, request } from '@playwright/test';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
import { SidebarItem } from '../constant/sidebar';
|
import { SidebarItem } from '../constant/sidebar';
|
||||||
import { adjectives, nouns } from '../constant/user';
|
import { adjectives, nouns } from '../constant/user';
|
||||||
@ -411,6 +411,23 @@ export const generateRandomUsername = (prefix = '') => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const verifyDomainLinkInCard = async (
|
||||||
|
entityCard: Locator,
|
||||||
|
domain: Domain['responseData']
|
||||||
|
) => {
|
||||||
|
const domainLink = entityCard.getByTestId('domain-link').filter({
|
||||||
|
hasText: domain.displayName,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(domainLink).toBeVisible();
|
||||||
|
await expect(domainLink).toContainText(domain.displayName);
|
||||||
|
|
||||||
|
const href = await domainLink.getAttribute('href');
|
||||||
|
|
||||||
|
expect(href).toContain('/domain/');
|
||||||
|
await expect(domainLink).toBeEnabled();
|
||||||
|
};
|
||||||
|
|
||||||
export const verifyDomainPropagation = async (
|
export const verifyDomainPropagation = async (
|
||||||
page: Page,
|
page: Page,
|
||||||
domain: Domain['responseData'],
|
domain: Domain['responseData'],
|
||||||
@ -418,12 +435,16 @@ export const verifyDomainPropagation = async (
|
|||||||
) => {
|
) => {
|
||||||
await page.getByTestId('searchBox').fill(childFqnSearchTerm);
|
await page.getByTestId('searchBox').fill(childFqnSearchTerm);
|
||||||
await page.getByTestId('searchBox').press('Enter');
|
await page.getByTestId('searchBox').press('Enter');
|
||||||
|
await page.waitForSelector(`[data-testid*="table-data-card"]`);
|
||||||
|
|
||||||
await expect(
|
const entityCard = page.getByTestId(`table-data-card_${childFqnSearchTerm}`);
|
||||||
page
|
|
||||||
.getByTestId(`table-data-card_${childFqnSearchTerm}`)
|
await expect(entityCard).toBeVisible();
|
||||||
.getByTestId('domains-link')
|
|
||||||
).toContainText(domain.displayName);
|
const domainLink = entityCard.getByTestId('domain-link').first();
|
||||||
|
|
||||||
|
await expect(domainLink).toBeVisible();
|
||||||
|
await expect(domainLink).toContainText(domain.displayName);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const replaceAllSpacialCharWith_ = (text: string) => {
|
export const replaceAllSpacialCharWith_ = (text: string) => {
|
||||||
|
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 Collate.
|
||||||
|
* Licensed 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 '@testing-library/jest-dom';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import searchClassBase from '../../../utils/SearchClassBase';
|
||||||
|
import ExploreSearchCard from './ExploreSearchCard';
|
||||||
|
import { ExploreSearchCardProps } from './ExploreSearchCard.interface';
|
||||||
|
|
||||||
|
jest.mock('../../../utils/RouterUtils', () => ({
|
||||||
|
getDomainPath: jest.fn().mockReturnValue('/mock-domain'),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../utils/EntityUtils', () => ({
|
||||||
|
getEntityName: jest.fn().mockReturnValue('Mock Entity'),
|
||||||
|
highlightSearchText: jest.fn().mockReturnValue(''),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../utils/SearchClassBase', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
default: {
|
||||||
|
getListOfEntitiesWithoutDomain: jest.fn(),
|
||||||
|
getListOfEntitiesWithoutTier: jest.fn().mockReturnValue([]),
|
||||||
|
getServiceIcon: jest.fn().mockReturnValue(<span>service-icon</span>),
|
||||||
|
getEntityBreadcrumbs: jest.fn().mockReturnValue([]),
|
||||||
|
getEntityIcon: jest.fn().mockReturnValue(<span>entity-icon</span>),
|
||||||
|
getEntityLink: jest.fn().mockReturnValue('/entity/test'),
|
||||||
|
getEntityName: jest.fn().mockReturnValue('Test Domain'),
|
||||||
|
getSearchEntityLinkTarget: jest.fn().mockReturnValue('_self'),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../common/DomainDisplay/DomainDisplay.component', () => ({
|
||||||
|
DomainDisplay: jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(<div data-testid="domain-display">Domain Display</div>),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const baseSource: ExploreSearchCardProps['source'] = {
|
||||||
|
id: 'base-1',
|
||||||
|
fullyQualifiedName: 'test.fqn',
|
||||||
|
name: 'test',
|
||||||
|
entityType: 'table',
|
||||||
|
tags: [],
|
||||||
|
owners: [],
|
||||||
|
domains: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultProps: Omit<ExploreSearchCardProps, 'source'> = {
|
||||||
|
id: '1',
|
||||||
|
showEntityIcon: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCard = (
|
||||||
|
sourceOverrides: Partial<ExploreSearchCardProps['source']>
|
||||||
|
) =>
|
||||||
|
render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<ExploreSearchCard
|
||||||
|
{...defaultProps}
|
||||||
|
source={{ ...baseSource, ...sourceOverrides }}
|
||||||
|
/>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('ExploreSearchCard - Domain section', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders DomainDisplay component', () => {
|
||||||
|
renderCard({
|
||||||
|
domains: [{ id: '1', fullyQualifiedName: 'domain.test', type: 'domain' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByTestId('domain-display')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders empty Domain row when no domains exist and entity requires domain', () => {
|
||||||
|
(
|
||||||
|
searchClassBase.getListOfEntitiesWithoutDomain as jest.Mock
|
||||||
|
).mockReturnValue([]);
|
||||||
|
|
||||||
|
renderCard({ domains: [] });
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('domain-icon')).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(screen.getByTestId('Domain')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render Domain when entityType is excluded from domains', () => {
|
||||||
|
(
|
||||||
|
searchClassBase.getListOfEntitiesWithoutDomain as jest.Mock
|
||||||
|
).mockReturnValue(['table']);
|
||||||
|
|
||||||
|
renderCard({ domains: [] });
|
||||||
|
|
||||||
|
expect(screen.queryByText('Domain')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
@ -30,13 +30,13 @@ import { Table } from '../../../generated/entity/data/table';
|
|||||||
import { EntityReference } from '../../../generated/entity/type';
|
import { EntityReference } from '../../../generated/entity/type';
|
||||||
import { TagLabel } from '../../../generated/tests/testCase';
|
import { TagLabel } from '../../../generated/tests/testCase';
|
||||||
import { AssetCertification } from '../../../generated/type/assetCertification';
|
import { AssetCertification } from '../../../generated/type/assetCertification';
|
||||||
import { getEntityName, highlightSearchText } from '../../../utils/EntityUtils';
|
import { highlightSearchText } from '../../../utils/EntityUtils';
|
||||||
import { getDomainPath } from '../../../utils/RouterUtils';
|
|
||||||
import searchClassBase from '../../../utils/SearchClassBase';
|
import searchClassBase from '../../../utils/SearchClassBase';
|
||||||
import { stringToHTML } from '../../../utils/StringsUtils';
|
import { stringToHTML } from '../../../utils/StringsUtils';
|
||||||
import { getUsagePercentile } from '../../../utils/TableUtils';
|
import { getUsagePercentile } from '../../../utils/TableUtils';
|
||||||
import { useRequiredParams } from '../../../utils/useRequiredParams';
|
import { useRequiredParams } from '../../../utils/useRequiredParams';
|
||||||
import CertificationTag from '../../common/CertificationTag/CertificationTag';
|
import CertificationTag from '../../common/CertificationTag/CertificationTag';
|
||||||
|
import { DomainDisplay } from '../../common/DomainDisplay/DomainDisplay.component';
|
||||||
import { OwnerLabel } from '../../common/OwnerLabel/OwnerLabel.component';
|
import { OwnerLabel } from '../../common/OwnerLabel/OwnerLabel.component';
|
||||||
import TitleBreadcrumb from '../../common/TitleBreadcrumb/TitleBreadcrumb.component';
|
import TitleBreadcrumb from '../../common/TitleBreadcrumb/TitleBreadcrumb.component';
|
||||||
import TableDataCardBody from '../../Database/TableDataCardBody/TableDataCardBody';
|
import TableDataCardBody from '../../Database/TableDataCardBody/TableDataCardBody';
|
||||||
@ -84,14 +84,13 @@ const ExploreSearchCard: React.FC<ExploreSearchCardProps> = forwardRef<
|
|||||||
);
|
);
|
||||||
|
|
||||||
const _otherDetails: ExtraInfo[] = [
|
const _otherDetails: ExtraInfo[] = [
|
||||||
...(source?.domains
|
...(source?.domains && source.domains.length > 0
|
||||||
? source.domains.map((domain) => ({
|
? [
|
||||||
key: 'Domains',
|
{
|
||||||
value: getDomainPath(domain.fullyQualifiedName) ?? '',
|
key: 'Domains',
|
||||||
placeholderText: getEntityName(domain),
|
value: <DomainDisplay domains={source.domains} />,
|
||||||
isLink: true,
|
},
|
||||||
openInNewTab: false,
|
]
|
||||||
}))
|
|
||||||
: !searchClassBase
|
: !searchClassBase
|
||||||
.getListOfEntitiesWithoutDomain()
|
.getListOfEntitiesWithoutDomain()
|
||||||
.includes(source?.entityType ?? '')
|
.includes(source?.entityType ?? '')
|
||||||
|
@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 Collate.
|
||||||
|
* Licensed 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 { Dropdown, Typography } from 'antd';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { ReactComponent as DomainIcon } from '../../../assets/svg/ic-domain.svg';
|
||||||
|
import { EntityReference } from '../../../generated/entity/type';
|
||||||
|
import { getEntityName } from '../../../utils/EntityUtils';
|
||||||
|
import { getDomainPath } from '../../../utils/RouterUtils';
|
||||||
|
|
||||||
|
interface DomainDisplayProps {
|
||||||
|
domains: EntityReference[];
|
||||||
|
showIcon?: boolean;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DomainLink: React.FC<{
|
||||||
|
domain: EntityReference;
|
||||||
|
}> = ({ domain }) => (
|
||||||
|
<>
|
||||||
|
<Link
|
||||||
|
className="no-underline"
|
||||||
|
data-testid="domain-link"
|
||||||
|
to={getDomainPath(domain.fullyQualifiedName) ?? ''}>
|
||||||
|
<Typography.Text className="text-sm text-primary">
|
||||||
|
{getEntityName(domain)}
|
||||||
|
</Typography.Text>
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const DomainDisplay = ({
|
||||||
|
domains,
|
||||||
|
showIcon = true,
|
||||||
|
className = '',
|
||||||
|
}: DomainDisplayProps) => {
|
||||||
|
if (!domains || domains.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (domains.length > 1) {
|
||||||
|
const firstDomain = domains[0];
|
||||||
|
const remainingDomains = domains.slice(1);
|
||||||
|
const remainingCount = remainingDomains.length;
|
||||||
|
|
||||||
|
const dropdownItems = remainingDomains.map((domain, index) => ({
|
||||||
|
key: index,
|
||||||
|
label: (
|
||||||
|
<div className="d-flex items-center gap-2">
|
||||||
|
<DomainIcon height={14} name="domain" width={14} />
|
||||||
|
<Link
|
||||||
|
className="no-underline"
|
||||||
|
to={getDomainPath(domain.fullyQualifiedName) ?? ''}>
|
||||||
|
<Typography.Text className="text-sm text-primary">
|
||||||
|
{getEntityName(domain)}
|
||||||
|
</Typography.Text>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`d-flex items-center gap-2 ${className}`}>
|
||||||
|
{showIcon && (
|
||||||
|
<div className="d-flex">
|
||||||
|
<DomainIcon
|
||||||
|
data-testid="domain-icon"
|
||||||
|
height={18}
|
||||||
|
name="domain"
|
||||||
|
width={18}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="d-flex items-center gap-2">
|
||||||
|
<DomainLink domain={firstDomain} />
|
||||||
|
|
||||||
|
<Dropdown
|
||||||
|
menu={{
|
||||||
|
items: dropdownItems,
|
||||||
|
className: 'domain-tooltip-list',
|
||||||
|
}}
|
||||||
|
trigger={['hover']}>
|
||||||
|
<Typography.Text
|
||||||
|
className={`flex-center cursor-pointer align-middle ant-typography-secondary domain-count-button ${
|
||||||
|
remainingCount <= 9 ? 'h-6 w-6' : ''
|
||||||
|
}`}
|
||||||
|
data-testid="domain-count-button">
|
||||||
|
<span className="ant-typography domain-count-label">
|
||||||
|
{`+${remainingCount}`}
|
||||||
|
</span>
|
||||||
|
</Typography.Text>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`d-flex items-center gap-2 ${className}`}>
|
||||||
|
{showIcon && (
|
||||||
|
<div className="d-flex">
|
||||||
|
<DomainIcon
|
||||||
|
data-testid="domain-icon"
|
||||||
|
height={18}
|
||||||
|
name="domain"
|
||||||
|
width={18}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="d-flex items-center gap-1">
|
||||||
|
<DomainLink domain={domains[0]} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,269 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 Collate.
|
||||||
|
* Licensed 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 '@testing-library/jest-dom';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import { EntityReference } from '../../../generated/entity/type';
|
||||||
|
import { DomainDisplay } from './DomainDisplay.component';
|
||||||
|
|
||||||
|
jest.mock('../../../utils/EntityUtils', () => ({
|
||||||
|
getEntityName: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation((entity) => entity?.name || 'Unknown'),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../utils/RouterUtils', () => ({
|
||||||
|
getDomainPath: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation((fqn: string) => `/domain/${fqn}`),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../assets/svg/ic-domain.svg', () => ({
|
||||||
|
ReactComponent: () => <div data-testid="domain-icon">Domain Icon</div>,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mockDomain1: EntityReference = {
|
||||||
|
id: 'domain-1',
|
||||||
|
fullyQualifiedName: 'domain.one',
|
||||||
|
name: 'Domain One',
|
||||||
|
type: 'domain',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockDomain2: EntityReference = {
|
||||||
|
id: 'domain-2',
|
||||||
|
fullyQualifiedName: 'domain.two',
|
||||||
|
name: 'Domain Two',
|
||||||
|
type: 'domain',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockDomain3: EntityReference = {
|
||||||
|
id: 'domain-3',
|
||||||
|
fullyQualifiedName: 'domain.three',
|
||||||
|
name: 'Domain Three',
|
||||||
|
type: 'domain',
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderDomainDisplay = (props: any) =>
|
||||||
|
render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<DomainDisplay {...props} />
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('DomainDisplay Component', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render nothing when domains array is empty', () => {
|
||||||
|
const { container } = renderDomainDisplay({ domains: [] });
|
||||||
|
|
||||||
|
expect(container.firstChild).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render nothing when domains is undefined', () => {
|
||||||
|
const { container } = renderDomainDisplay({ domains: undefined });
|
||||||
|
|
||||||
|
expect(container.firstChild).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render nothing when domains is null', () => {
|
||||||
|
const { container } = renderDomainDisplay({ domains: null });
|
||||||
|
|
||||||
|
expect(container.firstChild).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render single domain with icon by default', () => {
|
||||||
|
renderDomainDisplay({ domains: [mockDomain1] });
|
||||||
|
|
||||||
|
expect(screen.getByTestId('domain-icon')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Domain One')).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('link')).toHaveAttribute(
|
||||||
|
'href',
|
||||||
|
'/domain/domain.one'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render single domain without icon when showIcon is false', () => {
|
||||||
|
renderDomainDisplay({ domains: [mockDomain1], showIcon: false });
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('domain-icon')).not.toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Domain One')).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('link')).toHaveAttribute(
|
||||||
|
'href',
|
||||||
|
'/domain/domain.one'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render multiple domains with dropdown by default', () => {
|
||||||
|
renderDomainDisplay({
|
||||||
|
domains: [mockDomain1, mockDomain2, mockDomain3],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText('Domain One')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('domain-count-button')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('+2')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Domain Two')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Domain Three')).not.toBeInTheDocument();
|
||||||
|
expect(screen.getAllByTestId('domain-icon')).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render single domain normally', () => {
|
||||||
|
renderDomainDisplay({
|
||||||
|
domains: [mockDomain1],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText('Domain One')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('domain-count-button')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(', ')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render correct links for all domains', () => {
|
||||||
|
renderDomainDisplay({ domains: [mockDomain1, mockDomain2] });
|
||||||
|
|
||||||
|
expect(screen.getByRole('link', { name: 'Domain One' })).toHaveAttribute(
|
||||||
|
'href',
|
||||||
|
'/domain/domain.one'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle domain with missing fullyQualifiedName', () => {
|
||||||
|
const domainWithoutFQN = {
|
||||||
|
...mockDomain1,
|
||||||
|
fullyQualifiedName: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
renderDomainDisplay({ domains: [domainWithoutFQN] });
|
||||||
|
|
||||||
|
expect(screen.getByRole('link')).toHaveAttribute(
|
||||||
|
'href',
|
||||||
|
'/domain/undefined'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle domain with missing name', () => {
|
||||||
|
const domainWithoutName = {
|
||||||
|
...mockDomain1,
|
||||||
|
name: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
renderDomainDisplay({ domains: [domainWithoutName] });
|
||||||
|
|
||||||
|
expect(screen.getByText('Unknown')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have proper link accessibility', () => {
|
||||||
|
renderDomainDisplay({ domains: [mockDomain1] });
|
||||||
|
|
||||||
|
const link = screen.getByRole('link');
|
||||||
|
|
||||||
|
expect(link).toBeInTheDocument();
|
||||||
|
expect(link).toHaveAttribute('href', '/domain/domain.one');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have proper test IDs for testing', () => {
|
||||||
|
renderDomainDisplay({ domains: [mockDomain1] });
|
||||||
|
|
||||||
|
expect(screen.getByTestId('domain-icon')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('domain-link')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should style domain links correctly', () => {
|
||||||
|
renderDomainDisplay({ domains: [mockDomain1] });
|
||||||
|
|
||||||
|
const link = screen.getByRole('link');
|
||||||
|
|
||||||
|
expect(link).toHaveClass('no-underline');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should style domain text correctly', () => {
|
||||||
|
renderDomainDisplay({ domains: [mockDomain1] });
|
||||||
|
|
||||||
|
const domainText = screen.getByText('Domain One');
|
||||||
|
|
||||||
|
expect(domainText).toHaveClass('text-sm', 'text-primary');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not render icon when showIcon is false', () => {
|
||||||
|
renderDomainDisplay({ domains: [mockDomain1], showIcon: false });
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('domain-icon')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render only one icon for multiple domains', () => {
|
||||||
|
renderDomainDisplay({ domains: [mockDomain1, mockDomain2, mockDomain3] });
|
||||||
|
|
||||||
|
const domainIcons = screen.getAllByTestId('domain-icon');
|
||||||
|
|
||||||
|
expect(domainIcons).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle domain with empty name', () => {
|
||||||
|
const domainWithEmptyName = {
|
||||||
|
...mockDomain1,
|
||||||
|
name: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
renderDomainDisplay({ domains: [domainWithEmptyName] });
|
||||||
|
|
||||||
|
expect(screen.getByText('Unknown')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle domain with empty fullyQualifiedName', () => {
|
||||||
|
const domainWithEmptyFQN = {
|
||||||
|
...mockDomain1,
|
||||||
|
fullyQualifiedName: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
renderDomainDisplay({ domains: [domainWithEmptyFQN] });
|
||||||
|
|
||||||
|
expect(screen.getByRole('link')).toHaveAttribute('href', '/domain/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle mixed domain data (some with names, some without)', () => {
|
||||||
|
const domainsWithMixedData = [
|
||||||
|
mockDomain1,
|
||||||
|
{ ...mockDomain2, name: undefined },
|
||||||
|
mockDomain3,
|
||||||
|
];
|
||||||
|
|
||||||
|
renderDomainDisplay({ domains: domainsWithMixedData });
|
||||||
|
|
||||||
|
expect(screen.getByText('Domain One')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show correct count in dropdown button', () => {
|
||||||
|
const manyDomains = [
|
||||||
|
mockDomain1,
|
||||||
|
mockDomain2,
|
||||||
|
mockDomain3,
|
||||||
|
mockDomain1,
|
||||||
|
mockDomain2,
|
||||||
|
];
|
||||||
|
|
||||||
|
renderDomainDisplay({
|
||||||
|
domains: manyDomains,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText('+4')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should always use dropdown behavior for multiple domains', () => {
|
||||||
|
renderDomainDisplay({ domains: [mockDomain1, mockDomain2, mockDomain3] });
|
||||||
|
|
||||||
|
expect(screen.getByText('Domain One')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('domain-count-button')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('+2')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Domain Two')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
@ -42,14 +42,22 @@
|
|||||||
|
|
||||||
.domain-count-button {
|
.domain-count-button {
|
||||||
background-color: @primary-button-background;
|
background-color: @primary-button-background;
|
||||||
height: 32px;
|
height: auto;
|
||||||
width: 32px;
|
width: auto;
|
||||||
|
padding: 0 4px;
|
||||||
border-radius: 200px;
|
border-radius: 200px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
span {
|
span {
|
||||||
color: @blue-24;
|
color: @blue-24;
|
||||||
}
|
}
|
||||||
border: 1px solid @blue-24;
|
border: 1px solid @blue-24;
|
||||||
margin-left: -4px;
|
margin-left: -4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.domain-count-label {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user