Enhance Bulk Edit Entity tests with response handling and update MySQL ingestion class to exclude specific schemas. Refactor glossary utility functions for improved visibility checks. (#20837)

* Enhance Bulk Edit Entity tests with response handling and update MySQL ingestion class to exclude specific schemas. Refactor glossary utility functions for improved visibility checks.

* Refactor ImageAttachment component to improve loading state handling and update tests for better coverage. Adjust Bulk Edit Entity tests to streamline response handling and fix selector usage in GlossaryVersionPage tests.

* revert framenavigated wait from table
This commit is contained in:
Shailesh Parmar 2025-04-16 11:41:05 +05:30 committed by GitHub
parent fb5af8ad7c
commit be716153ee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 176 additions and 81 deletions

View File

@ -161,11 +161,17 @@ test.describe('Bulk Edit Entity', () => {
failed: '0',
});
const updateButtonResponse = page.waitForResponse(
`/api/v1/services/databaseServices/name/*/importAsync?*dryRun=false&recursive=false*`
);
await page.getByRole('button', { name: 'Update' }).click();
await page
.locator('.inovua-react-toolkit-load-mask__background-layer')
.waitFor({ state: 'detached' });
await updateButtonResponse;
await page.waitForEvent('framenavigated');
await toastNotification(page, /details updated successfully/);
await page.click('[data-testid="databases"]');
@ -311,12 +317,15 @@ test.describe('Bulk Edit Entity', () => {
await page.waitForSelector('.InovuaReactDataGrid__header-layout', {
state: 'visible',
});
const updateButtonResponse = page.waitForResponse(
`/api/v1/databases/name/*/importAsync?*dryRun=false&recursive=false*`
);
await page.getByRole('button', { name: 'Update' }).click();
await page
.locator('.inovua-react-toolkit-load-mask__background-layer')
.waitFor({ state: 'detached' });
await updateButtonResponse;
await page.waitForEvent('framenavigated');
await toastNotification(page, /details updated successfully/);
// Verify Details updated
@ -443,8 +452,13 @@ test.describe('Bulk Edit Entity', () => {
processed: '2',
failed: '0',
});
const updateButtonResponse = page.waitForResponse(
`/api/v1/databaseSchemas/name/*/importAsync?*dryRun=false&recursive=false*`
);
await page.getByRole('button', { name: 'Update' }).click();
await updateButtonResponse;
await page.waitForEvent('framenavigated');
await toastNotification(page, /details updated successfully/);
// Verify Details updated
@ -561,6 +575,9 @@ test.describe('Bulk Edit Entity', () => {
failed: '0',
});
const updateButtonResponse = page.waitForResponse(
`/api/v1/tables/name/*/importAsync?*dryRun=false&recursive=false*`
);
await page.click('[type="button"] >> text="Update"', { force: true });
await page
.locator('.inovua-react-toolkit-load-mask__background-layer')
@ -569,7 +586,7 @@ test.describe('Bulk Edit Entity', () => {
await page.waitForSelector('.message-banner-wrapper', {
state: 'detached',
});
await updateButtonResponse;
await toastNotification(page, /details updated successfully/);
// Verify Details updated

View File

@ -204,7 +204,7 @@ test('GlossaryTerm', async ({ page }) => {
const glossaryTermsRes = page.waitForResponse(
'/api/v1/glossaryTerms/name/**'
);
await page.click('[data-testid="version-button"]');
await page.getByRole('dialog').getByRole('img').click();
await page.waitForLoadState('networkidle');
await glossaryTermsRes;

View File

@ -37,6 +37,7 @@ class MysqlIngestionClass extends ServiceBaseClass {
name = '';
defaultFilters = ['^information_schema$', '^performance_schema$'];
tableFilter: string[];
excludeSchemas: string[];
profilerTable = 'alert_entity';
constructor(tableFilter?: string[]) {
const serviceName = `pw-mysql-with-%-${uuid()}`;
@ -47,6 +48,7 @@ class MysqlIngestionClass extends ServiceBaseClass {
'alert_entity',
'chart_entity',
];
this.excludeSchemas = ['openmetadata'];
}
async createService(page: Page) {
@ -77,6 +79,12 @@ class MysqlIngestionClass extends ServiceBaseClass {
.locator('#root\\/tableFilterPattern\\/includes')
.press('Enter');
}
for (const schema of this.excludeSchemas) {
await page.fill('#root\\/schemaFilterPattern\\/excludes', schema);
await page
.locator('#root\\/schemaFilterPattern\\/excludes')
.press('Enter');
}
}
async runAdditionalTests(
@ -187,7 +195,7 @@ class MysqlIngestionClass extends ServiceBaseClass {
await page.waitForSelector('.ant-select-selection-item-content');
await expect(page.locator('.ant-select-selection-item-content')).toHaveText(
this.defaultFilters.concat(this.tableFilter)
this.defaultFilters.concat([...this.excludeSchemas, ...this.tableFilter])
);
}
}

View File

@ -1106,11 +1106,10 @@ export const approveTagsTask = async (
await selectActiveGlossary(page, entity.data.displayName);
await page.waitForLoadState('networkidle');
const tagVisibility = await page.isVisible(
`[data-testid="tag-${value.tag}"]`
);
const tagVisibility = page.locator(`[data-testid="tag-${value.tag}"]`);
await tagVisibility.scrollIntoViewIfNeeded();
expect(tagVisibility).toBe(true);
await expect(tagVisibility).toBeVisible();
};
export async function openColumnDropdown(page: Page): Promise<void> {

View File

@ -39,7 +39,7 @@ export const createGlossaryTermRowDetails = () => {
};
export const fillTextInputDetails = async (page: Page, text: string) => {
await page.locator('.InovuaReactDataGrid__cell--cell-active').press('Enter');
await page.keyboard.press('Enter');
await page.locator('.ant-layout-content').getByRole('textbox').fill(text);
await page
@ -69,6 +69,8 @@ export const fillOwnerDetails = async (page: Page, owners: string[]) => {
.locator('.InovuaReactDataGrid__cell--cell-active')
.press('Enter', { delay: 100 });
await expect(page.getByTestId('select-owner-tabs')).toBeVisible();
await page.waitForLoadState('networkidle');
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });

View File

@ -13,6 +13,7 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { NodeViewProps } from '@tiptap/react';
import React from 'react';
import { UPLOADED_ASSETS_URL } from '../../../../../constants/BlockEditor.constants';
import ImageAttachment from './ImageAttachment';
describe('ImageAttachment', () => {
@ -28,24 +29,7 @@ describe('ImageAttachment', () => {
jest.clearAllMocks();
});
it('should render loading state when isMediaLoading is true and needs authentication', () => {
const authenticatedNode = {
...mockNode,
attrs: {
...mockNode.attrs,
url: '/api/v1/attachments/123',
},
} as unknown as NodeViewProps['node'];
render(
<ImageAttachment isMediaLoading mediaSrc="" node={authenticatedNode} />
);
expect(screen.getByTestId('loader')).toBeInTheDocument();
expect(screen.getByText('label.loading')).toBeInTheDocument();
});
it('should render uploading state when isUploading is true', () => {
it('should render loading state when isUploading is true', () => {
const uploadingNode = {
...mockNode,
attrs: {
@ -62,8 +46,29 @@ describe('ImageAttachment', () => {
/>
);
expect(screen.getByTestId('loader')).toBeInTheDocument();
expect(screen.getByText('label.uploading')).toBeInTheDocument();
const imageContainer = screen.getByTestId('image-container');
expect(imageContainer).toHaveClass('loading-state');
expect(screen.queryByTestId('uploaded-image-node')).not.toBeInTheDocument();
});
it('should render loading state when media is loading and needs authentication', () => {
const authenticatedNode = {
...mockNode,
attrs: {
...mockNode.attrs,
url: `${UPLOADED_ASSETS_URL}/123`,
},
} as unknown as NodeViewProps['node'];
render(
<ImageAttachment isMediaLoading mediaSrc="" node={authenticatedNode} />
);
const imageContainer = screen.getByTestId('image-container');
expect(imageContainer).toHaveClass('loading-state');
expect(screen.queryByTestId('uploaded-image-node')).not.toBeInTheDocument();
});
it('should render image when mediaSrc is provided', async () => {
@ -85,20 +90,13 @@ describe('ImageAttachment', () => {
it('should show error state when image fails to load', async () => {
render(
<ImageAttachment
isMediaLoading={false}
mediaSrc="invalid-url"
node={mockNode}
/>
<ImageAttachment isMediaLoading={false} mediaSrc="" node={mockNode} />
);
const image = screen.getByTestId('uploaded-image-node');
fireEvent.error(image);
await waitFor(() => {
expect(screen.getByTestId('uploaded-image-node')).toHaveStyle({
visibility: 'hidden',
});
expect(
screen.queryByTestId('uploaded-image-node')
).not.toBeInTheDocument();
});
});
@ -107,23 +105,82 @@ describe('ImageAttachment', () => {
...mockNode,
attrs: {
...mockNode.attrs,
url: '/api/v1/attachments/123',
url: `${UPLOADED_ASSETS_URL}/123`,
},
} as unknown as NodeViewProps['node'];
render(
<ImageAttachment isMediaLoading mediaSrc="" node={authenticatedNode} />
);
const imageContainer = screen.getByTestId('image-container');
expect(imageContainer).toHaveClass('loading-state');
expect(screen.queryByTestId('uploaded-image-node')).not.toBeInTheDocument();
});
it('should display authenticated image when mediaSrc is provided', async () => {
const authenticatedNode = {
...mockNode,
attrs: {
...mockNode.attrs,
url: `${UPLOADED_ASSETS_URL}/123`,
},
} as unknown as NodeViewProps['node'];
const mediaSrc = 'https://example.com/authenticated-image.jpg';
render(
<ImageAttachment
isMediaLoading={false}
mediaSrc=""
mediaSrc={mediaSrc}
node={authenticatedNode}
/>
);
expect(screen.getByTestId('loader')).toBeInTheDocument();
expect(screen.getByText('label.loading')).toBeInTheDocument();
const image = screen.getByTestId('uploaded-image-node');
expect(image).toBeInTheDocument();
expect(image).toHaveAttribute('src', mediaSrc);
});
it('should reset states when url or mediaSrc changes', async () => {
it('should reset states when url changes', async () => {
const { rerender } = render(
<ImageAttachment
isMediaLoading={false}
mediaSrc="https://example.com/image1.jpg"
node={mockNode}
/>
);
// Simulate image load
const image = screen.getByTestId('uploaded-image-node');
fireEvent.load(image);
// Rerender with new url
const newNode = {
...mockNode,
attrs: {
...mockNode.attrs,
url: 'https://example.com/new-image.jpg',
},
} as unknown as NodeViewProps['node'];
rerender(
<ImageAttachment
isMediaLoading={false}
mediaSrc="https://example.com/image1.jpg"
node={newNode}
/>
);
// Image should be hidden again until it loads
expect(screen.getByTestId('uploaded-image-node')).toHaveAttribute(
'src',
'https://example.com/image1.jpg'
);
});
it('should reset states when mediaSrc changes', async () => {
const { rerender } = render(
<ImageAttachment
isMediaLoading={false}
@ -146,6 +203,31 @@ describe('ImageAttachment', () => {
);
// Image should be hidden again until it loads
expect(image).toHaveStyle({ visibility: 'hidden' });
expect(screen.getByTestId('uploaded-image-node')).toHaveAttribute(
'src',
'https://example.com/image2.jpg'
);
});
it('should handle empty alt text', () => {
const nodeWithEmptyAlt = {
...mockNode,
attrs: {
...mockNode.attrs,
alt: '',
},
} as unknown as NodeViewProps['node'];
render(
<ImageAttachment
isMediaLoading={false}
mediaSrc="https://example.com/image.jpg"
node={nodeWithEmptyAlt}
/>
);
const image = screen.getByTestId('uploaded-image-node');
expect(image).toHaveAttribute('alt', '');
});
});

View File

@ -11,13 +11,10 @@
* limitations under the License.
*/
import { NodeViewProps } from '@tiptap/react';
import { Spin } from 'antd';
import classNames from 'classnames';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ReactComponent as IconFormatImage } from '../../../../../assets/svg/ic-format-image.svg';
import { UPLOADED_ASSETS_URL } from '../../../../../constants/BlockEditor.constants';
import Loader from '../../../../common/Loader/Loader';
const ImageAttachment = ({
node,
@ -32,7 +29,6 @@ const ImageAttachment = ({
const [imageError, setImageError] = useState<boolean>(false);
const [imageLoaded, setImageLoaded] = useState<boolean>(false);
const needsAuthentication = url?.includes(UPLOADED_ASSETS_URL);
const { t } = useTranslation();
// Reset states when url changes
useEffect(() => {
@ -56,34 +52,25 @@ const ImageAttachment = ({
return (
<div className="image-wrapper">
<Spin
indicator={<Loader size="small" />}
spinning={showLoadingOverlay}
tip={isUploading ? t('label.uploading') : t('label.loading')}>
<div
className={classNames('image-container', {
'loading-state': showLoadingOverlay || imageError,
})}>
{(showLoadingOverlay || imageError) && (
<div className="loading-overlay">
<IconFormatImage width={40} />
</div>
)}
{displaySrc && (
<img
alt={alt ?? ''}
data-testid="uploaded-image-node"
src={displaySrc}
style={{
visibility: imageLoaded ? 'visible' : 'hidden',
display: 'block',
}}
onError={handleImageError}
onLoad={handleImageLoad}
/>
)}
</div>
</Spin>
<div
className={classNames('image-container', {
'loading-state': showLoadingOverlay || imageError,
})}
data-testid="image-container">
{displaySrc ? (
<img
alt={alt ?? ''}
data-testid="uploaded-image-node"
src={displaySrc}
onError={handleImageError}
onLoad={handleImageLoad}
/>
) : (
<div className="loading-overlay">
<IconFormatImage width={40} />
</div>
)}
</div>
</div>
);
};