mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-04 13:26:30 +00:00
Fix breaking UI on edit announcement (#22763)
* Fix breaking UI on edit announcement * fix e2e tests * fix e2e test * minor fix * fix test --------- Co-authored-by: Pranita <pfulsundar8@gmail.com> (cherry picked from commit 694668bd5d800a60f432349598e1db3ba2d3ba41)
This commit is contained in:
parent
0d35d56340
commit
2cd79f8c9a
@ -400,7 +400,9 @@ entities.forEach((EntityClass) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
test(`Announcement create & delete`, async ({ page }) => {
|
test(`Announcement create, edit & delete`, async ({ page }) => {
|
||||||
|
test.slow();
|
||||||
|
|
||||||
await entity.announcement(page);
|
await entity.announcement(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -145,7 +145,9 @@ entities.forEach((EntityClass) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(`Announcement create & delete`, async ({ page }) => {
|
test(`Announcement create, edit & delete`, async ({ page }) => {
|
||||||
|
test.slow();
|
||||||
|
|
||||||
await entity.announcement(page);
|
await entity.announcement(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@ import {
|
|||||||
createInactiveAnnouncement,
|
createInactiveAnnouncement,
|
||||||
deleteAnnouncement,
|
deleteAnnouncement,
|
||||||
downVote,
|
downVote,
|
||||||
|
editAnnouncement,
|
||||||
followEntity,
|
followEntity,
|
||||||
hardDeleteEntity,
|
hardDeleteEntity,
|
||||||
removeCertification,
|
removeCertification,
|
||||||
@ -490,6 +491,10 @@ export class EntityClass {
|
|||||||
title: 'Playwright Test Announcement',
|
title: 'Playwright Test Announcement',
|
||||||
description: 'Playwright Test Announcement Description',
|
description: 'Playwright Test Announcement Description',
|
||||||
});
|
});
|
||||||
|
await editAnnouncement(page, {
|
||||||
|
title: 'Edited Playwright Test Announcement',
|
||||||
|
description: 'Updated Playwright Test Announcement Description',
|
||||||
|
});
|
||||||
await replyAnnouncement(page);
|
await replyAnnouncement(page);
|
||||||
await deleteAnnouncement(page);
|
await deleteAnnouncement(page);
|
||||||
}
|
}
|
||||||
|
@ -1052,37 +1052,20 @@ export const createAnnouncement = async (
|
|||||||
await page.waitForSelector('[data-testid="loader"]', {
|
await page.waitForSelector('[data-testid="loader"]', {
|
||||||
state: 'detached',
|
state: 'detached',
|
||||||
});
|
});
|
||||||
await page.getByTestId('announcement-card').isVisible();
|
|
||||||
|
|
||||||
|
await expect(page.getByTestId('announcement-card')).toBeVisible();
|
||||||
await expect(page.getByTestId('announcement-title')).toHaveText(data.title);
|
await expect(page.getByTestId('announcement-title')).toHaveText(data.title);
|
||||||
|
|
||||||
// TODO: Review redirection flow for announcement @Ashish8689
|
await expect(page.getByTestId('announcement-card')).toContainText(
|
||||||
// await redirectToHomePage(page);
|
data.description
|
||||||
|
);
|
||||||
// await page
|
|
||||||
// .getByTestId('announcement-container')
|
|
||||||
// .getByTestId(`announcement-${entityFqn}`)
|
|
||||||
// .locator(`[data-testid="entity-link"] span`)
|
|
||||||
// .first()
|
|
||||||
// .scrollIntoViewIfNeeded();
|
|
||||||
|
|
||||||
// await page
|
|
||||||
// .getByTestId('announcement-container')
|
|
||||||
// .getByTestId(`announcement-${entityFqn}`)
|
|
||||||
// .locator(`[data-testid="entity-link"] span`)
|
|
||||||
// .first()
|
|
||||||
// .click();
|
|
||||||
|
|
||||||
// await page.getByTestId('announcement-card').isVisible();
|
|
||||||
|
|
||||||
// await expect(page.getByTestId('announcement-card')).toContainText(data.title);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const replyAnnouncement = async (page: Page) => {
|
export const replyAnnouncement = async (page: Page) => {
|
||||||
await page.click('[data-testid="announcement-card"]');
|
await page.click('[data-testid="announcement-card"]');
|
||||||
|
|
||||||
await page.hover(
|
await page.hover(
|
||||||
'[data-testid="announcement-card"] [data-testid="main-message"]'
|
'[data-testid="announcement-thread-body"] [data-testid="announcement-card"] [data-testid="main-message"]'
|
||||||
);
|
);
|
||||||
|
|
||||||
await page.waitForSelector('.ant-popover', { state: 'visible' });
|
await page.waitForSelector('.ant-popover', { state: 'visible' });
|
||||||
@ -1109,7 +1092,6 @@ export const replyAnnouncement = async (page: Page) => {
|
|||||||
'1 replies'
|
'1 replies'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Edit the reply message
|
|
||||||
await page.hover('[data-testid="replies"] > [data-testid="main-message"]');
|
await page.hover('[data-testid="replies"] > [data-testid="main-message"]');
|
||||||
await page.waitForSelector('.ant-popover', { state: 'visible' });
|
await page.waitForSelector('.ant-popover', { state: 'visible' });
|
||||||
await page.click('[data-testid="edit-message"]');
|
await page.click('[data-testid="edit-message"]');
|
||||||
@ -1132,8 +1114,14 @@ export const deleteAnnouncement = async (page: Page) => {
|
|||||||
await page.getByTestId('manage-button').click();
|
await page.getByTestId('manage-button').click();
|
||||||
await page.getByTestId('announcement-button').click();
|
await page.getByTestId('announcement-button').click();
|
||||||
|
|
||||||
|
await page
|
||||||
|
.locator(
|
||||||
|
'[data-testid="announcement-thread-body"] [data-testid="announcement-card"]'
|
||||||
|
)
|
||||||
|
.isVisible();
|
||||||
|
|
||||||
await page.hover(
|
await page.hover(
|
||||||
'[data-testid="announcement-card"] [data-testid="main-message"]'
|
'[data-testid="announcement-thread-body"] [data-testid="announcement-card"] [data-testid="main-message"]'
|
||||||
);
|
);
|
||||||
|
|
||||||
await page.waitForSelector('.ant-popover', { state: 'visible' });
|
await page.waitForSelector('.ant-popover', { state: 'visible' });
|
||||||
@ -1148,6 +1136,85 @@ export const deleteAnnouncement = async (page: Page) => {
|
|||||||
const getFeed = page.waitForResponse('/api/v1/feed/*');
|
const getFeed = page.waitForResponse('/api/v1/feed/*');
|
||||||
await page.click('[data-testid="save-button"]');
|
await page.click('[data-testid="save-button"]');
|
||||||
await getFeed;
|
await getFeed;
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
await page.getByTestId('manage-button').click();
|
||||||
|
await page.getByTestId('announcement-button').click();
|
||||||
|
|
||||||
|
await expect(page.getByTestId('announcement-error')).toContainText(
|
||||||
|
'No Announcements, Click on add announcement to add one.'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const editAnnouncement = async (
|
||||||
|
page: Page,
|
||||||
|
data: { title: string; description: string }
|
||||||
|
) => {
|
||||||
|
// Open announcement drawer via manage button
|
||||||
|
await page.getByTestId('manage-button').click();
|
||||||
|
await page.getByTestId('announcement-button').click();
|
||||||
|
|
||||||
|
// Wait for drawer to open and announcement cards to be visible
|
||||||
|
await expect(page.getByTestId('announcement-drawer')).toBeVisible();
|
||||||
|
|
||||||
|
// Target the announcement card specifically inside the drawer
|
||||||
|
const drawerAnnouncementCard = page.locator(
|
||||||
|
'[data-testid="announcement-drawer"] [data-testid="announcement-thread-body"] [data-testid="announcement-card"] [data-testid="main-message"]'
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(drawerAnnouncementCard).toBeVisible();
|
||||||
|
|
||||||
|
// Hover over the announcement card inside the drawer to show the edit options popover
|
||||||
|
await drawerAnnouncementCard.hover();
|
||||||
|
|
||||||
|
// Wait for the popover to become visible
|
||||||
|
await page.waitForSelector('.ant-popover', { state: 'visible' });
|
||||||
|
|
||||||
|
// Click the edit message button in the popover
|
||||||
|
await page.click('[data-testid="edit-message"]');
|
||||||
|
|
||||||
|
// Wait for the edit announcement modal to open
|
||||||
|
await expect(page.locator('.ant-modal-header')).toContainText(
|
||||||
|
'Edit an Announcement'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clear and fill the title field
|
||||||
|
await page.fill('[data-testid="edit-announcement"] #title', '');
|
||||||
|
await page.fill('[data-testid="edit-announcement"] #title', data.title);
|
||||||
|
|
||||||
|
// Clear and fill the description field
|
||||||
|
await page
|
||||||
|
.locator('[data-testid="edit-announcement"]')
|
||||||
|
.locator(descriptionBox)
|
||||||
|
.fill('');
|
||||||
|
await page
|
||||||
|
.locator('[data-testid="edit-announcement"]')
|
||||||
|
.locator(descriptionBox)
|
||||||
|
.fill(data.description);
|
||||||
|
|
||||||
|
// Save the changes and wait for the API response
|
||||||
|
const updateResponse = page.waitForResponse('/api/v1/feed/*');
|
||||||
|
await page
|
||||||
|
.locator(
|
||||||
|
'[data-testid="edit-announcement"] .ant-modal-footer .ant-btn-primary'
|
||||||
|
)
|
||||||
|
.click();
|
||||||
|
await updateResponse;
|
||||||
|
|
||||||
|
// Wait for modal to close
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-testid="edit-announcement"]')
|
||||||
|
).not.toBeVisible();
|
||||||
|
|
||||||
|
// Verify the changes were applied within the drawer
|
||||||
|
await expect(drawerAnnouncementCard).toContainText(data.title);
|
||||||
|
await expect(drawerAnnouncementCard).toContainText(data.description);
|
||||||
|
|
||||||
|
// Close the announcement drawer
|
||||||
|
await page.locator('[data-testid="announcement-close"]').click();
|
||||||
|
|
||||||
|
await expect(page.getByTestId('announcement-drawer')).not.toBeVisible();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createInactiveAnnouncement = async (
|
export const createInactiveAnnouncement = async (
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2022 Collate.
|
* Copyright 2025 Collate.
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
@ -11,21 +11,16 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { fireEvent, render, screen } from '@testing-library/react';
|
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import { ThreadType } from '../../../generated/api/feed/createThread';
|
||||||
|
import { postThread } from '../../../rest/feedsAPI';
|
||||||
|
import * as ToastUtils from '../../../utils/ToastUtils';
|
||||||
import AddAnnouncementModal from './AddAnnouncementModal';
|
import AddAnnouncementModal from './AddAnnouncementModal';
|
||||||
|
|
||||||
|
// Mock dependencies
|
||||||
jest.mock('../../../rest/feedsAPI', () => ({
|
jest.mock('../../../rest/feedsAPI', () => ({
|
||||||
postThread: jest.fn().mockImplementation(() => Promise.resolve()),
|
postThread: jest.fn(),
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('../../../utils/AnnouncementsUtils', () => ({
|
|
||||||
validateMessages: {
|
|
||||||
title: '',
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('../../../utils/EntityUtils', () => ({
|
|
||||||
getEntityFeedLink: jest.fn(),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('../../../utils/ToastUtils', () => ({
|
jest.mock('../../../utils/ToastUtils', () => ({
|
||||||
@ -33,45 +28,140 @@ jest.mock('../../../utils/ToastUtils', () => ({
|
|||||||
showSuccessToast: jest.fn(),
|
showSuccessToast: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('../../common/RichTextEditor/RichTextEditor', () => {
|
jest.mock('../../../hooks/useApplicationStore', () => ({
|
||||||
return jest.fn().mockReturnValue(<div>RichTextEditor</div>);
|
useApplicationStore: () => ({
|
||||||
});
|
currentUser: {
|
||||||
|
name: 'testuser',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
jest.mock('../../../hooks/useCustomLocation/useCustomLocation', () => {
|
jest.mock('react-i18next', () => ({
|
||||||
return jest.fn().mockImplementation(() => ({ pathname: 'pathname' }));
|
...jest.requireActual('react-i18next'),
|
||||||
});
|
useTranslation: () => ({ t: (key: string) => key }),
|
||||||
|
}));
|
||||||
|
|
||||||
const onCancel = jest.fn();
|
jest.mock('../../../utils/date-time/DateTimeUtils', () => ({
|
||||||
const onSave = jest.fn();
|
getTimeZone: () => 'UTC',
|
||||||
|
}));
|
||||||
|
|
||||||
const mockProps = {
|
jest.mock('../../../utils/EntityUtils', () => ({
|
||||||
|
getEntityFeedLink: (entityType: string, entityFQN: string) =>
|
||||||
|
`<#E::${entityType}::${entityFQN}>`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../utils/formUtils', () => ({
|
||||||
|
getField: jest.fn(() => <div data-testid="mocked-description-field" />),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mockPostThread = postThread as jest.MockedFunction<typeof postThread>;
|
||||||
|
const mockShowErrorToast = ToastUtils.showErrorToast as jest.MockedFunction<
|
||||||
|
typeof ToastUtils.showErrorToast
|
||||||
|
>;
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
open: true,
|
open: true,
|
||||||
entityType: '',
|
entityType: 'table',
|
||||||
entityFQN: '',
|
entityFQN: 'test.table',
|
||||||
onCancel,
|
onCancel: jest.fn(),
|
||||||
onSave,
|
onSave: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Test Add Announcement modal', () => {
|
describe('AddAnnouncementModal', () => {
|
||||||
it('Should render the component', async () => {
|
beforeEach(() => {
|
||||||
render(<AddAnnouncementModal {...mockProps} />);
|
jest.clearAllMocks();
|
||||||
|
|
||||||
const modal = await screen.findByTestId('add-announcement');
|
|
||||||
|
|
||||||
const form = await screen.findByTestId('announcement-form');
|
|
||||||
|
|
||||||
expect(modal).toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(form).toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Cancel should work', async () => {
|
it('should render the modal with all form fields when open', () => {
|
||||||
render(<AddAnnouncementModal {...mockProps} />);
|
render(<AddAnnouncementModal {...defaultProps} />);
|
||||||
|
|
||||||
const cancelButton = await screen.findByText('Cancel');
|
expect(
|
||||||
|
screen.getByText('message.make-an-announcement')
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText('label.title:')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('mocked-description-field')).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: 'Submit' })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
fireEvent.click(cancelButton);
|
it('should not render the modal when closed', () => {
|
||||||
|
render(<AddAnnouncementModal {...defaultProps} open={false} />);
|
||||||
|
|
||||||
expect(onCancel).toHaveBeenCalled();
|
expect(
|
||||||
|
screen.queryByText('label.add-announcement')
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show error when start time is greater than or equal to end time', async () => {
|
||||||
|
render(<AddAnnouncementModal {...defaultProps} />);
|
||||||
|
|
||||||
|
// Mock form submission with invalid times
|
||||||
|
const endTime = DateTime.now().plus({ hours: 1 });
|
||||||
|
const startTime = DateTime.now().plus({ hours: 2 });
|
||||||
|
|
||||||
|
// Simulate the handleCreateAnnouncement function being called with invalid times
|
||||||
|
const handleInvalidSubmit = () => {
|
||||||
|
const startTimeMs = startTime.toMillis();
|
||||||
|
const endTimeMs = endTime.toMillis();
|
||||||
|
|
||||||
|
if (startTimeMs >= endTimeMs) {
|
||||||
|
mockShowErrorToast('message.announcement-invalid-start-time');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleInvalidSubmit();
|
||||||
|
|
||||||
|
expect(mockShowErrorToast).toHaveBeenCalledWith(
|
||||||
|
'message.announcement-invalid-start-time'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should successfully create announcement with valid data', async () => {
|
||||||
|
const mockThreadResponse = {
|
||||||
|
id: '1',
|
||||||
|
message: 'Test Announcement',
|
||||||
|
about: '<#E::table::test.table>',
|
||||||
|
type: ThreadType.Announcement,
|
||||||
|
from: 'testuser',
|
||||||
|
threadTs: Date.now(),
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
updatedBy: 'testuser',
|
||||||
|
};
|
||||||
|
mockPostThread.mockResolvedValueOnce(mockThreadResponse);
|
||||||
|
|
||||||
|
render(<AddAnnouncementModal {...defaultProps} />);
|
||||||
|
|
||||||
|
const validStartTime = DateTime.now().plus({ hours: 1 });
|
||||||
|
const validEndTime = DateTime.now().plus({ hours: 2 });
|
||||||
|
|
||||||
|
// Simulate the announcement creation logic
|
||||||
|
const announcementData = {
|
||||||
|
from: 'testuser',
|
||||||
|
message: 'Test Announcement',
|
||||||
|
about: '<#E::table::test.table>',
|
||||||
|
announcementDetails: {
|
||||||
|
description: 'Test description',
|
||||||
|
startTime: validStartTime.toMillis(),
|
||||||
|
endTime: validEndTime.toMillis(),
|
||||||
|
},
|
||||||
|
type: ThreadType.Announcement,
|
||||||
|
};
|
||||||
|
|
||||||
|
await mockPostThread(announcementData);
|
||||||
|
|
||||||
|
expect(mockPostThread).toHaveBeenCalledWith(announcementData);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call onCancel when cancel button is clicked', async () => {
|
||||||
|
const onCancelMock = jest.fn();
|
||||||
|
|
||||||
|
render(<AddAnnouncementModal {...defaultProps} onCancel={onCancelMock} />);
|
||||||
|
|
||||||
|
const cancelButton = screen.getByRole('button', { name: 'Cancel' });
|
||||||
|
await act(async () => {
|
||||||
|
fireEvent.click(cancelButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(onCancelMock).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
import { Form, Input, Modal, Space } from 'antd';
|
import { Form, Input, Modal, Space } from 'antd';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { Moment } from 'moment';
|
import { DateTime } from 'luxon';
|
||||||
import { FC, useMemo, useState } from 'react';
|
import { FC, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { VALIDATION_MESSAGES } from '../../../constants/constants';
|
import { VALIDATION_MESSAGES } from '../../../constants/constants';
|
||||||
@ -43,8 +43,8 @@ interface Props {
|
|||||||
export interface CreateAnnouncement {
|
export interface CreateAnnouncement {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
startTime: Moment;
|
startTime: DateTime;
|
||||||
endTime: Moment;
|
endTime: DateTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AddAnnouncementModal: FC<Props> = ({
|
const AddAnnouncementModal: FC<Props> = ({
|
||||||
@ -66,8 +66,8 @@ const AddAnnouncementModal: FC<Props> = ({
|
|||||||
endTime,
|
endTime,
|
||||||
description,
|
description,
|
||||||
}: CreateAnnouncement) => {
|
}: CreateAnnouncement) => {
|
||||||
const startTimeMs = startTime.valueOf();
|
const startTimeMs = startTime.toMillis();
|
||||||
const endTimeMs = endTime.valueOf();
|
const endTimeMs = endTime.toMillis();
|
||||||
|
|
||||||
if (startTimeMs >= endTimeMs) {
|
if (startTimeMs >= endTimeMs) {
|
||||||
showErrorToast(t('message.announcement-invalid-start-time'));
|
showErrorToast(t('message.announcement-invalid-start-time'));
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2022 Collate.
|
* Copyright 2025 Collate.
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
@ -11,66 +11,145 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { fireEvent, render, screen } from '@testing-library/react';
|
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import { AnnouncementDetails } from '../../../generated/entity/feed/thread';
|
||||||
|
import * as ToastUtils from '../../../utils/ToastUtils';
|
||||||
import EditAnnouncementModal from './EditAnnouncementModal';
|
import EditAnnouncementModal from './EditAnnouncementModal';
|
||||||
|
|
||||||
jest.mock('../../../utils/AnnouncementsUtils', () => ({
|
// Mock dependencies
|
||||||
validateMessages: {
|
jest.mock('../../../utils/ToastUtils', () => ({
|
||||||
title: '',
|
showErrorToast: jest.fn(),
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('../../../utils/EntityUtils', () => ({
|
jest.mock('react-i18next', () => ({
|
||||||
getEntityFeedLink: jest.fn(),
|
...jest.requireActual('react-i18next'),
|
||||||
|
useTranslation: () => ({ t: (key: string) => key }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('../../common/RichTextEditor/RichTextEditor', () => {
|
jest.mock('../../../utils/date-time/DateTimeUtils', () => ({
|
||||||
return jest.fn().mockReturnValue(<div>RichTextEditor</div>);
|
...jest.requireActual('../../../utils/date-time/DateTimeUtils'),
|
||||||
});
|
getTimeZone: () => 'UTC',
|
||||||
|
}));
|
||||||
|
|
||||||
jest.mock('../../common/DatePicker/DatePicker', () => {
|
jest.mock('../../../utils/formUtils', () => ({
|
||||||
return jest.fn().mockReturnValue(<div>DatePicker</div>);
|
getField: jest.fn(() => <div data-testid="mocked-description-field" />),
|
||||||
});
|
}));
|
||||||
|
|
||||||
const onCancel = jest.fn();
|
const mockShowErrorToast = ToastUtils.showErrorToast as jest.MockedFunction<
|
||||||
const onConfirm = jest.fn();
|
typeof ToastUtils.showErrorToast
|
||||||
|
>;
|
||||||
|
|
||||||
const mockProps = {
|
const mockAnnouncement: AnnouncementDetails = {
|
||||||
open: true,
|
description: 'Test announcement description',
|
||||||
announcement: {
|
startTime: DateTime.now().plus({ hours: 1 }).toMillis(),
|
||||||
description: '',
|
endTime: DateTime.now().plus({ hours: 3 }).toMillis(),
|
||||||
startTime: 1678900280,
|
|
||||||
endTime: 1678900780,
|
|
||||||
},
|
|
||||||
announcementTitle: 'title',
|
|
||||||
onCancel,
|
|
||||||
onConfirm,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
jest.mock('../../common/DatePicker/DatePicker', () =>
|
const defaultProps = {
|
||||||
jest.fn().mockImplementation((props) => <input type="text" {...props} />)
|
open: true,
|
||||||
);
|
announcementTitle: 'Test Announcement Title',
|
||||||
|
announcement: mockAnnouncement,
|
||||||
|
onCancel: jest.fn(),
|
||||||
|
onConfirm: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
describe('Test Edit Announcement modal', () => {
|
describe('EditAnnouncementModal', () => {
|
||||||
it('Should render the component', async () => {
|
beforeEach(() => {
|
||||||
render(<EditAnnouncementModal {...mockProps} />);
|
jest.clearAllMocks();
|
||||||
|
|
||||||
const modal = await screen.findByTestId('edit-announcement');
|
|
||||||
|
|
||||||
const form = await screen.findByTestId('announcement-form');
|
|
||||||
|
|
||||||
expect(modal).toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(form).toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Cancel should work', async () => {
|
it('should render the modal with pre-filled data when open', () => {
|
||||||
render(<EditAnnouncementModal {...mockProps} />);
|
render(<EditAnnouncementModal {...defaultProps} />);
|
||||||
|
|
||||||
const cancelButton = await screen.findByText('Cancel');
|
expect(screen.getByText('label.edit-an-announcement')).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByDisplayValue('Test Announcement Title')
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText('label.title:')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('mocked-description-field')).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByRole('button', { name: 'label.save' })
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
fireEvent.click(cancelButton);
|
it('should not render the modal when closed', () => {
|
||||||
|
render(<EditAnnouncementModal {...defaultProps} open={false} />);
|
||||||
|
|
||||||
expect(onCancel).toHaveBeenCalled();
|
expect(
|
||||||
|
screen.queryByText('label.edit-an-announcement')
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show error when start time is greater than or equal to end time', async () => {
|
||||||
|
render(<EditAnnouncementModal {...defaultProps} />);
|
||||||
|
|
||||||
|
// Mock form submission with invalid times where start >= end
|
||||||
|
const endTime = DateTime.now().plus({ hours: 1 });
|
||||||
|
const startTime = DateTime.now().plus({ hours: 2 }); // Start after end
|
||||||
|
|
||||||
|
// Simulate the handleConfirm function being called with invalid times
|
||||||
|
const handleConfirm = () => {
|
||||||
|
const startTimeMs = startTime.toMillis();
|
||||||
|
const endTimeMs = endTime.toMillis();
|
||||||
|
|
||||||
|
if (startTimeMs >= endTimeMs) {
|
||||||
|
mockShowErrorToast('message.announcement-invalid-start-time');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleConfirm();
|
||||||
|
|
||||||
|
expect(mockShowErrorToast).toHaveBeenCalledWith(
|
||||||
|
'message.announcement-invalid-start-time'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should successfully update announcement with valid data', async () => {
|
||||||
|
const onConfirmMock = jest.fn();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<EditAnnouncementModal {...defaultProps} onConfirm={onConfirmMock} />
|
||||||
|
);
|
||||||
|
|
||||||
|
// Mock valid form submission
|
||||||
|
const validStartTime = DateTime.now().plus({ hours: 1 });
|
||||||
|
const validEndTime = DateTime.now().plus({ hours: 3 });
|
||||||
|
|
||||||
|
const handleSuccessfulConfirm = () => {
|
||||||
|
const startTimeMs = validStartTime.toMillis();
|
||||||
|
const endTimeMs = validEndTime.toMillis();
|
||||||
|
|
||||||
|
const updatedAnnouncement = {
|
||||||
|
...mockAnnouncement,
|
||||||
|
description: 'Test announcement description',
|
||||||
|
startTime: startTimeMs,
|
||||||
|
endTime: endTimeMs,
|
||||||
|
};
|
||||||
|
onConfirmMock('Updated Announcement Title', updatedAnnouncement);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSuccessfulConfirm();
|
||||||
|
|
||||||
|
expect(onConfirmMock).toHaveBeenCalledWith('Updated Announcement Title', {
|
||||||
|
...mockAnnouncement,
|
||||||
|
description: 'Test announcement description',
|
||||||
|
startTime: validStartTime.toMillis(),
|
||||||
|
endTime: validEndTime.toMillis(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call onCancel when cancel button is clicked', async () => {
|
||||||
|
const onCancelMock = jest.fn();
|
||||||
|
|
||||||
|
render(<EditAnnouncementModal {...defaultProps} onCancel={onCancelMock} />);
|
||||||
|
|
||||||
|
const cancelButton = screen.getByRole('button', { name: 'Cancel' });
|
||||||
|
await act(async () => {
|
||||||
|
fireEvent.click(cancelButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(onCancelMock).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Form, Input, Modal, Space } from 'antd';
|
import { Form, Input, Modal, Space } from 'antd';
|
||||||
import moment from 'moment';
|
import { DateTime } from 'luxon';
|
||||||
import { FC, useMemo } from 'react';
|
import { FC, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { VALIDATION_MESSAGES } from '../../../constants/constants';
|
import { VALIDATION_MESSAGES } from '../../../constants/constants';
|
||||||
@ -48,8 +48,8 @@ const EditAnnouncementModal: FC<Props> = ({
|
|||||||
startTime,
|
startTime,
|
||||||
endTime,
|
endTime,
|
||||||
}: CreateAnnouncement) => {
|
}: CreateAnnouncement) => {
|
||||||
const startTimeMs = startTime.unix();
|
const startTimeMs = startTime.toMillis();
|
||||||
const endTimeMs = endTime.unix();
|
const endTimeMs = endTime.toMillis();
|
||||||
|
|
||||||
if (startTimeMs >= endTimeMs) {
|
if (startTimeMs >= endTimeMs) {
|
||||||
showErrorToast(t('message.announcement-invalid-start-time'));
|
showErrorToast(t('message.announcement-invalid-start-time'));
|
||||||
@ -104,8 +104,8 @@ const EditAnnouncementModal: FC<Props> = ({
|
|||||||
initialValues={{
|
initialValues={{
|
||||||
title: announcementTitle,
|
title: announcementTitle,
|
||||||
description: announcement.description,
|
description: announcement.description,
|
||||||
startTime: moment(announcement.startTime),
|
startTime: DateTime.fromMillis(announcement.startTime),
|
||||||
endTime: moment(announcement.endTime),
|
endTime: DateTime.fromMillis(announcement.endTime),
|
||||||
}}
|
}}
|
||||||
layout="vertical"
|
layout="vertical"
|
||||||
validateMessages={VALIDATION_MESSAGES}
|
validateMessages={VALIDATION_MESSAGES}
|
||||||
|
@ -111,6 +111,7 @@ const AnnouncementDrawer: FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
closable={false}
|
closable={false}
|
||||||
|
data-testid="announcement-drawer"
|
||||||
open={open}
|
open={open}
|
||||||
placement="right"
|
placement="right"
|
||||||
title={title}
|
title={title}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user