diff --git a/dashboard/src/api/apiUrlLinks/glossaryUrl.ts b/dashboard/src/api/apiUrlLinks/glossaryUrl.ts index f80028ae312..15883043d85 100644 --- a/dashboard/src/api/apiUrlLinks/glossaryUrl.ts +++ b/dashboard/src/api/apiUrlLinks/glossaryUrl.ts @@ -37,6 +37,9 @@ const glossaryImportUrl = () => { }; const glossaryTypeUrl = (glossaryType: string, guid: string) => { + if (glossaryType == "glossary") { + return `${glossaryUrl()}/${guid}`; + } return `${glossaryUrl()}/${glossaryType}/${guid}`; }; @@ -90,5 +93,5 @@ export { assignTermtoEntitiesUrl, assignTermtoCategoryUrl, assignGlossaryTypeUrl, - removeTermorCatgeoryUrl, + removeTermorCatgeoryUrl }; diff --git a/dashboard/src/components/Modal.tsx b/dashboard/src/components/Modal.tsx index 373e5fee264..e6f8c5d00f1 100644 --- a/dashboard/src/components/Modal.tsx +++ b/dashboard/src/components/Modal.tsx @@ -98,6 +98,9 @@ export const CustomModal: React.FC = ({ aria-labelledby="customized-dialog-title" aria-busy={isLoading} open={open} + disableEnforceFocus + disableAutoFocus + disableRestoreFocus sx={{ "& .MuiDialog-paper": { // maxWidth: "80% !important", diff --git a/dashboard/src/components/ShowMore/ShowMoreView.tsx b/dashboard/src/components/ShowMore/ShowMoreView.tsx index 48f368d0cb5..8098ec86ea9 100644 --- a/dashboard/src/components/ShowMore/ShowMoreView.tsx +++ b/dashboard/src/components/ShowMore/ShowMoreView.tsx @@ -120,19 +120,19 @@ const ShowMoreView = ({ } else if (title == "Terms" || title == "Category") { let selectedTerm = !isEmpty(data) ? data?.find( - (obj: { - qualifiedName: string; - displayText: string; - termGuid: string; - }) => { - if ( - (obj.qualifiedName || obj.displayText) == - currentValue.selectedValue - ) { - return obj; - } + (obj: { + qualifiedName: string; + displayText: string; + termGuid: string; + }) => { + if ( + (obj.qualifiedName || obj.displayText) == + currentValue.selectedValue + ) { + return obj; } - ) + } + ) : {}; if (isEmpty(gType)) { @@ -190,8 +190,8 @@ const ShowMoreView = ({ const checkSuperTypes = (classificationName: string) => { var tagObj = !isEmpty(classificationDefs) ? classificationDefs.find((obj: { name: string }) => { - return obj.name == classificationName; - }) + return obj.name == classificationName; + }) : {}; return !isEmpty(tagObj?.superTypes) @@ -203,10 +203,10 @@ const ShowMoreView = ({ const getTagParentList = (name: string) => { let tagObj = !isEmpty(classificationDefs) - ? classificationDefs?.find((obj: { name: string }) => { - return obj.name == name; - }) - : {}, + ? classificationDefs?.find((obj: { name: string }) => { + return obj.name == name; + }) + : {}, tagParents = tagObj ? tagObj?.["superTypes"] : null, parentName = name; if (tagParents && tagParents.length) { @@ -255,14 +255,16 @@ const ShowMoreView = ({ if (title == "Terms" || title == "Category") { const { termGuid, categoryGuid }: any = data || {}; - const searchParams = new URLSearchParams(location.search); + const searchParams = new URLSearchParams(); + let gTypeGuid = + (title == "Terms" ? termGuid : categoryGuid) || data?.guid; + + searchParams.set("gid", gTypeGuid); searchParams.set("gtype", title == "Terms" ? "term" : "category"); searchParams.set("viewType", title == "Terms" ? "term" : "category"); searchParams.set("fromView", "entity"); - - let gTypeGuid = - (title == "Terms" ? termGuid : categoryGuid) || data?.guid; + searchParams.set("searchType", "basic"); return ( { - // Handle undefined displayKey by extracting a string value - const deleteValue = obj[displayKey] || obj.displayText || obj.text || obj.name || ''; - handleDelete(deleteValue); - } + // Handle undefined displayKey by extracting a string value + const deleteValue = obj[displayKey] || obj.displayText || obj.text || obj.name || ''; + handleDelete(deleteValue); + } : isDeleteIcon && obj.count > 1 - ? () => { + ? () => { const searchParams = new URLSearchParams(); searchParams.set("tabActive", "classification"); searchParams.set("filter", obj.typeName); @@ -326,7 +328,7 @@ const ShowMoreView = ({ search: searchParams.toString() }); } - : undefined + : undefined } size="small" variant="outlined" @@ -407,11 +409,10 @@ const ShowMoreView = ({ sx={{ fontWeight: 600 }} > {!isEmpty(currentEntity) - ? `${currentValue.assetName} ${ - !isEmpty(currentEntity?.typeName) - ? `(${currentEntity.typeName})` - : "" - }` + ? `${currentValue.assetName} ${!isEmpty(currentEntity?.typeName) + ? `(${currentEntity.typeName})` + : "" + }` : ""} {" "} ? diff --git a/dashboard/src/components/TreeNodeIcons.tsx b/dashboard/src/components/TreeNodeIcons.tsx index 5403a2a9d63..f8769652647 100644 --- a/dashboard/src/components/TreeNodeIcons.tsx +++ b/dashboard/src/components/TreeNodeIcons.tsx @@ -83,10 +83,10 @@ const TreeNodeIcons = (props: { selectedSearchData = !isEmpty(savedSearchData) ? savedSearchData?.find((obj: { name: string; guid: string }) => { - if (obj.name == node.id) { - return obj; - } - }) + if (obj.name == node.id) { + return obj; + } + }) : {}; const handleCloseGlossaryModal = () => { @@ -202,17 +202,17 @@ const TreeNodeIcons = (props: { !isEmpty(node.children) && node.cGuid != undefined)) && !(treeName == "CustomFilters" && node.types == "parent") && ( - { - handleClickNodeMenu(e); - }} - className="tree-item-more-label" - size="small" - data-cy="dropdownMenuButton" - > - - - )} + { + handleClickNodeMenu(e); + }} + className="tree-item-more-label" + size="small" + data-cy="dropdownMenuButton" + > + + + )} { e.stopPropagation(); @@ -232,193 +232,200 @@ const TreeNodeIcons = (props: { {((treeName == "Classifications" && !addOnClassification.includes(node.id)) || (treeName == "Glossary" && node.types == "parent")) && ( - { - e.stopPropagation(); - if ( - treeName == "Classifications" && - !addOnClassification.includes(node.id) - ) { - setTagModal(true); - } - if ( - treeName == "Glossary" && - node.types == "parent" && - isEmptyServicetype - ) { - setTermModal(true); - } - if ( - treeName == "Glossary" && - node.types == "parent" && - !isEmptyServicetype - ) { - setCategoryModal(true); - } - handleCloseNode(); - }} - className="sidebar-menu-item" - data-cy="createClassification" - > - - - - { + e.stopPropagation(); + if ( + treeName == "Classifications" && + !addOnClassification.includes(node.id) + ) { + setTagModal(true); + } + if ( + treeName == "Glossary" && + node.types == "parent" && + isEmptyServicetype + ) { + setTermModal(true); + } + if ( + treeName == "Glossary" && + node.types == "parent" && + !isEmptyServicetype + ) { + setCategoryModal(true); + } + handleCloseNode(); + }} + className="sidebar-menu-item" + data-cy="createClassification" > - {`Create ${ - treeName == "Classifications" + + + + + {`Create ${treeName == "Classifications" ? "Sub-classification" : isEmptyServicetype - ? "Term" - : "Category" - }`} - - - )} + ? "Term" + : "Category" + }`} + + + )} {((treeName == "Classifications" && !addOnClassification.includes(node.id)) || - (treeName == "Glossary" && isEmptyServicetype)) && ( - { - if (treeName == "Classifications") { - const searchParams = new URLSearchParams(); - const classificationName = node.label - ? node.label.split(" (")[0] - : node.nodeName || node.id; - searchParams.set("tag", classificationName); - navigate({ - pathname: `/tag/tagAttribute/${classificationName}`, - search: searchParams.toString() - }); - setExpandNode(null); - } - if (treeName == "Glossary" && node.types == "parent") { - setGlossaryModal(true); - setExpandNode(null); - } - if (treeName == "Glossary" && node.types == "child") { - const searchParams = new URLSearchParams(); - searchParams.set("gid", node.guid); - searchParams.set("term", "term"); - searchParams.set("gtype", "term"); - searchParams.set("viewType", "term"); - searchParams.set("searchType", "basic"); - searchParams.set("term", `${node.id}@${node.parent}`); - navigate({ - pathname: `/glossary/${node.cGuid}`, - search: searchParams.toString() - }); - setExpandNode(null); - } - }} - data-cy="createClassification" - className="sidebar-menu-item" - > - - - - { + if (treeName == "Classifications") { + const searchParams = new URLSearchParams(); + const classificationName = node.label + ? node.label.split(" (")[0] + : node.nodeName || node.id; + searchParams.set("tag", classificationName); + navigate({ + pathname: `/tag/tagAttribute/${classificationName}`, + search: searchParams.toString() + }); + setExpandNode(null); + } + if (treeName == "Glossary" && node.types == "parent") { + const searchParams = new URLSearchParams(); + searchParams.set("gid", node.guid); + searchParams.set("gtype", "glossary"); + searchParams.set("viewType", "glossary"); + searchParams.set("searchType", "basic"); + navigate({ + pathname: `/glossary/${node.guid}`, + search: searchParams.toString() + }); + setExpandNode(null); + } + if (treeName == "Glossary" && node.types == "child") { + const searchParams = new URLSearchParams(); + searchParams.set("gid", node.guid); + searchParams.set("term", "term"); + searchParams.set("gtype", isEmptyServicetype ? "term" : "category"); + searchParams.set("viewType", isEmptyServicetype ? "term" : "category"); + searchParams.set("searchType", "basic"); + if (isEmptyServicetype) { + const termName = node.id.endsWith(`@${node.parent}`) ? node.id : `${node.id}@${node.parent}`; + searchParams.set("term", termName); + } + navigate({ + pathname: `/glossary/${node.cGuid}`, + search: searchParams.toString() + }); + setExpandNode(null); + } + }} + data-cy="createClassification" + className="sidebar-menu-item" > - {`View/Edit ${ - treeName == "Glossary" - ? node.types == "parent" - ? "Glossary" - : "Term" - : "" - }`} - - - )} + + + + + {treeName === "Classifications" + ? "View/Edit Classification" + : node.types === "parent" + ? "View/Edit Glossary" + : isEmptyServicetype ? "View/Edit Term" : "View/Edit Category"} + + + )} {((treeName == "Classifications" && !addOnClassification.includes(node.id)) || (treeName == "Glossary" && node.types == "parent") || (treeName == "Glossary" && node.types == "child" && isEmptyServicetype)) && ( - // && - // !isEmpty(gtype) - { - if (treeName == "Classifications") { - setdeleteTagModal(true); - } - if (treeName == "Glossary") { - setDeleteGlossaryModal(true); - } - handleCloseNode(); - }} - data-cy="createClassification" - className="sidebar-menu-item" - > - - - - { + if (treeName == "Classifications") { + setdeleteTagModal(true); + } + if (treeName == "Glossary") { + setDeleteGlossaryModal(true); + } + handleCloseNode(); + }} + data-cy="createClassification" + className="sidebar-menu-item" > - {`Delete ${ - treeName == "Glossary" + + + + + {`Delete ${treeName == "Glossary" ? node.types == "parent" ? "Glossary" : "Term" : "" - }`} - - - )} + }`} + + + )} {(treeName == "Classifications" || (treeName == "Glossary" && (node.types == "child" || node.types == undefined) && isEmptyServicetype)) && ( - { - e.stopPropagation(); - setExpandNode(null); - const searchParams = new URLSearchParams(); - searchParams.set("searchType", "basic"); - if (treeName == "Classifications") { - let classificationName: string; - if (node.label) { - classificationName = node.label.split(" (")[0]; - } else if (node.nodeName) { - classificationName = node.nodeName; - } else { - classificationName = node.id; + { + e.stopPropagation(); + setExpandNode(null); + const searchParams = new URLSearchParams(); + searchParams.set("searchType", "basic"); + if (treeName == "Classifications") { + let classificationName: string; + if (node.label) { + classificationName = node.label.split(" (")[0]; + } else if (node.nodeName) { + classificationName = node.nodeName; + } else { + classificationName = node.id; + } + searchParams.set("tag", classificationName); + } else if (treeName == "Glossary") { + const termValue = node.types == "child" && node.parent + ? `${node.id}@${node.parent}` + : node.id; + searchParams.set("term", termValue); } - searchParams.set("tag", classificationName); - } else if (treeName == "Glossary") { - const termValue = node.types == "child" && node.parent - ? `${node.id}@${node.parent}` - : node.id; - searchParams.set("term", termValue); - } - navigate({ - pathname: "/search/searchResult", - search: searchParams.toString() - }); - }} - data-cy="createClassification" - className="sidebar-menu-item" - > - - - - - Search - - - )} + + + + + Search + + + )} {treeName == "Glossary" && !isEmptyServicetype && node.types == "child" && diff --git a/dashboard/src/components/__tests__/TreeNodeIcons.test.tsx b/dashboard/src/components/__tests__/TreeNodeIcons.test.tsx index 91142d7e7a4..69786c4531b 100644 --- a/dashboard/src/components/__tests__/TreeNodeIcons.test.tsx +++ b/dashboard/src/components/__tests__/TreeNodeIcons.test.tsx @@ -74,7 +74,7 @@ jest.mock('../../api/apiMethods/savedSearchApiMethod', () => ({ jest.mock('../../utils/Utils', () => ({ isEmpty: (val: any) => - val == null || (Array.isArray(val) ? val.length === 0 : val === '') , + val == null || (Array.isArray(val) ? val.length === 0 : val === ''), serverError: jest.fn() })) @@ -118,7 +118,7 @@ describe('TreeNodeIcons', () => { }) it('handles CustomFilters rename flow', async () => { - + const updatedData = jest.fn() render( { expect(screen.getByTestId('delete-glossary')).toBeTruthy() }) - it('opens glossary edit modal for parent', () => { + it('navigates to glossary view/edit for parent', () => { render( { fireEvent.click(screen.getByTestId('MoreHorizOutlinedIcon')) fireEvent.click(screen.getByText('View/Edit Glossary')) - expect(screen.getByTestId('glossary-form')).toBeTruthy() + expect(navigateMock).toHaveBeenCalledWith({ + pathname: '/glossary/gloss1-guid', + search: expect.stringContaining('gtype=glossary') + }) }) it('navigates to search for classification', () => { @@ -295,4 +298,27 @@ describe('TreeNodeIcons', () => { }) }) + it('navigates to glossary child view/edit without double appending glossary name if term already contains it', () => { + render( + + ) + + fireEvent.click(screen.getByTestId('MoreHorizOutlinedIcon')) + fireEvent.click(screen.getByText('View/Edit Term')) + expect(navigateMock).toHaveBeenCalledWith({ + pathname: '/glossary/c1', + search: expect.stringContaining('term=Term1%40Gloss') // Should not be Term1%40Gloss%40Gloss + }) + }) + }) diff --git a/dashboard/src/styles/classificationForm.scss b/dashboard/src/styles/classificationForm.scss index 612ff3665d3..1214e70644e 100644 --- a/dashboard/src/styles/classificationForm.scss +++ b/dashboard/src/styles/classificationForm.scss @@ -23,8 +23,8 @@ min-height: 90px !important; } -/* Prevent duplicate toolbars in ReactQuill */ -.classification-form-editor .ql-toolbar:not(:first-of-type) { +/* Prevent duplicate toolbars in ReactQuill by hiding the zombie toolbars that precede the active one */ +.classification-form-editor .ql-toolbar:has(~ .ql-toolbar) { display: none !important; } @@ -34,4 +34,4 @@ box-sizing: border-box; font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; padding: 8px; -} +} \ No newline at end of file diff --git a/dashboard/src/views/DetailPage/DetailPageAttributes.tsx b/dashboard/src/views/DetailPage/DetailPageAttributes.tsx index 6aac958b823..ea8b781412a 100644 --- a/dashboard/src/views/DetailPage/DetailPageAttributes.tsx +++ b/dashboard/src/views/DetailPage/DetailPageAttributes.tsx @@ -50,6 +50,7 @@ import AddUpdateTermForm from "@views/Glossary/AddUpdateTermForm"; import AddUpdateCategoryForm from "@views/Glossary/AddUpdateCategoryForm"; import { removeTermorCategory } from "@api/apiMethods/glossaryApiMethod"; import { StyledPaper } from "@utils/Muiutils"; +import AddUpdateGlossaryForm from "@views/Glossary/AddUpdateGlossaryForm"; import ShowMoreView from "@components/ShowMore/ShowMoreView"; import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline"; import AddTag from "@views/Classification/AddTag"; @@ -80,6 +81,11 @@ const DetailPageAttribute = ({ const [openAddTermModal, setOpenAddTermModal] = useState(false); const [categoryModal, setCategoryModal] = useState(false); + const [editGlossaryModal, setEditGlossaryModal] = useState(false); + + const handleCloseEditGlossaryModal = () => { + setEditGlossaryModal(false); + }; const handleCloseTermModal = () => { setOpenAddTermModal(false); @@ -146,7 +152,19 @@ const DetailPageAttribute = ({ {name}{" "} {isEmpty(bmguid) && ( - + @@ -175,11 +196,11 @@ const DetailPageAttribute = ({ style={{ ...(isEmpty(bmguid) ? { - display: "grid", - gridTemplateColumns: "1fr 1fr", - gridGap: "1rem 2rem", - marginBottom: "0.75rem" - } + display: "grid", + gridTemplateColumns: "1fr 1fr", + gridGap: "1rem 2rem", + marginBottom: "0.75rem" + } : {}) }} > @@ -353,7 +374,7 @@ const DetailPageAttribute = ({ ) ))} {!isEmpty(gtypeParams) && - gtypeParams == "category" && + (gtypeParams == "category" || gtypeParams == "glossary") && (loading ? ( ) : ( !isEmpty(guid) && - gtypeParams == "category" && ( + (gtypeParams == "category" || gtypeParams == "glossary") && ( Terms - - { - if (!hasAnyGlossaryTerms) { - toast.dismiss(); - toast.info("There are no available terms"); - return; - } - setOpenAddTermModal(true); - }} - > - {" "} - - + {gtypeParams != "glossary" && ( + + { + if (!hasAnyGlossaryTerms) { + toast.dismiss(); + toast.info("There are no available terms"); + return; + } + setOpenAddTermModal(true); + }} + > + {" "} + + + )} - + {isEmpty(data?.["terms"]) ? ( + N/A + ) : ( + + )} ) ))} {!isEmpty(gtypeParams) && - gtypeParams == "term" && + (gtypeParams == "term" || gtypeParams == "glossary") && (loading ? ( ) : ( !isEmpty(guid) && - gtypeParams == "term" && ( + (gtypeParams == "term" || gtypeParams == "glossary") && ( Categories - - { - setCategoryModal(true); - }} - > - {" "} - - + {gtypeParams != "glossary" && ( + + { + setCategoryModal(true); + }} + > + {" "} + + + )} - + {isEmpty(data?.["categories"]) ? ( + N/A + ) : ( + + )} ) @@ -691,6 +724,15 @@ const DetailPageAttribute = ({ dataObj={data} /> )} + {editGlossaryModal && ( + + )} {attributeModal && ( { useEffect(() => { let params: any = { gtype: gtype, guid: guid }; + dispatchApi(fetchGlossaryDetails(params)); }, [guid]); diff --git a/dashboard/src/views/DetailPage/__tests__/DetailPageAttributes.test.tsx b/dashboard/src/views/DetailPage/__tests__/DetailPageAttributes.test.tsx index bc3893b94aa..e99f00fd491 100644 --- a/dashboard/src/views/DetailPage/__tests__/DetailPageAttributes.test.tsx +++ b/dashboard/src/views/DetailPage/__tests__/DetailPageAttributes.test.tsx @@ -15,6 +15,7 @@ * limitations under the License. */ +import "@testing-library/jest-dom" import React from 'react' import { fireEvent, render, screen } from '@testing-library/react' import { MemoryRouter } from 'react-router-dom' @@ -334,6 +335,13 @@ describe('DetailPageAttribute', () => { fireEvent.click(screen.getByText('close-category')) }) + it('shows edit button when gtype is glossary', () => { + mockGtype = 'glossary' + mockParams.guid = 'g-glossary' + renderComp() + expect(document.querySelector('[data-cy="addTag"]')).not.toBeNull() + }) + it('hides edit button when bmguid present', () => { mockParams.bmguid = 'bm-1' renderComp() @@ -366,6 +374,14 @@ describe('DetailPageAttribute', () => { fireEvent.click(screen.getByText('close-assign-term')) }) + it('glossary page shows terms but hides Add Term button', () => { + mockGtype = 'glossary' + mockParams.guid = 'gc' + renderComp() + expect(screen.getByTestId('smv-Terms')).toBeInTheDocument() + expect(document.querySelector('[data-title="Add Term"]')).toBeNull() + }) + it('toast when no glossary terms available for assign term', () => { mockGtype = 'category' mockParams.guid = 'gc' @@ -392,6 +408,14 @@ describe('DetailPageAttribute', () => { fireEvent.click(screen.getByText('close-assign-cat')) }) + it('glossary page shows categories but hides Add Categories button', () => { + mockGtype = 'glossary' + mockParams.guid = 'gt' + renderComp() + expect(screen.getByTestId('smv-Category')).toBeInTheDocument() + expect(document.querySelector('[data-title="Add Categories"]')).toBeNull() + }) + it('superTypes section loading and loaded', () => { const { rerender } = render( @@ -518,7 +542,7 @@ describe('DetailPageAttribute', () => { superTypes: [], subTypes: [], }) - expect(screen.getByTestId('smv-Terms')).toBeInTheDocument() + expect(screen.getAllByText('N/A').length).toBeGreaterThan(0) }) it('term Categories list tolerates null entity data via optional chaining', () => { @@ -529,7 +553,7 @@ describe('DetailPageAttribute', () => { superTypes: [], subTypes: [], }) - expect(screen.getByTestId('smv-Category')).toBeInTheDocument() + expect(screen.getAllByText('N/A').length).toBeGreaterThan(0) }) it('Sub Classifications ShowMore tolerates null entity data', () => { diff --git a/dashboard/src/views/Glossary/AddUpdateCategoryForm.tsx b/dashboard/src/views/Glossary/AddUpdateCategoryForm.tsx index 7a754544d97..6e9f0cf44fb 100644 --- a/dashboard/src/views/Glossary/AddUpdateCategoryForm.tsx +++ b/dashboard/src/views/Glossary/AddUpdateCategoryForm.tsx @@ -118,6 +118,11 @@ const AddUpdateCategoryForm = (props: { } if (isAdd) { dispatchApi(fetchGlossaryData()); + if (gType && glossaryTypeGuid) { + let params: any = { gtype: gType, guid: glossaryTypeGuid }; + dispatchApi(fetchGlossaryDetails(params)); + dispatchApi(fetchDetailPageData(glossaryTypeGuid as string)); + } } else { if (!isEmpty(dataObj)) { let params: any = { gtype: gType, guid: glossaryTypeGuid }; diff --git a/dashboard/src/views/Glossary/AddUpdateGlossaryForm.tsx b/dashboard/src/views/Glossary/AddUpdateGlossaryForm.tsx index 5f4a2285406..03020cc1efc 100644 --- a/dashboard/src/views/Glossary/AddUpdateGlossaryForm.tsx +++ b/dashboard/src/views/Glossary/AddUpdateGlossaryForm.tsx @@ -57,14 +57,22 @@ const hasSelectedImportFile = (fileData: unknown): boolean => { return true; return false; }; +import { useLocation, useParams } from "react-router-dom"; +import { fetchGlossaryDetails } from "@redux/slice/glossaryDetailsSlice"; +import { fetchDetailPageData } from "@redux/slice/detailPageSlice"; const AddUpdateGlossaryForm = (props: { open: any; onClose: any; isAdd: any; node: Record | undefined; + dataObj?: any; }) => { - const { open, onClose, isAdd, node } = props; + const { open, onClose, isAdd, node, dataObj } = props; + const { guid: glossaryGuid } = useParams(); + const location = useLocation(); + const searchParams = new URLSearchParams(location.search); + const gType: string | undefined | null = searchParams.get("gtype"); const dispatch = useAppDispatch(); const toastId: any = useRef(null); const { glossaryData }: any = useAppSelector((state: any) => state.glossary); @@ -75,314 +83,325 @@ const AddUpdateGlossaryForm = (props: { glossaryObj = glossaryData.find((obj: { name: string }) => { return obj.name == id; }); - const { name, shortDescription, longDescription } = glossaryObj || {}; + const { name, shortDescription } = glossaryObj || {}; + const longDescription = dataObj?.longDescription || glossaryObj?.longDescription || ""; defaultValue["name"] = name; defaultValue["shortDescription"] = shortDescription; defaultValue["longDescription"] = longDescription; } - const { - control, - handleSubmit, - setValue, - formState: { isSubmitting } - } = useForm({ - defaultValues: isAdd ? {} : defaultValue, - mode: "onChange", - shouldUnregister: true - }); - - const [glossaryCreateTab, setGlossaryCreateTab] = - useState("create"); - const [importFileData, setImportFileData] = useState([]); - const [importProgress, setImportProgress] = useState(0); - const [importErrorDetails, setImportErrorDetails] = useState(false); - const [importData, setImportData] = useState(null); - const [importUploading, setImportUploading] = useState(false); - - const resetImportUi = useCallback(() => { - setGlossaryCreateTab("create"); - setImportFileData([]); - setImportProgress(0); - setImportErrorDetails(false); - setImportData(null); - setImportUploading(false); - }, []); + const { + control, + handleSubmit, + setValue, + formState: { isSubmitting } + } = useForm({ + defaultValues: isAdd ? {} : defaultValue, + mode: "onChange", + shouldUnregister: true + }); - const handleModalClose = useCallback(() => { - resetImportUi(); - onClose(); - }, [onClose, resetImportUi]); + const [glossaryCreateTab, setGlossaryCreateTab] = + useState("create"); + const [importFileData, setImportFileData] = useState([]); + const [importProgress, setImportProgress] = useState(0); + const [importErrorDetails, setImportErrorDetails] = useState(false); + const [importData, setImportData] = useState(null); + const [importUploading, setImportUploading] = useState(false); - useEffect(() => { - if (!open) { - resetImportUi(); - } - }, [open, resetImportUi]); - - const handleGlossaryCreateTabChange = ( - _event: MouseEvent, - next: GlossaryCreateTab | null - ) => { - if (next == null) return; - setGlossaryCreateTab(next); - if (next === "create") { + const resetImportUi = useCallback(() => { + setGlossaryCreateTab("create"); setImportFileData([]); setImportProgress(0); setImportErrorDetails(false); setImportData(null); - } - }; + setImportUploading(false); + }, []); - const handleDownloadTemplate = async () => { - try { - await downloadGlossaryImportTemplate(); - } catch { - toast.dismiss(toastId.current); - toastId.current = toast.error("Could not download template"); - } - }; + const handleModalClose = useCallback(() => { + resetImportUi(); + onClose(); + }, [onClose, resetImportUi]); - const handleImportUpload = async () => { - if (!hasSelectedImportFile(importFileData)) return; - setImportUploading(true); - try { - const importResp = await postGlossaryImportFormData( - importFileData, - (pct) => setImportProgress(pct) - ); - if (importResp.data.failedImportInfoList == undefined) { - toast.dismiss(toastId.current); - toastId.current = toast.success( - `File: ${importFileData.name} imported successfully` - ); - await dispatch(fetchGlossaryData()); - handleModalClose(); - return; + useEffect(() => { + if (!open) { + resetImportUi(); } - if (importResp.data.failedImportInfoList != undefined) { + }, [open, resetImportUi]); + + const handleGlossaryCreateTabChange = ( + _event: MouseEvent, + next: GlossaryCreateTab | null + ) => { + if (next == null) return; + setGlossaryCreateTab(next); + if (next === "create") { + setImportFileData([]); + setImportProgress(0); + setImportErrorDetails(false); + setImportData(null); + } + }; + + const handleDownloadTemplate = async () => { + try { + await downloadGlossaryImportTemplate(); + } catch { toast.dismiss(toastId.current); - toastId.current = toast.error( - importResp.data.failedImportInfoList[0].remarks + toastId.current = toast.error("Could not download template"); + } + }; + + const handleImportUpload = async () => { + if (!hasSelectedImportFile(importFileData)) return; + setImportUploading(true); + try { + const importResp = await postGlossaryImportFormData( + importFileData, + (pct) => setImportProgress(pct) ); - setImportErrorDetails(true); + if (importResp.data.failedImportInfoList == undefined) { + toast.dismiss(toastId.current); + toastId.current = toast.success( + `File: ${importFileData.name} imported successfully` + ); + await dispatch(fetchGlossaryData()); + handleModalClose(); + return; + } + if (importResp.data.failedImportInfoList != undefined) { + toast.dismiss(toastId.current); + toastId.current = toast.error( + importResp.data.failedImportInfoList[0].remarks + ); + setImportErrorDetails(true); + } + setImportData(importResp.data); + } catch (error) { + const message = getApiErrorToastMessage(error); + if (message === null) { + return; + } + toast.dismiss(toastId.current); + toastId.current = toast.error(message); + } finally { + setImportUploading(false); } - setImportData(importResp.data); - } catch (error) { - const message = getApiErrorToastMessage(error); - if (message === null) { - return; + }; + + const onSubmit = async (formValues: any) => { + let formData = { ...formValues }; + const { guid, qualifiedName } = glossaryObj; + const { + name, + shortDescription, + longDescription + }: { name: string; shortDescription: string; longDescription: string } = + formData; + let data: Record = {}; + if (!isAdd) { + data["guid"] = guid; + data["qualifiedName"] = qualifiedName; } - toast.dismiss(toastId.current); - toastId.current = toast.error(message); - } finally { - setImportUploading(false); - } - }; + data["name"] = name; + data["shortDescription"] = !isEmpty(shortDescription) + ? shortDescription + : ""; + data["longDescription"] = !isEmpty(longDescription) ? longDescription : ""; - const onSubmit = async (formValues: any) => { - let formData = { ...formValues }; - const { guid, qualifiedName } = glossaryObj; - const { - name, - shortDescription, - longDescription - }: { name: string; shortDescription: string; longDescription: string } = - formData; - let data: Record = {}; - if (!isAdd) { - data["guid"] = guid; - data["qualifiedName"] = qualifiedName; - } - data["name"] = name; - data["shortDescription"] = !isEmpty(shortDescription) - ? shortDescription - : ""; - data["longDescription"] = !isEmpty(longDescription) ? longDescription : ""; + try { + if (isAdd) { + await createGlossary(data); + } else { + await editGlossary(guid, data); + } - try { - if (isAdd) { - await createGlossary(data); - } else { - await editGlossary(guid, data); - } + await dispatch(fetchGlossaryData()); - await dispatch(fetchGlossaryData()); - toast.dismiss(toastId.current); - toastId.current = toast.success( - `Glossary ${name} was ${isAdd ? "created" : "updated"} successfully` - ); - handleModalClose(); - } catch (error) { - serverError(error, toastId); - } - }; + if (!isAdd && glossaryGuid) { + let params: any = { gtype: gType, guid: glossaryGuid }; + dispatch(fetchGlossaryDetails(params)); + dispatch(fetchDetailPageData(glossaryGuid as string)); + } - const showImportError = isAdd && glossaryCreateTab === "import" && importErrorDetails; + toast.dismiss(toastId.current); + toastId.current = toast.success( + `Glossary ${name} was ${isAdd ? "created" : "updated"} successfully` + ); + handleModalClose(); + } catch (error) { + console.log( + `Error occur while ${isAdd ? "created" : "updated"} Glossary`, + error + ); + serverError(error, toastId); + } + }; - const modalTitle = showImportError - ? "Error Details" - : isAdd - ? "Create Glossary" - : "Edit Glossary"; + const showImportError = isAdd && glossaryCreateTab === "import" && importErrorDetails; - const titleIconNode = - showImportError ? ( - { - e.stopPropagation(); - setImportErrorDetails(false); - }} - size="small" - sx={{ color: (theme) => theme.palette.grey[500] }} - > - - - - - ) : undefined; + const modalTitle = showImportError + ? "Error Details" + : isAdd + ? "Create Glossary" + : "Edit Glossary"; - const isImportMode = isAdd && glossaryCreateTab === "import" && !importErrorDetails; + const titleIconNode = + showImportError ? ( + { + e.stopPropagation(); + setImportErrorDetails(false); + }} + size="small" + sx={{ color: (theme) => theme.palette.grey[500] }} + > + + + + + ) : undefined; - const button2Label = (() => { - if (!isAdd) return "Update"; - if (glossaryCreateTab === "create") return "Create"; - if (importErrorDetails) return ""; - return "Upload"; - })(); + const isImportMode = isAdd && glossaryCreateTab === "import" && !importErrorDetails; - const button2Handler = (() => { - if (!isAdd || glossaryCreateTab === "create") return handleSubmit(onSubmit); - if (importErrorDetails) return () => {}; - return handleImportUpload; - })(); + const button2Label = (() => { + if (!isAdd) return "Update"; + if (glossaryCreateTab === "create") return "Create"; + if (importErrorDetails) return ""; + return "Upload"; + })(); - const disableButton2 = (() => { - if (!isAdd) return isSubmitting; - if (glossaryCreateTab === "create") return isSubmitting; - if (importErrorDetails) return true; - return !hasSelectedImportFile(importFileData); - })(); + const button2Handler = (() => { + if (!isAdd || glossaryCreateTab === "create") return handleSubmit(onSubmit); + if (importErrorDetails) return () => { }; + return handleImportUpload; + })(); - const button2Loading = - isAdd && glossaryCreateTab === "import" && !importErrorDetails - ? importUploading - : isSubmitting; + const disableButton2 = (() => { + if (!isAdd) return isSubmitting; + if (glossaryCreateTab === "create") return isSubmitting; + if (importErrorDetails) return true; + return !hasSelectedImportFile(importFileData); + })(); - const hideButton2 = Boolean( - isAdd && glossaryCreateTab === "import" && importErrorDetails - ); + const button2Loading = + isAdd && glossaryCreateTab === "import" && !importErrorDetails + ? importUploading + : isSubmitting; - return ( - <> - - - {isAdd && ( - - + + + {isAdd && ( + + + - - Create glossary - - - Import glossary terms - - - - )} - {(!isAdd || glossaryCreateTab === "create") && ( - - )} - {isImportMode && ( - - - - - )} - {showImportError && importData?.failedImportInfoList && ( - + + + )} + {(!isAdd || glossaryCreateTab === "create") && ( + + )} + {isImportMode && ( + + + + + )} + {showImportError && importData?.failedImportInfoList && ( + + + {importData.failedImportInfoList.map( + ( + value: { + index: number; + remarks: string; + }, + index: number + ) => ( + + + + ) + )} + + + )} + + + +); }; export default AddUpdateGlossaryForm; diff --git a/dashboard/src/views/Glossary/AddUpdateTermForm.tsx b/dashboard/src/views/Glossary/AddUpdateTermForm.tsx index f5ece0289d0..36d5c857c08 100644 --- a/dashboard/src/views/Glossary/AddUpdateTermForm.tsx +++ b/dashboard/src/views/Glossary/AddUpdateTermForm.tsx @@ -114,6 +114,11 @@ const AddUpdateTermForm = (props: { } if (isAdd) { dispatchApi(fetchGlossaryData()); + if (gType && entityGuid) { + let params: any = { gtype: gType, guid: entityGuid }; + dispatchApi(fetchGlossaryDetails(params)); + dispatchApi(fetchDetailPageData(entityGuid as string)); + } } else { if (!isEmpty(dataObj)) { let params: any = { gtype: gType, guid: entityGuid }; diff --git a/dashboard/src/views/Glossary/GlossaryForm.tsx b/dashboard/src/views/Glossary/GlossaryForm.tsx index 3ab9a82fdde..52ce264b4b4 100644 --- a/dashboard/src/views/Glossary/GlossaryForm.tsx +++ b/dashboard/src/views/Glossary/GlossaryForm.tsx @@ -29,9 +29,9 @@ import ReactQuill from "react-quill-new"; const GlossaryForm = (props: { control: any; handleSubmit: any; - setValue: any; + setValue?: any; }) => { - const { control, handleSubmit, setValue } = props; + const { control, handleSubmit } = props; const [alignment, setAlignment] = useState("formatted"); const handleChange = ( @@ -146,7 +146,6 @@ const GlossaryForm = (props: { placeholder={"Description required"} onChange={(text) => { field.onChange(text); - setValue("description", text); }} className="classification-form-editor" value={field.value || ""} @@ -161,7 +160,6 @@ const GlossaryForm = (props: { e.stopPropagation(); const value = e.target.value; field.onChange(value); - setValue("description", value); }} style={{ width: "100%" }} /> diff --git a/dashboard/src/views/Glossary/__tests__/AddUpdateCategoryForm.test.tsx b/dashboard/src/views/Glossary/__tests__/AddUpdateCategoryForm.test.tsx index a8102431f8e..dc8ce2571eb 100644 --- a/dashboard/src/views/Glossary/__tests__/AddUpdateCategoryForm.test.tsx +++ b/dashboard/src/views/Glossary/__tests__/AddUpdateCategoryForm.test.tsx @@ -27,9 +27,9 @@ import { Provider } from 'react-redux'; import { configureStore } from '@reduxjs/toolkit'; import { MemoryRouter } from 'react-router-dom'; import AddUpdateCategoryForm from '../AddUpdateCategoryForm'; -import * as glossarySlice from '@redux/slice/glossarySlice'; -import * as glossaryDetailsSlice from '@redux/slice/glossaryDetailsSlice'; -import * as detailPageSlice from '@redux/slice/detailPageSlice'; +import * as glossarySlice from '../../../redux/slice/glossarySlice'; +import * as glossaryDetailsSlice from '../../../redux/slice/glossaryDetailsSlice'; +import * as detailPageSlice from '../../../redux/slice/detailPageSlice'; // Mock functions const mockOnClose = jest.fn(); @@ -59,34 +59,34 @@ const mockToastSuccess = jest.fn(); const mockToastDismiss = jest.fn(); jest.mock('react-toastify', () => ({ toast: { - success: (...args: any[]) => mockToastSuccess(...args), - dismiss: (...args: any[]) => mockToastDismiss(...args), + success: function() { return mockToastSuccess.apply(null, arguments as any); }, + dismiss: function() { return mockToastDismiss.apply(null, arguments as any); }, error: jest.fn() } })); // Mock API methods -jest.mock('@api/apiMethods/glossaryApiMethod', () => ({ - createTermorCategory: (...args: any[]) => mockCreateTermorCategory(...args), - editTermorCatgeory: (...args: any[]) => mockEditTermorCatgeory(...args) +jest.mock('../../../api/apiMethods/glossaryApiMethod', () => ({ + createTermorCategory: function() { return mockCreateTermorCategory.apply(null, arguments as any); }, + editTermorCatgeory: function() { return mockEditTermorCatgeory.apply(null, arguments as any); } })); // Mock Redux slices -jest.mock('@redux/slice/glossarySlice', () => ({ +jest.mock('../../../redux/slice/glossarySlice', () => ({ fetchGlossaryData: jest.fn() })); -jest.mock('@redux/slice/glossaryDetailsSlice', () => ({ +jest.mock('../../../redux/slice/glossaryDetailsSlice', () => ({ fetchGlossaryDetails: jest.fn() })); -jest.mock('@redux/slice/detailPageSlice', () => ({ +jest.mock('../../../redux/slice/detailPageSlice', () => ({ fetchDetailPageData: jest.fn() })); // Mock Utils -jest.mock('@utils/Utils', () => { - const actualUtils = jest.requireActual('@utils/Utils'); +jest.mock('../../../utils/Utils', () => { + const actualUtils = jest.requireActual('../../../utils/Utils'); return { ...actualUtils, isEmpty: (value: any) => { @@ -95,12 +95,12 @@ jest.mock('@utils/Utils', () => { if (typeof value === 'string' && value.trim().length === 0) return true; return false; }, - serverError: (...args: any[]) => mockServerError(...args) + serverError: function() { return mockServerError.apply(null, arguments as any); } }; }); // Mock CustomModal -jest.mock('@components/Modal', () => ({ +jest.mock('../../../components/Modal', () => ({ __esModule: true, default: ({ open, onClose, children, title, button1Handler, button2Handler, button2Label, disableButton2 }: any) => open ? ( @@ -222,18 +222,18 @@ describe('AddUpdateCategoryForm', () => { mockToastSuccess.mockReturnValue('toast-id-123'); // Set up Redux action mocks - (glossarySlice.fetchGlossaryData as jest.Mock).mockImplementation((...args: any[]) => { - mockFetchGlossaryData(...args); + (glossarySlice.fetchGlossaryData as unknown as jest.Mock).mockImplementation(function() { + mockFetchGlossaryData.apply(null, arguments as any); return { type: 'FETCH_GLOSSARY_DATA' }; }); - (glossaryDetailsSlice.fetchGlossaryDetails as jest.Mock).mockImplementation((...args: any[]) => { - mockFetchGlossaryDetails(...args); + (glossaryDetailsSlice.fetchGlossaryDetails as unknown as jest.Mock).mockImplementation(function() { + mockFetchGlossaryDetails.apply(null, arguments as any); return { type: 'FETCH_GLOSSARY_DETAILS' }; }); - (detailPageSlice.fetchDetailPageData as jest.Mock).mockImplementation((...args: any[]) => { - mockFetchDetailPageData(...args); + (detailPageSlice.fetchDetailPageData as unknown as jest.Mock).mockImplementation(function() { + mockFetchDetailPageData.apply(null, arguments as any); return { type: 'FETCH_DETAIL_PAGE_DATA' }; }); }); @@ -388,6 +388,44 @@ describe('AddUpdateCategoryForm', () => { expect(mockOnClose).toHaveBeenCalled(); }, 30000); + it('should dispatch fetchGlossaryDetails and fetchDetailPageData when creating category successfully in add mode and URL has gtype and guid', async () => { + mockLocation = { search: '?gtype=glossary' }; + mockParams = { guid: 'entity-guid-456' }; + + const store = createStore([ + { + name: 'Parent Glossary', + guid: 'parent-guid', + shortDescription: 'Parent Short Desc', + longDescription: 'Parent Long Desc' + } + ]); + + render( + + + + + + ); + + await fillFormAndSubmit('Test Category'); + + await waitFor(() => { + expect(mockFetchGlossaryDetails).toHaveBeenCalledWith({ gtype: 'glossary', guid: 'entity-guid-456' }); + }, { timeout: 10000 }); + + await waitFor(() => { + expect(mockFetchDetailPageData).toHaveBeenCalledWith('entity-guid-456'); + }, { timeout: 10000 }); + }); + it('should create child category with parentCategory in add mode', async () => { mockParams = { guid: 'glossary-type-guid' }; const store = createStore([ diff --git a/dashboard/src/views/Glossary/__tests__/AddUpdateGlossaryForm.test.tsx b/dashboard/src/views/Glossary/__tests__/AddUpdateGlossaryForm.test.tsx index fbb8e57b5cb..aa8b69a5b4b 100644 --- a/dashboard/src/views/Glossary/__tests__/AddUpdateGlossaryForm.test.tsx +++ b/dashboard/src/views/Glossary/__tests__/AddUpdateGlossaryForm.test.tsx @@ -56,19 +56,25 @@ const mockGlossaryData = [ // Mock toast jest.mock('react-toastify', () => ({ toast: { - success: (...args: any[]) => mockToastSuccess(...args), - dismiss: (...args: any[]) => mockToastDismiss(...args) + success: function () { return mockToastSuccess.apply(null, arguments as any); }, + dismiss: function () { return mockToastDismiss.apply(null, arguments as any); } } })); +// Mock react-router-dom +jest.mock('react-router-dom', () => ({ + useLocation: () => ({ search: '?gtype=glossary&guid=test-guid' }), + useParams: () => ({}) +})); + // Mock API methods -jest.mock('@api/apiMethods/glossaryApiMethod', () => ({ - createGlossary: (...args: any[]) => mockCreateGlossary(...args), - editGlossary: (...args: any[]) => mockEditGlossary(...args) +jest.mock('../../../api/apiMethods/glossaryApiMethod', () => ({ + createGlossary: function () { return mockCreateGlossary.apply(null, arguments as any); }, + editGlossary: function () { return mockEditGlossary.apply(null, arguments as any); } })); // Mock Redux hooks -jest.mock('@hooks/reducerHook', () => { +jest.mock('../../../hooks/reducerHook', () => { // Define mock glossary data inside the factory to avoid hoisting issues const mockGlossaryDataLocal = [ { @@ -86,23 +92,23 @@ jest.mock('@hooks/reducerHook', () => { longDescription: 'Another long description' } ]; - + // Define mock state inside the factory function to avoid hoisting issues const mockState = { glossary: { glossaryData: mockGlossaryDataLocal } }; - + const mockUseAppSelectorFn = jest.fn((sel: any) => { // If selector is not a function, return default if (typeof sel !== 'function') { return { glossaryData: mockGlossaryDataLocal }; } - + // Execute selector const result = sel(mockState); - + // CRITICAL: Never return undefined - return glossaryData if result is undefined // Also ensure glossaryData is always an array, never null if (result === undefined || result === null) { @@ -112,20 +118,20 @@ jest.mock('@hooks/reducerHook', () => { if (result.glossaryData === null || result.glossaryData === undefined) { return { glossaryData: mockGlossaryDataLocal }; } - + return result; }); - + return { useAppDispatch: () => mockDispatch, - useAppSelector: (...args: any[]) => mockUseAppSelectorFn(...args), + useAppSelector: mockUseAppSelectorFn, // Export the mock function for use in tests __mockUseAppSelector: mockUseAppSelectorFn }; }); // Get the mock function for use in tests -const { __mockUseAppSelector: mockUseAppSelector } = require('@hooks/reducerHook'); +const { __mockUseAppSelector: mockUseAppSelector } = require('../../../hooks/reducerHook'); // Mock Redux slice - fetchGlossaryData is a thunk that returns a function const mockFetchGlossaryDataThunk = jest.fn(() => async (dispatch: any) => { @@ -133,14 +139,15 @@ const mockFetchGlossaryDataThunk = jest.fn(() => async (dispatch: any) => { return Promise.resolve({ type: 'glossary/fetchGlossaryData' }); }); -jest.mock('@redux/slice/glossarySlice', () => ({ - fetchGlossaryData: (...args: any[]) => mockFetchGlossaryDataThunk(...args) +jest.mock('../../../redux/slice/glossarySlice', () => ({ + fetchGlossaryData: function() { return mockFetchGlossaryDataThunk.apply(null, arguments as any); } })); // Mock Utils -jest.mock('@utils/Utils', () => { - const actualLodash = jest.requireActual('lodash'); +jest.mock('../../../utils/Utils', () => { + const actualUtils = jest.requireActual('../../../utils/Utils') as any; return { + ...actualUtils, isEmpty: (value: any) => { return ( value === undefined || @@ -149,12 +156,12 @@ jest.mock('@utils/Utils', () => { (typeof value === 'string' && value.trim().length === 0) ); }, - serverError: (...args: any[]) => mockServerError(...args) + serverError: function () { return mockServerError.apply(null, arguments as any); } }; }); // Mock Modal component -jest.mock('@components/Modal', () => ({ +jest.mock('../../../components/Modal', () => ({ __esModule: true, default: ({ open, onClose, children, title, button1Label, button1Handler, button2Label, button2Handler, disableButton2 }: any) => open ? ( @@ -283,7 +290,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { return action; }); // Reset useAppSelector to default behavior - const { __mockUseAppSelector } = require('@hooks/reducerHook'); + const { __mockUseAppSelector } = require('../../../hooks/reducerHook'); __mockUseAppSelector.mockImplementation((selector: any) => { const mockState = { glossary: { @@ -474,7 +481,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); }); @@ -498,7 +505,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); }); @@ -534,7 +541,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); }); @@ -574,7 +581,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); }); @@ -608,7 +615,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); // Wait for async operations to complete @@ -643,7 +650,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); }); @@ -676,7 +683,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); }); @@ -701,7 +708,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); }); @@ -726,7 +733,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); }); @@ -751,7 +758,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); }); @@ -776,7 +783,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); }); @@ -813,7 +820,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); // Wait for async operations to complete @@ -849,7 +856,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); }); @@ -877,7 +884,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { } ]; - const { __mockUseAppSelector } = require('@hooks/reducerHook'); + const { __mockUseAppSelector } = require('../../../hooks/reducerHook'); __mockUseAppSelector.mockReturnValueOnce({ glossaryData: incompleteGlossaryData }); @@ -895,7 +902,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { }); test('handles empty glossaryData array', () => { - const { __mockUseAppSelector } = require('@hooks/reducerHook'); + const { __mockUseAppSelector } = require('../../../hooks/reducerHook'); __mockUseAppSelector.mockReturnValueOnce({ glossaryData: [] }); @@ -914,7 +921,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { }); test('handles null glossaryData', () => { - const { __mockUseAppSelector } = require('@hooks/reducerHook'); + const { __mockUseAppSelector } = require('../../../hooks/reducerHook'); __mockUseAppSelector.mockReturnValueOnce({ glossaryData: null }); @@ -957,7 +964,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); }); @@ -981,7 +988,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); }); @@ -1005,7 +1012,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); }); @@ -1029,7 +1036,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); }); @@ -1053,7 +1060,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); }); @@ -1077,7 +1084,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); }); @@ -1101,7 +1108,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); }); @@ -1122,7 +1129,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { } ]; - const { __mockUseAppSelector } = require('@hooks/reducerHook'); + const { __mockUseAppSelector } = require('../../../hooks/reducerHook'); __mockUseAppSelector.mockReturnValueOnce({ glossaryData: glossaryDataWithoutGuid }); @@ -1150,7 +1157,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { } ]; - const { __mockUseAppSelector } = require('@hooks/reducerHook'); + const { __mockUseAppSelector } = require('../../../hooks/reducerHook'); __mockUseAppSelector.mockReturnValueOnce({ glossaryData: glossaryDataWithoutQualifiedName }); @@ -1231,7 +1238,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); }); @@ -1260,7 +1267,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); }); @@ -1288,7 +1295,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); }); @@ -1321,7 +1328,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); // Wait for async operations to complete @@ -1353,7 +1360,7 @@ describe('AddUpdateGlossaryForm - 100% Coverage', () => { ); const submitBtn = screen.getByTestId('submit-btn'); - + await act(async () => { fireEvent.click(submitBtn); // Wait for async operations to complete diff --git a/dashboard/src/views/Glossary/__tests__/AddUpdateTermForm.test.tsx b/dashboard/src/views/Glossary/__tests__/AddUpdateTermForm.test.tsx index fabac3dba62..e54b68d37c1 100644 --- a/dashboard/src/views/Glossary/__tests__/AddUpdateTermForm.test.tsx +++ b/dashboard/src/views/Glossary/__tests__/AddUpdateTermForm.test.tsx @@ -27,9 +27,9 @@ import { configureStore } from '@reduxjs/toolkit'; import { MemoryRouter } from 'react-router-dom'; import AddUpdateTermForm from '../AddUpdateTermForm'; import userEvent from '@testing-library/user-event'; -import * as glossarySlice from '@redux/slice/glossarySlice'; -import * as glossaryDetailsSlice from '@redux/slice/glossaryDetailsSlice'; -import * as detailPageSlice from '@redux/slice/detailPageSlice'; +import * as glossarySlice from '../../../redux/slice/glossarySlice'; +import * as glossaryDetailsSlice from '../../../redux/slice/glossaryDetailsSlice'; +import * as detailPageSlice from '../../../redux/slice/detailPageSlice'; // Mock state variables let mockGuid: string | undefined = undefined; @@ -58,9 +58,9 @@ const mockToastDismiss = jest.fn(); jest.mock('react-toastify', () => ({ toast: { - success: (...args: any[]) => mockToastSuccess(...args), + success: function () { return mockToastSuccess.apply(null, arguments as any); }, error: jest.fn(), - dismiss: (...args: any[]) => mockToastDismiss(...args) + dismiss: function () { return mockToastDismiss.apply(null, arguments as any); } } })); @@ -68,9 +68,9 @@ jest.mock('react-toastify', () => ({ const mockCreateTermorCategory = jest.fn(); const mockEditTermorCategory = jest.fn(); -jest.mock('@api/apiMethods/glossaryApiMethod', () => ({ - createTermorCategory: (...args: any[]) => mockCreateTermorCategory(...args), - editTermorCatgeory: (...args: any[]) => mockEditTermorCategory(...args) +jest.mock('../../../api/apiMethods/glossaryApiMethod', () => ({ + createTermorCategory: function () { return mockCreateTermorCategory.apply(null, arguments as any); }, + editTermorCatgeory: function () { return mockEditTermorCategory.apply(null, arguments as any); } })); // Mock Redux actions @@ -81,20 +81,20 @@ const mockFetchGlossaryData = jest.fn(() => (dispatch: any) => { const mockFetchGlossaryDetails = jest.fn(); const mockFetchDetailPageData = jest.fn(); -jest.mock('@redux/slice/glossarySlice', () => ({ +jest.mock('../../../redux/slice/glossarySlice', () => ({ fetchGlossaryData: jest.fn() })); -jest.mock('@redux/slice/glossaryDetailsSlice', () => ({ +jest.mock('../../../redux/slice/glossaryDetailsSlice', () => ({ fetchGlossaryDetails: jest.fn() })); -jest.mock('@redux/slice/detailPageSlice', () => ({ +jest.mock('../../../redux/slice/detailPageSlice', () => ({ fetchDetailPageData: jest.fn() })); // Mock Utils -jest.mock('@utils/Utils', () => { +jest.mock('../../../utils/Utils', () => { const actualLodash = jest.requireActual('lodash'); return { isEmpty: actualLodash.isEmpty, @@ -103,25 +103,25 @@ jest.mock('@utils/Utils', () => { }); // Mock CustomModal -jest.mock('@components/Modal', () => ({ +jest.mock('../../../components/Modal', () => ({ __esModule: true, - default: ({ - open, - onClose, - children, - title, - button1Handler, - button2Handler, + default: ({ + open, + onClose, + children, + title, + button1Handler, + button2Handler, button2Label, - disableButton2 + disableButton2 }: any) => open ? (
{title}
{children} -