fix the wrong label displayed on semantic status (#23814)

This commit is contained in:
Ashish Gupta 2025-10-09 21:19:47 +05:30 committed by GitHub
parent 05f064787f
commit 9a18e14cb4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 395 additions and 41 deletions

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
import Icon from '@ant-design/icons';
import { Col, Row, Typography } from 'antd';
import { Typography } from 'antd';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { ReactComponent as FailIcon } from '../../../assets/svg/ic-fail.svg';
@ -22,6 +22,7 @@ import { DataContractResult } from '../../../generated/entity/datacontract/dataC
import { getContractStatusType } from '../../../utils/DataContract/DataContractUtils';
import RichTextEditorPreviewerNew from '../../common/RichTextEditor/RichTextEditorPreviewNew';
import StatusBadgeV2 from '../../common/StatusBadge/StatusBadgeV2.component';
import './contract-semantics.less';
const ContractSemantics: React.FC<{
semantics: SemanticsRule[];
@ -47,48 +48,44 @@ const ContractSemantics: React.FC<{
};
return (
<Row className="contract-semantic-component-container" gutter={[20, 0]}>
<Col span={18}>
<div className="rule-item-container">
{semantics.map((item) => (
<div className="rule-item" key={item.rule}>
<Icon
className={classNames('rule-icon', {
'rule-icon-default': !latestContractResults,
})}
component={getSemanticIconPerLastExecution(item.name)}
/>
<div className="rule-item-content">
<Typography.Text className="rule-name">
{item.name}
</Typography.Text>
<Typography.Text className="rule-description">
<RichTextEditorPreviewerNew
enableSeeMoreVariant
markdown={item.description}
maxLineLength="3"
/>
</Typography.Text>
</div>
</div>
))}
</div>
</Col>
<Col className="d-flex justify-end" span={6}>
{contractStatus && (
<div className="contract-status-container">
<Typography.Text>{`${t('label.entity-status', {
entity: t('label.schema'),
})} :`}</Typography.Text>
<StatusBadgeV2
dataTestId="contract-status-card-item-semantics-status"
label={contractStatus}
status={getContractStatusType(contractStatus)}
<div className="contract-semantic-component-container">
<div className="rule-item-container">
{semantics.map((item) => (
<div className="rule-item" key={item.rule}>
<Icon
className={classNames('rule-icon', {
'rule-icon-default': !latestContractResults,
})}
component={getSemanticIconPerLastExecution(item.name)}
/>
<div className="rule-item-content">
<Typography.Text className="rule-name">
{item.name}
</Typography.Text>
<Typography.Text className="rule-description">
<RichTextEditorPreviewerNew
enableSeeMoreVariant
markdown={item.description}
maxLineLength="3"
/>
</Typography.Text>
</div>
</div>
)}
</Col>
</Row>
))}
</div>
{contractStatus && (
<div className="contract-status-container">
<Typography.Text>{`${t('label.entity-status', {
entity: t('label.semantic-plural'),
})} :`}</Typography.Text>
<StatusBadgeV2
dataTestId="contract-status-card-item-semantics-status"
label={contractStatus}
status={getContractStatusType(contractStatus)}
/>
</div>
)}
</div>
);
};

View File

@ -0,0 +1,335 @@
/*
* 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 { render, screen } from '@testing-library/react';
import { SemanticsRule } from '../../../generated/entity/data/dataContract';
import {
ContractExecutionStatus,
DataContractResult,
} from '../../../generated/entity/datacontract/dataContractResult';
import { getContractStatusType } from '../../../utils/DataContract/DataContractUtils';
import ContractSemantics from './ContractSemantics.component';
jest.mock('../../common/RichTextEditor/RichTextEditorPreviewNew', () => {
return function MockRichTextEditorPreviewerNew({
markdown,
}: {
markdown: string;
}) {
return <div data-testid="rich-text-preview">{markdown}</div>;
};
});
jest.mock('../../common/StatusBadge/StatusBadgeV2.component', () => {
return function MockStatusBadgeV2({
label,
dataTestId,
}: {
label: string;
dataTestId: string;
}) {
return <div data-testid={dataTestId}>{label}</div>;
};
});
jest.mock('../../../utils/DataContract/DataContractUtils', () => ({
getContractStatusType: jest.fn((status: string) => status),
}));
jest.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string, options?: Record<string, unknown>) => {
const translations: Record<string, string> = {
'label.entity-status': `${options?.entity} Status`,
'label.semantic-plural': 'Semantic',
};
return translations[key] || key;
},
}),
}));
const mockSemantics: SemanticsRule[] = [
{
name: 'Rule 1',
description: 'First semantic rule description',
rule: '{"and":[{"==":[{"var":"name"},"test"]}]}',
enabled: true,
},
{
name: 'Rule 2',
description: 'Second semantic rule description',
rule: '{"and":[{"==":[{"var":"age"},"25"]}]}',
enabled: true,
},
];
const mockLatestContractResults: DataContractResult = {
timestamp: Date.now(),
dataContractFQN: 'contract',
contractExecutionStatus: ContractExecutionStatus.Success,
semanticsValidation: {
passed: 1,
failed: 1,
total: 2,
failedRules: [
{
ruleName: 'Rule 2',
reason: 'Second semantic rule description',
},
],
},
} as DataContractResult;
describe('ContractSemantics', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Basic Rendering', () => {
it('should render without crashing', () => {
render(<ContractSemantics semantics={mockSemantics} />);
expect(screen.getByText('Rule 1')).toBeInTheDocument();
expect(screen.getByText('Rule 2')).toBeInTheDocument();
});
it('should render all semantic rules', () => {
render(<ContractSemantics semantics={mockSemantics} />);
expect(
screen.getByText('First semantic rule description')
).toBeInTheDocument();
expect(
screen.getByText('Second semantic rule description')
).toBeInTheDocument();
});
it('should render with empty semantics array', () => {
const { container } = render(<ContractSemantics semantics={[]} />);
expect(container.querySelector('.rule-item')).not.toBeInTheDocument();
});
});
describe('Icon Rendering Based on Execution Results', () => {
it('should render default icons when no latest results are provided', () => {
const { container } = render(
<ContractSemantics semantics={mockSemantics} />
);
const icons = container.querySelectorAll('.rule-icon-default');
expect(icons).toHaveLength(2);
});
it('should render success icon for passed rules', () => {
render(
<ContractSemantics
latestContractResults={mockLatestContractResults}
semantics={mockSemantics}
/>
);
const icons = document.querySelectorAll('.rule-icon');
expect(icons).toHaveLength(2);
});
it('should render fail icon for failed rules', () => {
render(
<ContractSemantics
latestContractResults={mockLatestContractResults}
semantics={mockSemantics}
/>
);
const icons = document.querySelectorAll('.rule-icon');
expect(icons).toHaveLength(2);
});
});
describe('Contract Status Display', () => {
it('should display contract status when provided', () => {
render(
<ContractSemantics contractStatus="Passed" semantics={mockSemantics} />
);
expect(screen.getByText('Semantic Status :')).toBeInTheDocument();
expect(
screen.getByTestId('contract-status-card-item-semantics-status')
).toBeInTheDocument();
expect(screen.getByText('Passed')).toBeInTheDocument();
});
it('should not display contract status when not provided', () => {
render(<ContractSemantics semantics={mockSemantics} />);
expect(screen.queryByText('Semantic Status :')).not.toBeInTheDocument();
expect(
screen.queryByTestId('contract-status-card-item-semantics-status')
).not.toBeInTheDocument();
});
it('should display different status values correctly', () => {
const { rerender } = render(
<ContractSemantics contractStatus="Failed" semantics={mockSemantics} />
);
expect(screen.getByText('Failed')).toBeInTheDocument();
rerender(
<ContractSemantics contractStatus="Success" semantics={mockSemantics} />
);
expect(screen.getByText('Success')).toBeInTheDocument();
});
});
describe('Rich Text Preview Integration', () => {
it('should render RichTextEditorPreviewerNew for each rule description', () => {
render(<ContractSemantics semantics={mockSemantics} />);
const richTextPreviews = screen.getAllByTestId('rich-text-preview');
expect(richTextPreviews).toHaveLength(2);
expect(richTextPreviews[0]).toHaveTextContent(
'First semantic rule description'
);
expect(richTextPreviews[1]).toHaveTextContent(
'Second semantic rule description'
);
});
});
describe('Layout and Structure', () => {
it('should render with proper row and column structure', () => {
const { container } = render(
<ContractSemantics semantics={mockSemantics} />
);
expect(
container.querySelector('.contract-semantic-component-container')
).toBeInTheDocument();
expect(
container.querySelector('.rule-item-container')
).toBeInTheDocument();
});
it('should render multiple rule items', () => {
const { container } = render(
<ContractSemantics semantics={mockSemantics} />
);
const ruleItems = container.querySelectorAll('.rule-item');
expect(ruleItems).toHaveLength(2);
});
});
describe('Edge Cases', () => {
it('should handle semantics with missing descriptions', () => {
const semanticsWithoutDescription: SemanticsRule[] = [
{
name: 'Rule Without Description',
description: '',
rule: '{"and":[{"==":[{"var":"name"},"test"]}]}',
enabled: true,
},
];
render(<ContractSemantics semantics={semanticsWithoutDescription} />);
expect(screen.getByText('Rule Without Description')).toBeInTheDocument();
});
it('should handle latestContractResults without semanticsValidation', () => {
const resultsWithoutSemantics = {
...mockLatestContractResults,
semanticsValidation: undefined,
};
render(
<ContractSemantics
latestContractResults={resultsWithoutSemantics}
semantics={mockSemantics}
/>
);
expect(screen.getByText('Rule 1')).toBeInTheDocument();
expect(screen.getByText('Rule 2')).toBeInTheDocument();
});
it('should handle latestContractResults without failedRules', () => {
const resultsWithoutFailedRules: DataContractResult = {
...mockLatestContractResults,
semanticsValidation: {
passed: 2,
failed: 0,
total: 2,
failedRules: [],
},
};
render(
<ContractSemantics
latestContractResults={resultsWithoutFailedRules}
semantics={mockSemantics}
/>
);
expect(screen.getByText('Rule 1')).toBeInTheDocument();
expect(screen.getByText('Rule 2')).toBeInTheDocument();
});
it('should handle single semantic rule', () => {
const singleSemantic: SemanticsRule[] = [
{
name: 'Single Rule',
description: 'Single rule description',
rule: '{"and":[{"==":[{"var":"name"},"test"]}]}',
enabled: true,
},
];
render(<ContractSemantics semantics={singleSemantic} />);
expect(screen.getByText('Single Rule')).toBeInTheDocument();
expect(screen.getByText('Single rule description')).toBeInTheDocument();
});
});
describe('Integration with getContractStatusType', () => {
it('should call getContractStatusType with correct status', () => {
render(
<ContractSemantics contractStatus="Passed" semantics={mockSemantics} />
);
expect(getContractStatusType).toHaveBeenCalledWith('Passed');
});
});
describe('Accessibility', () => {
it('should have proper semantic HTML structure', () => {
const { container } = render(
<ContractSemantics contractStatus="Passed" semantics={mockSemantics} />
);
expect(container.querySelector('.rule-name')).toBeInTheDocument();
expect(container.querySelector('.rule-description')).toBeInTheDocument();
expect(
container.querySelector('.contract-status-container')
).toBeInTheDocument();
});
});
});

View File

@ -0,0 +1,22 @@
/*
* 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.
*/
.contract-semantic-component-container {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 120px;
.contract-status-container {
flex: none;
}
}