diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d323171d..48b16aa8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,10 +9,10 @@ "version": "0.1.0", "dependencies": { "@bcgov-nr/nr-theme": "^1.7.0", - "@carbon/charts-react": "^1.13.32", - "@carbon/icons-react": "^11.50.1", - "@carbon/pictograms-react": "^11.49.0", - "@carbon/react": "^1.27.0", + "@carbon/charts-react": "^1.22.5", + "@carbon/icons-react": "^11.53.0", + "@carbon/pictograms-react": "^11.69.0", + "@carbon/react": "^1.71.1", "@tanstack/react-query": "^5.50.1", "@types/node": "^22.0.0", "@vitejs/plugin-react": "^4.0.4", @@ -1690,9 +1690,9 @@ "license": "MIT" }, "node_modules/@carbon/charts": { - "version": "1.22.4", - "resolved": "https://registry.npmjs.org/@carbon/charts/-/charts-1.22.4.tgz", - "integrity": "sha512-htpbi6y2pIlHiH7FysqSu9qQVT9NpnbY0Z6c4a7QXi/0gf54TZWn4Lgmn3m6wA4gmsNp1Emqc0SE8tRIsh7LRA==", + "version": "1.22.5", + "resolved": "https://registry.npmjs.org/@carbon/charts/-/charts-1.22.5.tgz", + "integrity": "sha512-/EZBnFbud3IOPcCroBgR7mNXOB3U/z0BETyjv38FlkC/f5GtGvnXu+8RtrsYH+YW5JEfZlycjY5F8C5eP6E0Yg==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -1714,13 +1714,13 @@ } }, "node_modules/@carbon/charts-react": { - "version": "1.22.4", - "resolved": "https://registry.npmjs.org/@carbon/charts-react/-/charts-react-1.22.4.tgz", - "integrity": "sha512-e0yQQyuOEtrJk/pazSlF3E5M1jTqcHxLevxe4jfaHB/tFw5qhHPyTNR3qUdJ2Mf5evXQJghqGqxwE9xXK4djIA==", + "version": "1.22.5", + "resolved": "https://registry.npmjs.org/@carbon/charts-react/-/charts-react-1.22.5.tgz", + "integrity": "sha512-a/aZiCdN6BSw/b3dbRW4Segfy5bLISDjODdUi+oHoXPHQh8A+Sz9GMN5SREbuZ03jtZUvtocr51dEuoq5y8MKQ==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@carbon/charts": "1.22.4", + "@carbon/charts": "1.22.5", "@carbon/icons-react": "^11.53.0", "@ibm/telemetry-js": "^1.8.0" }, @@ -1821,9 +1821,9 @@ } }, "node_modules/@carbon/react": { - "version": "1.71.0", - "resolved": "https://registry.npmjs.org/@carbon/react/-/react-1.71.0.tgz", - "integrity": "sha512-OavbieRyUk11s6g9vClrzVTnWkoRbEcXjtieI7CD0lvHVAEc5hNf1+lKtS27LINBPKchJdi6Ox/p7N9Vc0jh1g==", + "version": "1.71.1", + "resolved": "https://registry.npmjs.org/@carbon/react/-/react-1.71.1.tgz", + "integrity": "sha512-wLlHFwJKoCFt2ltpvc9mRfdMtRg3YTNr/GHGcNFNfPjRtNFTuTx/ImmXc9WInctsEts3i2VOizT/pdRJ9fAP+A==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -1840,7 +1840,6 @@ "flatpickr": "4.6.13", "invariant": "^2.2.3", "lodash.debounce": "^4.0.8", - "lodash.findlast": "^4.5.0", "lodash.omit": "^4.5.0", "lodash.throttle": "^4.1.1", "prop-types": "^15.7.2", @@ -10451,12 +10450,6 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "license": "MIT" }, - "node_modules/lodash.findlast": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.findlast/-/lodash.findlast-4.6.0.tgz", - "integrity": "sha512-+OGwb1FVKjhc2aIEQ9vKqNDW1a0/HaCLr0iCIK10jfVif3dBE0nhQD0jOZNZLh7zOlmFUTrk+vt85eXoH4vKuA==", - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 4af11e42..00082c76 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,10 +5,10 @@ "type": "module", "dependencies": { "@bcgov-nr/nr-theme": "^1.7.0", - "@carbon/charts-react": "^1.13.32", - "@carbon/icons-react": "^11.50.1", - "@carbon/pictograms-react": "^11.49.0", - "@carbon/react": "^1.27.0", + "@carbon/charts-react": "^1.22.5", + "@carbon/icons-react": "^11.53.0", + "@carbon/pictograms-react": "^11.69.0", + "@carbon/react": "^1.71.1", "@tanstack/react-query": "^5.50.1", "@types/node": "^22.0.0", "@vitejs/plugin-react": "^4.0.4", diff --git a/frontend/src/__test__/components/ActionButtons.test.tsx b/frontend/src/__test__/components/ActionButtons.test.tsx index 6f554d9b..0c6137b5 100644 --- a/frontend/src/__test__/components/ActionButtons.test.tsx +++ b/frontend/src/__test__/components/ActionButtons.test.tsx @@ -1,39 +1,41 @@ // ActionButtons.test.tsx import React from "react"; -import { vi } from "vitest"; +import { MemoryRouter } from 'react-router-dom'; import { render, screen, fireEvent } from "@testing-library/react"; import ActionButtons from "../../components/ActionButtons"; - -// Mock console.log -const consoleLogMock = vi.spyOn(console, "log").mockImplementationOnce(() =>vi.fn()) ; - -afterEach(() => { - consoleLogMock.mockClear(); -}); - -afterAll(() => { - consoleLogMock.mockRestore(); -}); +import { NotificationProvider } from "../../contexts/NotificationProvider" describe("ActionButtons", () => { - const rowId = "test-row-id"; - - it("renders the 'View' and 'Document Download' buttons", () => { - render(); + const rowId = "123456"; + const favorited = false; + + it("renders the 'Favorite Opening' and 'Document Download' buttons", () => { + render( + + + + + + ); // Check that both buttons are in the document - expect(screen.getByRole("button", { name: /View/i })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /Favorite Opening/i })).toBeInTheDocument(); expect(screen.getByRole("button", { name: /Document Download/i })).toBeInTheDocument(); }); - it("calls console.log with rowId when the 'View' button is clicked", () => { - render(); + it("set the 'Favorite Opening' as favorited when button is clicked", () => { + render( + + + + + + ); // Find the "View" button and click it - const viewButton = screen.getByRole("button", { name: /View/i }); + const viewButton = screen.getByRole("button", { name: /Favorite Opening/i }); + expect(viewButton.classList).toContain('favorite-button'); fireEvent.click(viewButton); - - // Check if console.log was called with the correct rowId - expect(consoleLogMock).toHaveBeenCalledWith(rowId); + expect(screen.getByRole("button", { name: /Favorite Opening/i }).classList).toContain('favorite'); }); }); diff --git a/frontend/src/__test__/components/SilvicultureSearch/Openings/SearchScreenDataTable.test.tsx b/frontend/src/__test__/components/SilvicultureSearch/Openings/SearchScreenDataTable.test.tsx index 77b500cd..6970522a 100644 --- a/frontend/src/__test__/components/SilvicultureSearch/Openings/SearchScreenDataTable.test.tsx +++ b/frontend/src/__test__/components/SilvicultureSearch/Openings/SearchScreenDataTable.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, screen, fireEvent, act } from '@testing-library/react'; +import { render, screen, fireEvent, act, waitFor } from '@testing-library/react'; import { describe, expect, it, vi } from 'vitest'; import SearchScreenDataTable from '../../../../components/SilvicultureSearch/Openings/SearchScreenDataTable'; import { searchScreenColumns as columns } from '../../../../constants/tableConstants'; @@ -358,7 +358,7 @@ describe('Search Screen Data table test', () => { expect(screen.getByTestId('toggle-spatial')).toContainHTML('Hide map'); - const checkbox = document.querySelector('.cds--checkbox-group'); + const checkbox = await screen.findByTestId('checkbox-114207'); expect(checkbox).toBeInTheDocument(); }); @@ -386,13 +386,13 @@ describe('Search Screen Data table test', () => { ); - const checkboxGroup = document.querySelector('.cds--checkbox-group'); - expect(checkboxGroup).toBeInTheDocument(); + const checkbox = screen.getByTestId(`checkbox-${rows[0].openingId}`); + expect(checkbox).toBeInTheDocument(); expect(screen.getByTestId(`checkbox-${rows[0].openingId}`)).toBeInTheDocument(); - const checkbox = screen.getByTestId(`checkbox-${rows[0].openingId}`); - fireEvent.click(checkbox); - expect(checkbox).toBeChecked(); + const checkbox2 = screen.getByTestId(`checkbox-${rows[0].openingId}`); + fireEvent.click(checkbox2); + expect(checkbox2).toBeChecked(); expect(setOpeningIds).toHaveBeenCalledWith([parseFloat(rows[0].openingId)]); }); @@ -435,21 +435,13 @@ describe('Search Screen Data table test', () => { await act(async () => expect(screen.getByTestId('row-114207')).toBeInTheDocument() ); await act(async () => expect(screen.getByTestId('cell-actions-114206')).toBeInTheDocument() ); - - const overflowMenu = screen.getByTestId('action-ofl-114207'); - await act(async () => expect(overflowMenu).toBeInTheDocument() ); - await act(async () => fireEvent.click(overflowMenu)); const actionOverflow = screen.getByTestId(`action-fav-114207`); await act(async () => expect(actionOverflow).toBeInTheDocument() ); - expect(actionOverflow).toContainHTML('Favourite opening'); + expect(actionOverflow).toHaveAttribute('aria-pressed', 'false'); await act(async () => fireEvent.click(actionOverflow)); - const overflowMenuAgain = screen.getByTestId('action-ofl-114207'); - await act(async () => expect(overflowMenuAgain).toBeInTheDocument() ); - await act(async () => fireEvent.click(overflowMenuAgain)); - - expect(screen.getByTestId(`action-fav-114207`)).toContainHTML('Unfavourite opening'); + expect(screen.getByTestId(`action-fav-114207`)).toHaveAttribute('aria-pressed', 'true'); }); diff --git a/frontend/src/__test__/components/TableCellContent.test.tsx b/frontend/src/__test__/components/TableCellContent.test.tsx index 3cebab09..9c55bc85 100644 --- a/frontend/src/__test__/components/TableCellContent.test.tsx +++ b/frontend/src/__test__/components/TableCellContent.test.tsx @@ -1,9 +1,11 @@ // TableCellContent.test.tsx import React from "react"; +import { MemoryRouter } from 'react-router-dom'; import { vi } from "vitest"; import { render, screen } from "@testing-library/react"; import TableCellContent from "../../components/TableCellContent"; import { OpeningsSearch } from "../../types/OpeningsSearch"; +import { NotificationProvider } from "../../contexts/NotificationProvider" // Mock components vi.mock("../StatusTag", () => ({ @@ -28,6 +30,7 @@ describe("TableCellContent", () => { it("renders StatusTag when headerKey is 'statusDescription'", () => { render( + { selectedRows={selectedRows} handleRowSelectionChanged={handleRowSelectionChanged} /> + ); expect(screen.getByText(/Active/i)).toBeInTheDocument(); @@ -42,40 +46,52 @@ describe("TableCellContent", () => { it("renders ActionButtons and optionally SpatialCheckbox when headerKey is 'actions'", () => { const { rerender } = render( - + + + + + ); console.log(screen.debug()); - expect(screen.queryByText(/View/i)).toBeInTheDocument(); + expect(screen.queryByText(/Favorite Opening/i)).toBeInTheDocument(); }); it("renders category code and description when headerKey is 'Category'", () => { render( - + + + + + ); expect(screen.getAllByText("A - Category A")[0]).toBeInTheDocument(); }); it("renders default content for other headerKey values", () => { render( - + + + + + ); expect(screen.getByText(/Unknown Value/i)).toBeInTheDocument(); @@ -83,13 +99,17 @@ describe("TableCellContent", () => { it("renders SpatialCheckbox when headerKey is 'actions' and showSpatial is true", () => { render( - + + + + + ); //check if the Checkbox text is present expect(screen.getByText(/Click to view this opening's map activity./i)).toBeInTheDocument(); diff --git a/frontend/src/__test__/components/TableRowComponent.test.tsx b/frontend/src/__test__/components/TableRowComponent.test.tsx index 49fc2286..22f52f71 100644 --- a/frontend/src/__test__/components/TableRowComponent.test.tsx +++ b/frontend/src/__test__/components/TableRowComponent.test.tsx @@ -61,8 +61,8 @@ describe("TableRowComponent", () => { ); // Simulate click on the row - const tableRow = screen.getByRole("row"); - fireEvent.click(tableRow); + const tableRow = screen.getAllByRole("cell"); + fireEvent.click(tableRow[1]); // Verify that setOpeningDetails was called with true expect(setOpeningDetails).toHaveBeenCalledWith("1"); diff --git a/frontend/src/components/ActionButtons/index.tsx b/frontend/src/components/ActionButtons/index.tsx index 6de44329..4aac3f17 100644 --- a/frontend/src/components/ActionButtons/index.tsx +++ b/frontend/src/components/ActionButtons/index.tsx @@ -3,31 +3,71 @@ import React from "react"; import { Button } from "@carbon/react"; import * as Icons from "@carbon/icons-react"; +import { useNavigate } from "react-router-dom"; +import FavoriteButton from "../FavoriteButton"; +import { useNotification } from "../../contexts/NotificationProvider"; +import { setOpeningFavorite, deleteOpeningFavorite } from "../../services/OpeningFavouriteService"; interface ActionButtonsProps { + favorited: boolean; rowId: string; } -const ActionButtons: React.FC = ({ rowId }) => ( +const ActionButtons: React.FC = ({ favorited, rowId }) => { + const { displayNotification } = useNotification(); + const navigate = useNavigate(); + + const handleFavoriteChange = async (newStatus: boolean, openingId: number) => { + try { + if(!newStatus){ + await deleteOpeningFavorite(openingId); + }else{ + await setOpeningFavorite(openingId); + } + displayNotification({ + title: `Opening Id ${openingId} ${!newStatus ? 'un' : ''}favourited`, + subTitle: newStatus ? "You can follow this opening ID on your dashboard" : undefined, + type: 'success', + dismissIn: 8000, + buttonLabel: newStatus ? "Go to track openings" : undefined, + onClose: () => { + navigate("/opening?tab=metrics&scrollTo=trackOpenings"); + } + }); + } catch (error) { + displayNotification({ + title: 'Error', + subTitle: `Failed to update favorite status for ${openingId}`, + type: 'error', + dismissIn: 8000, + onClose: () => {} + }); + } + }; + + return ( <> -