diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/OpeningFavoriteEndpoint.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/OpeningFavoriteEndpoint.java index d766e9f0..1cd022db 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/OpeningFavoriteEndpoint.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/OpeningFavoriteEndpoint.java @@ -14,7 +14,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping(path = "/api/openings/favorites", produces = MediaType.APPLICATION_JSON_VALUE) +@RequestMapping(path = "/api/openings/favourites", produces = MediaType.APPLICATION_JSON_VALUE) @RequiredArgsConstructor public class OpeningFavoriteEndpoint { diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpoint.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpoint.java index 3b519f43..d6e27910 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpoint.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpoint.java @@ -23,21 +23,6 @@ public class UserOpeningEndpoint { private final UserOpeningService userOpeningService; - /** - * Gets up to three tracked Openings to a user. - * - * @return A list of openings or the http code 204-No Content. - */ - @GetMapping("/dashboard-track-openings") - public ResponseEntity> getUserTrackedOpenings() { - List userOpenings = userOpeningService.getUserTrackedOpenings(); - if (userOpenings.isEmpty()) { - return ResponseEntity.noContent().build(); - } - - return ResponseEntity.ok(userOpenings); - } - /** * Saves one Opening ID as favourite to an user. * diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/UserOpeningRepository.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/UserOpeningRepository.java index f56ebd49..422fdb84 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/UserOpeningRepository.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/UserOpeningRepository.java @@ -3,6 +3,7 @@ import ca.bc.gov.restapi.results.postgres.entity.UserOpeningEntity; import ca.bc.gov.restapi.results.postgres.entity.UserOpeningEntityId; import java.util.List; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; /** @@ -11,6 +12,7 @@ public interface UserOpeningRepository extends JpaRepository { - List findAllByUserId(String userId); + + List findAllByUserId(String userId, Pageable page); } diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningService.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningService.java index 448892c2..ac4fe837 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningService.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningService.java @@ -2,25 +2,17 @@ import ca.bc.gov.restapi.results.common.exception.OpeningNotFoundException; import ca.bc.gov.restapi.results.common.exception.UserFavoriteNotFoundException; -import ca.bc.gov.restapi.results.common.exception.UserOpeningNotFoundException; import ca.bc.gov.restapi.results.common.security.LoggedUserService; -import ca.bc.gov.restapi.results.oracle.dto.OpeningSearchResponseDto; -import ca.bc.gov.restapi.results.oracle.entity.OpeningEntity; -import ca.bc.gov.restapi.results.oracle.enums.OpeningCategoryEnum; -import ca.bc.gov.restapi.results.oracle.enums.OpeningStatusEnum; import ca.bc.gov.restapi.results.oracle.repository.OpeningRepository; -import ca.bc.gov.restapi.results.postgres.dto.MyRecentActionsRequestsDto; -import ca.bc.gov.restapi.results.postgres.entity.OpeningsActivityEntity; import ca.bc.gov.restapi.results.postgres.entity.UserOpeningEntity; import ca.bc.gov.restapi.results.postgres.entity.UserOpeningEntityId; import ca.bc.gov.restapi.results.postgres.repository.OpeningsActivityRepository; import ca.bc.gov.restapi.results.postgres.repository.UserOpeningRepository; import jakarta.transaction.Transactional; -import java.util.ArrayList; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.ocpsoft.prettytime.PrettyTime; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; /** @@ -35,64 +27,13 @@ public class UserOpeningService { private final UserOpeningRepository userOpeningRepository; - private final OpeningsActivityRepository openingsActivityRepository; - private final OpeningRepository openingRepository; - /** - * Gets user's tracked Openings. - * - * @return A list of {@link MyRecentActionsRequestsDto} containing the found records. - */ - public List getUserTrackedOpenings() { - log.info("Getting all user openings for the Track openings table"); - - String userId = loggedUserService.getLoggedUserId(); - List userList = userOpeningRepository.findAllByUserId(userId); - - if (userList.isEmpty()) { - log.info("No saved openings for the current user!"); - return List.of(); - } - - List openingIds = userList.stream().map(UserOpeningEntity::getOpeningId).toList(); - List openingActivities = - openingsActivityRepository.findAllByOpeningIdIn(openingIds); - - if (openingActivities.isEmpty()) { - log.info("No records found on the opening activity table for the opening ID list!"); - return List.of(); - } - - List resultList = new ArrayList<>(); - - PrettyTime prettyTime = new PrettyTime(); - - for (OpeningsActivityEntity activityEntity : openingActivities) { - MyRecentActionsRequestsDto requestsDto = - new MyRecentActionsRequestsDto( - activityEntity.getActivityTypeDesc(), - activityEntity.getOpeningId(), - activityEntity.getStatusCode(), - activityEntity.getStatusDesc(), - prettyTime.format(activityEntity.getLastUpdated()), - activityEntity.getLastUpdated()); - - resultList.add(requestsDto); - - if (resultList.size() == 3) { - break; - } - } - - return resultList; - } - public List listUserFavoriteOpenings() { log.info("Loading user favorite openings for {}", loggedUserService.getLoggedUserId()); List userList = userOpeningRepository - .findAllByUserId(loggedUserService.getLoggedUserId()); + .findAllByUserId(loggedUserService.getLoggedUserId(), PageRequest.of(0, 32)); if (userList.isEmpty()) { log.info("No saved openings for {}", loggedUserService.getLoggedUserId()); diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/OpeningFavoriteEndpointIntegrationTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/OpeningFavoriteEndpointIntegrationTest.java index 3f064214..ad50a528 100644 --- a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/OpeningFavoriteEndpointIntegrationTest.java +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/OpeningFavoriteEndpointIntegrationTest.java @@ -35,12 +35,12 @@ class OpeningFavoriteEndpointIntegrationTest extends AbstractTestContainerIntegr @Test @Order(1) - @DisplayName("No favorites to begin with") + @DisplayName("No favourites to begin with") void shouldBeEmpty() throws Exception { mockMvc .perform( - MockMvcRequestBuilders.get("/api/openings/favorites") + MockMvcRequestBuilders.get("/api/openings/favourites") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$").isEmpty()); @@ -52,7 +52,7 @@ void shouldBeEmpty() throws Exception { void shouldAddToFavorite() throws Exception { mockMvc .perform( - MockMvcRequestBuilders.put("/api/openings/favorites/{openingId}", 101) + MockMvcRequestBuilders.put("/api/openings/favourites/{openingId}", 101) .with(csrf().asHeader()) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isAccepted()) @@ -70,7 +70,7 @@ void shouldAddToFavorite() throws Exception { void shouldNotAddIfDoesNotExist() throws Exception { mockMvc .perform( - MockMvcRequestBuilders.put("/api/openings/favorites/{openingId}", 987) + MockMvcRequestBuilders.put("/api/openings/favourites/{openingId}", 987) .with(csrf().asHeader()) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound()) @@ -87,11 +87,11 @@ void shouldAddToFavoriteAgain() throws Exception { @Test @Order(5) - @DisplayName("Should see list of favorites") + @DisplayName("Should see list of favourites") void shouldBeAbleToSeeOpening() throws Exception { mockMvc .perform( - MockMvcRequestBuilders.get("/api/openings/favorites") + MockMvcRequestBuilders.get("/api/openings/favourites") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) @@ -104,7 +104,7 @@ void shouldBeAbleToSeeOpening() throws Exception { void shouldRemoveFromFavorites() throws Exception { mockMvc .perform( - MockMvcRequestBuilders.delete("/api/openings/favorites/{openingId}", 101) + MockMvcRequestBuilders.delete("/api/openings/favourites/{openingId}", 101) .with(csrf().asHeader()) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isNoContent()) @@ -121,7 +121,7 @@ void shouldRemoveFromFavorites() throws Exception { void shouldThrownErrorIfNoFavoriteFound() throws Exception { mockMvc .perform( - MockMvcRequestBuilders.delete("/api/openings/favorites/{openingId}", 101) + MockMvcRequestBuilders.delete("/api/openings/favourites/{openingId}", 101) .with(csrf().asHeader()) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound()) diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpointTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpointTest.java deleted file mode 100644 index c0c69400..00000000 --- a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpointTest.java +++ /dev/null @@ -1,81 +0,0 @@ -package ca.bc.gov.restapi.results.postgres.endpoint; - -import static org.mockito.Mockito.when; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import ca.bc.gov.restapi.results.postgres.dto.MyRecentActionsRequestsDto; -import ca.bc.gov.restapi.results.postgres.service.UserOpeningService; -import java.time.LocalDateTime; -import java.util.List; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.http.MediaType; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.web.servlet.MockMvc; - -@WebMvcTest(UserOpeningEndpoint.class) -@WithMockUser -class UserOpeningEndpointTest { - - @Autowired private MockMvc mockMvc; - - @MockBean private UserOpeningService userOpeningService; - - @Test - @DisplayName("Get user tracked openings happy path should succeed") - void getUserTrackedOpenings_happyPath_shouldSucceed() throws Exception { - MyRecentActionsRequestsDto action = - new MyRecentActionsRequestsDto( - "Update", 123456L, "APP", "Approved", "2 minutes ago", LocalDateTime.now()); - when(userOpeningService.getUserTrackedOpenings()).thenReturn(List.of(action)); - - mockMvc - .perform( - get("/api/user-openings/dashboard-track-openings") - .with(csrf().asHeader()) - .header("Content-Type", MediaType.APPLICATION_JSON_VALUE) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("$[0].activityType").value("Update")) - .andExpect(jsonPath("$[0].openingId").value("123456")) - .andExpect(jsonPath("$[0].statusCode").value("APP")) - .andExpect(jsonPath("$[0].statusDescription").value("Approved")) - .andExpect(jsonPath("$[0].lastUpdatedLabel").value("2 minutes ago")) - .andReturn(); - } - - @Test - @DisplayName("Get user tracked openings no data should succeed") - void getUserTrackedOpenings_noData_shouldSucceed() throws Exception { - when(userOpeningService.getUserTrackedOpenings()).thenReturn(List.of()); - - mockMvc - .perform( - get("/api/user-openings/dashboard-track-openings") - .with(csrf().asHeader()) - .header("Content-Type", MediaType.APPLICATION_JSON_VALUE) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isNoContent()) - .andReturn(); - } - - void saveUserOpening_happyPath_shouldSucceed() throws Exception { - // - } - - void deleteUserOpening_happyPath_shouldSucceed() throws Exception { - // - } - - void deleteUserOpening_notFound_shouldFail() throws Exception { - // - } -} diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningServiceTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningServiceTest.java index eb6a30b5..fe740f50 100644 --- a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningServiceTest.java +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningServiceTest.java @@ -5,17 +5,12 @@ import static org.mockito.Mockito.when; import ca.bc.gov.restapi.results.common.exception.UserFavoriteNotFoundException; -import ca.bc.gov.restapi.results.common.exception.UserOpeningNotFoundException; import ca.bc.gov.restapi.results.common.security.LoggedUserService; import ca.bc.gov.restapi.results.oracle.entity.OpeningEntity; import ca.bc.gov.restapi.results.oracle.repository.OpeningRepository; -import ca.bc.gov.restapi.results.postgres.dto.MyRecentActionsRequestsDto; -import ca.bc.gov.restapi.results.postgres.entity.OpeningsActivityEntity; import ca.bc.gov.restapi.results.postgres.entity.UserOpeningEntity; import ca.bc.gov.restapi.results.postgres.repository.OpeningsActivityRepository; import ca.bc.gov.restapi.results.postgres.repository.UserOpeningRepository; -import java.time.LocalDateTime; -import java.util.List; import java.util.Optional; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -28,13 +23,17 @@ @ExtendWith(MockitoExtension.class) class UserOpeningServiceTest { - @Mock LoggedUserService loggedUserService; + @Mock + LoggedUserService loggedUserService; - @Mock UserOpeningRepository userOpeningRepository; + @Mock + UserOpeningRepository userOpeningRepository; - @Mock OpeningsActivityRepository openingsActivityRepository; + @Mock + OpeningsActivityRepository openingsActivityRepository; - @Mock OpeningRepository openingRepository; + @Mock + OpeningRepository openingRepository; private UserOpeningService userOpeningService; @@ -44,53 +43,8 @@ class UserOpeningServiceTest { void setup() { this.userOpeningService = new UserOpeningService( - loggedUserService, userOpeningRepository, openingsActivityRepository,openingRepository); - } - - @Test - @DisplayName("Get user tracked openings happy path should succeed") - void getUserTrackedOpenings_happyPath_shouldSucceed() { - when(loggedUserService.getLoggedUserId()).thenReturn(USER_ID); - - UserOpeningEntity entity = new UserOpeningEntity(); - entity.setUserId(USER_ID); - entity.setOpeningId(223344L); - - when(userOpeningRepository.findAllByUserId(USER_ID)).thenReturn(List.of(entity)); - - LocalDateTime now = LocalDateTime.now().minusMinutes(2); - OpeningsActivityEntity openingEntity = new OpeningsActivityEntity(); - openingEntity.setOpeningId(entity.getOpeningId()); - openingEntity.setActivityTypeCode("UPD"); - openingEntity.setActivityTypeDesc("Update"); - openingEntity.setStatusCode("APP"); - openingEntity.setStatusDesc("Approved"); - openingEntity.setLastUpdated(now); - openingEntity.setEntryUserid(USER_ID); - - when(openingsActivityRepository.findAllByOpeningIdIn(List.of(223344L))) - .thenReturn(List.of(openingEntity)); - - List openings = userOpeningService.getUserTrackedOpenings(); - - Assertions.assertFalse(openings.isEmpty()); - Assertions.assertEquals("Update", openings.get(0).activityType()); - Assertions.assertEquals(223344L, openings.get(0).openingId()); - Assertions.assertEquals("APP", openings.get(0).statusCode()); - Assertions.assertEquals("Approved", openings.get(0).statusDescription()); - Assertions.assertEquals("2 minutes ago", openings.get(0).lastUpdatedLabel()); - } - - @Test - @DisplayName("Get user tracked openings no data should succeed") - void getUserTrackedOpenings_noData_shouldSucceed() { - when(loggedUserService.getLoggedUserId()).thenReturn(USER_ID); - - when(userOpeningRepository.findAllByUserId(USER_ID)).thenReturn(List.of()); - - List openings = userOpeningService.getUserTrackedOpenings(); - - Assertions.assertTrue(openings.isEmpty()); + loggedUserService, userOpeningRepository, + openingRepository); } @Test diff --git a/frontend/src/__test__/components/BCHeaderwSide.test.tsx b/frontend/src/__test__/components/BCHeaderwSide.test.tsx index 5a920686..3015de35 100644 --- a/frontend/src/__test__/components/BCHeaderwSide.test.tsx +++ b/frontend/src/__test__/components/BCHeaderwSide.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent, act } from '@testing-library/react'; import { describe, expect, it, vi } from 'vitest'; import { BrowserRouter } from 'react-router-dom'; import BCHeaderwSide from '../../components/BCHeaderwSide'; @@ -23,10 +23,10 @@ vi.mock('../../services/TestService', () => ({ ]), })); -const renderComponent = () => { +const renderComponent = async () => { const qc = new QueryClient(); - render( + await act(() => render( @@ -34,7 +34,7 @@ const renderComponent = () => { - ); + )); }; const clientRoles: UserClientRolesType[] = [ @@ -60,18 +60,18 @@ const state = { }, }; -describe('BCHeaderwSide', () => { +describe('BCHeaderwSide', async () => { it('should renders the component', () => { renderComponent(); expect(screen.getByTestId('header')).toBeDefined(); }); - it('should renders the site name', () => { + it('should renders the site name', async () => { renderComponent(); expect(screen.getByText('SILVA')).toBeDefined(); }); - it('opens and closes the My Profile panel', () => { + it('opens and closes the My Profile panel', async () => { renderComponent(); const userSettingsButton = screen.getByTestId('header-button__user'); fireEvent.click(userSettingsButton); @@ -80,14 +80,14 @@ describe('BCHeaderwSide', () => { // expect(screen.queryByText('My Profile')).not.toBeVisible(); }); - it('renders the correct menu item names', () => { + it('renders the correct menu item names', async () => { renderComponent(); leftMenu.forEach(item => { expect(screen.getByText(item.name)).toBeInTheDocument(); }); }); - it('renders sub menu items', () => { + it('renders sub menu items', async () => { renderComponent(); const subMenuItem = leftMenu[0].items[0]; // Assuming the first item has sub items const menuItem = screen.getByText(subMenuItem.name); diff --git a/frontend/src/__test__/components/OpeningHistory.test.tsx b/frontend/src/__test__/components/OpeningHistory.test.tsx index e5be3a72..d682cf44 100644 --- a/frontend/src/__test__/components/OpeningHistory.test.tsx +++ b/frontend/src/__test__/components/OpeningHistory.test.tsx @@ -3,28 +3,28 @@ import { render, act } from '@testing-library/react'; import { describe, it, expect, vi } from 'vitest'; import OpeningHistory from '../../components/OpeningHistory'; import { NotificationProvider } from '../../contexts/NotificationProvider'; -import { deleteOpeningFavorite, fetchOpeningTrends } from '../../services/OpeningFavoriteService'; +import { deleteOpeningFavorite, fetchOpeningFavourites } from '../../services/OpeningFavouriteService'; -vi.mock('../../services/OpeningFavoriteService', () => ({ +vi.mock('../../services/OpeningFavouriteService', () => ({ deleteOpeningFavorite: vi.fn(), - fetchOpeningTrends: vi.fn(), + fetchOpeningFavourites: vi.fn(), })); describe('OpeningHistory Component', () => { it('renders correctly with given histories', async () => { - (fetchOpeningTrends as vi.Mock).mockReturnValueOnce(Promise.resolve([1, 2])); + (fetchOpeningFavourites as vi.Mock).mockReturnValueOnce(Promise.resolve([1, 2])); let container; await act(async () => { ({ container } = render()); }); // Check for the presence of Opening Ids - expect(container.querySelector('div[data-id="1"').innerHTML).toContain('Opening Id 1'); - expect(container.querySelector('div[data-id="2"').innerHTML).toContain('Opening Id 2'); + expect(container.querySelector('div[data-id="1"').innerHTML).toContain('Opening ID 1'); + expect(container.querySelector('div[data-id="2"').innerHTML).toContain('Opening ID 2'); }); it('renders correctly with empty histories', async () => { - (fetchOpeningTrends as vi.Mock).mockReturnValueOnce(Promise.resolve([])); + (fetchOpeningFavourites as vi.Mock).mockReturnValueOnce(Promise.resolve([])); let container; await act(async () => { ({ container } = render()); @@ -33,14 +33,16 @@ describe('OpeningHistory Component', () => { // Select the div with the specific class const activityHistoryContainer = container.querySelector('.row.activity-history-container.gx-4'); - // Check if the container is empty - expect(activityHistoryContainer).toBeInTheDocument(); // Ensure the element exists - expect(activityHistoryContainer?.children.length).toBe(0); // Confirm it's empty by checking for no children + // Check if the container is showing the EmptySection component, with the title, description and icon + expect(activityHistoryContainer.innerHTML).toContain('empty-section-container'); + expect(activityHistoryContainer.innerHTML).toContain('You don\'t have any favourites to show yet!'); + expect(activityHistoryContainer.innerHTML).toContain('You can favourite your openings by clicking on the heart icon inside opening details page'); + }); // check if when clicked on the FavoriteButton, the deleteOpeningFavorite function is called it('should call deleteOpeningFavorite when FavoriteButton is clicked', async () => { - (fetchOpeningTrends as vi.Mock).mockReturnValueOnce(Promise.resolve([1, 2])); + (fetchOpeningFavourites as vi.Mock).mockReturnValueOnce(Promise.resolve([1, 2])); let container; await act(async () => { ({ container } = render()); @@ -53,7 +55,7 @@ describe('OpeningHistory Component', () => { }); it('should call deleteOpeningFavorite and handle error when FavoriteButton is clicked', async () => { - (fetchOpeningTrends as vi.Mock).mockReturnValueOnce(Promise.resolve([1, 2])); + (fetchOpeningFavourites as vi.Mock).mockReturnValueOnce(Promise.resolve([1, 2])); (deleteOpeningFavorite as vi.Mock).mockRejectedValueOnce(new Error('Failed to delete favorite')); let container; await act(async () => { @@ -64,5 +66,5 @@ describe('OpeningHistory Component', () => { await act(async () => favoriteButton && favoriteButton.click()); expect(deleteOpeningFavorite).toHaveBeenCalled(); - }); + }); }); diff --git a/frontend/src/__test__/components/OpeningMetricsTab.test.tsx b/frontend/src/__test__/components/OpeningMetricsTab.test.tsx index 08a02b41..3f9678b0 100644 --- a/frontend/src/__test__/components/OpeningMetricsTab.test.tsx +++ b/frontend/src/__test__/components/OpeningMetricsTab.test.tsx @@ -1,13 +1,13 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import React from 'react'; -import { render, act, waitFor, fireEvent, screen } from '@testing-library/react'; +import { render, act, waitFor, screen } from '@testing-library/react'; import OpeningMetricsTab from '../../components/OpeningMetricsTab'; import { NotificationProvider } from '../../contexts/NotificationProvider'; -import { fetchOpeningTrends } from '../../services/OpeningFavoriteService'; +import { fetchOpeningFavourites } from '../../services/OpeningFavouriteService'; import { fetchFreeGrowingMilestones, fetchOpeningsPerYear, fetchRecentOpenings } from '../../services/OpeningService'; -vi.mock('../../services/OpeningFavoriteService', () => ({ - fetchOpeningTrends: vi.fn(), +vi.mock('../../services/OpeningFavouriteService', () => ({ + fetchOpeningFavourites: vi.fn(), })); vi.mock('../../services/OpeningService', async () => { const actual = await vi.importActual('../../services/OpeningService'); @@ -22,26 +22,10 @@ vi.mock('../../services/OpeningService', async () => { describe('OpeningMetricsTab', () => { beforeEach(() => { vi.clearAllMocks(); - (fetchRecentOpenings as vi.Mock).mockResolvedValue([{ - id: '123', - openingId: '123', - fileId: '1', - cuttingPermit: '1', - timberMark: '1', - cutBlock: '1', - grossAreaHa: 1, - statusDesc: 'Approved', - categoryDesc: 'Another:Another', - disturbanceStart: '1', - entryTimestamp: '1', - updateTimestamp: '1', - }]); - (fetchOpeningsPerYear as vi.Mock).mockResolvedValue([ - { group: '2022', key: 'Openings', value: 10 }, - { group: '2023', key: 'Openings', value: 15 }, - ]); + (fetchRecentOpenings as vi.Mock).mockResolvedValue([]); + (fetchOpeningsPerYear as vi.Mock).mockResolvedValue([]); (fetchFreeGrowingMilestones as vi.Mock).mockResolvedValue([{ group: '1-5', value: 11 }]); - (fetchOpeningTrends as vi.Mock).mockResolvedValue([1, 2, 3]); + (fetchOpeningFavourites as vi.Mock).mockResolvedValue([1, 2, 3]); }); @@ -55,25 +39,43 @@ describe('OpeningMetricsTab', () => { expect(screen.getByText('Check quantity and evolution of openings')).toBeInTheDocument(); expect(screen.getByText('Track Openings')).toBeInTheDocument(); expect(screen.getByText('Follow your favourite openings')).toBeInTheDocument(); - expect(screen.getByText('Free growing milestone declarations')).toBeInTheDocument(); - expect(screen.getByText('Check opening standards unit for inspections purposes')).toBeInTheDocument(); expect(screen.getByText('My recent actions')).toBeInTheDocument(); expect(screen.getByText('Check your recent requests and files')).toBeInTheDocument(); }); - it('should call fetchOpeningTrends and set submissionTrends state', async () => { + it('should call fetchOpeningFavourites and set submissionTrends state', async () => { - + let container; await act(async () => { - render(); + ({ container } = render()); }); await waitFor(() => { - expect(fetchOpeningTrends).toHaveBeenCalled(); - expect(screen.getByText('Opening Id 1')).toBeInTheDocument(); - expect(screen.getByText('Opening Id 2')).toBeInTheDocument(); - expect(screen.getByText('Opening Id 3')).toBeInTheDocument(); + expect(fetchOpeningFavourites).toHaveBeenCalled(); }); + + expect(container).not.toBeNull(); + + expect(container.querySelector('.row.activity-history-container.gx-4')).toBeInTheDocument(); + + expect( + container + .querySelector('.row.activity-history-container.gx-4') + .innerHTML + ).toContain('Opening ID 1'); + + expect( + container + .querySelector('.row.activity-history-container.gx-4') + .innerHTML + ).toContain('Opening ID 2'); + + expect( + container + .querySelector('.row.activity-history-container.gx-4') + .innerHTML + ).toContain('Opening ID 3'); + }); it('should scroll to "Track Openings" section when scrollTo parameter is "trackOpenings"', async () => { diff --git a/frontend/src/__test__/screens/Opening.test.tsx b/frontend/src/__test__/screens/Opening.test.tsx index 863c0d76..04b6c8f4 100644 --- a/frontend/src/__test__/screens/Opening.test.tsx +++ b/frontend/src/__test__/screens/Opening.test.tsx @@ -8,7 +8,7 @@ import { BrowserRouter } from 'react-router-dom'; import { RecentOpening } from '../../types/RecentOpening'; import { getWmsLayersWhitelistUsers } from '../../services/SecretsService'; import { fetchFreeGrowingMilestones, fetchOpeningsPerYear, fetchRecentOpenings } from '../../services/OpeningService'; -import { fetchOpeningTrends } from '../../services/OpeningFavoriteService'; +import { fetchOpeningFavourites } from '../../services/OpeningFavouriteService'; import { AuthProvider } from '../../contexts/AuthProvider'; const data = { @@ -20,8 +20,8 @@ const data = { "lastUpdated": "2024-05-16T19:59:21.635Z" }; -vi.mock('../../services/OpeningFavoriteService', () => ({ - fetchOpeningTrends: vi.fn(), +vi.mock('../../services/OpeningFavouriteService', () => ({ + fetchOpeningFavourites: vi.fn(), })); vi.mock('../../services/SecretsService', () => ({ @@ -84,7 +84,7 @@ describe('Opening screen test cases', () => { { group: '2023', key: 'Openings', value: 15 }, ]); (fetchFreeGrowingMilestones as vi.Mock).mockResolvedValue([{ group: '1-5', value: 11 }]); - (fetchOpeningTrends as vi.Mock).mockResolvedValue([1,2,3]); + (fetchOpeningFavourites as vi.Mock).mockResolvedValue([1,2,3]); diff --git a/frontend/src/__test__/services/OpeningFavoriteService.test.ts b/frontend/src/__test__/services/OpeningFavoriteService.test.ts index d24962fa..bb25235b 100644 --- a/frontend/src/__test__/services/OpeningFavoriteService.test.ts +++ b/frontend/src/__test__/services/OpeningFavoriteService.test.ts @@ -1,14 +1,13 @@ import { describe, it, expect, vi } from 'vitest'; import axios from 'axios'; -import { fetchOpeningTrends} from '../../services/OpeningFavoriteService'; import { getAuthIdToken } from '../../services/AuthService'; import { env } from '../../env'; -import { fetchOpeningTrends, setOpeningFavorite, deleteOpeningFavorite } from '../../services/OpeningFavoriteService'; +import { fetchOpeningFavourites, setOpeningFavorite, deleteOpeningFavorite } from '../../services/OpeningFavouriteService'; vi.mock('axios'); vi.mock('../../services/AuthService'); -describe('OpeningFavoriteService', () => { +describe('OpeningFavouriteService', () => { const backendUrl = env.VITE_BACKEND_URL; const authToken = 'test-token'; @@ -21,9 +20,9 @@ describe('OpeningFavoriteService', () => { const mockData = [1, 2, 3]; (axios.get as vi.Mock).mockResolvedValue({ data: mockData }); - const result = await fetchOpeningTrends(); + const result = await fetchOpeningFavourites(); - expect(axios.get).toHaveBeenCalledWith(`${backendUrl}/api/openings/favorites`, { + expect(axios.get).toHaveBeenCalledWith(`${backendUrl}/api/openings/favourites`, { headers: { Authorization: `Bearer ${authToken}` } }); expect(result).toEqual(mockData); @@ -33,9 +32,9 @@ describe('OpeningFavoriteService', () => { const mockData = []; (axios.get as vi.Mock).mockResolvedValue({ data: mockData }); - const result = await fetchOpeningTrends(); + const result = await fetchOpeningFavourites(); - expect(axios.get).toHaveBeenCalledWith(`${backendUrl}/api/openings/favorites`, { + expect(axios.get).toHaveBeenCalledWith(`${backendUrl}/api/openings/favourites`, { headers: { Authorization: `Bearer ${authToken}` } }); expect(result).toEqual(mockData); @@ -44,16 +43,16 @@ describe('OpeningFavoriteService', () => { it('should handle error while fetching submission trends', async () => { (axios.get as vi.Mock).mockRejectedValue(new Error('Network Error')); - await expect(fetchOpeningTrends()).rejects.toThrow('Network Error'); + await expect(fetchOpeningFavourites()).rejects.toThrow('Network Error'); }); it('should fetch submission trends successfully', async () => { const mockData = [1, 2, 3]; (axios.get as vi.Mock).mockResolvedValue({ data: mockData }); - const result = await fetchOpeningTrends(); + const result = await fetchOpeningFavourites(); - expect(axios.get).toHaveBeenCalledWith(`${backendUrl}/api/openings/favorites`, { + expect(axios.get).toHaveBeenCalledWith(`${backendUrl}/api/openings/favourites`, { headers: { Authorization: `Bearer ${authToken}` } }); expect(result).toEqual(mockData); @@ -63,9 +62,9 @@ describe('OpeningFavoriteService', () => { const mockData = []; (axios.get as vi.Mock).mockResolvedValue({ data: mockData }); - const result = await fetchOpeningTrends(); + const result = await fetchOpeningFavourites(); - expect(axios.get).toHaveBeenCalledWith(`${backendUrl}/api/openings/favorites`, { + expect(axios.get).toHaveBeenCalledWith(`${backendUrl}/api/openings/favourites`, { headers: { Authorization: `Bearer ${authToken}` } }); expect(result).toEqual(mockData); @@ -74,7 +73,7 @@ describe('OpeningFavoriteService', () => { it('should handle error while fetching submission trends', async () => { (axios.get as vi.Mock).mockRejectedValue(new Error('Network Error')); - await expect(fetchOpeningTrends()).rejects.toThrow('Network Error'); + await expect(fetchOpeningFavourites()).rejects.toThrow('Network Error'); }); it('should set an opening as favorite successfully', async () => { @@ -83,7 +82,7 @@ describe('OpeningFavoriteService', () => { await setOpeningFavorite(openingId); - expect(axios.put).toHaveBeenCalledWith(`${backendUrl}/api/openings/favorites/${openingId}`, null, { + expect(axios.put).toHaveBeenCalledWith(`${backendUrl}/api/openings/favourites/${openingId}`, null, { headers: { Authorization: `Bearer ${authToken}` } }); }); @@ -101,7 +100,7 @@ describe('OpeningFavoriteService', () => { await deleteOpeningFavorite(openingId); - expect(axios.delete).toHaveBeenCalledWith(`${backendUrl}/api/openings/favorites/${openingId}`, { + expect(axios.delete).toHaveBeenCalledWith(`${backendUrl}/api/openings/favourites/${openingId}`, { headers: { Authorization: `Bearer ${authToken}` } }); }); diff --git a/frontend/src/__test__/services/OpeningService.test.ts b/frontend/src/__test__/services/OpeningService.test.ts index 6dd4f320..b3427fc3 100644 --- a/frontend/src/__test__/services/OpeningService.test.ts +++ b/frontend/src/__test__/services/OpeningService.test.ts @@ -130,16 +130,7 @@ describe('OpeningService', () => { it('should fetch recent actions successfully', () => { const result = fetchRecentActions(); - expect(result).toEqual([ - { - activityType: 'Update', - openingId: '1541297', - statusCode: 'APP', - statusDescription: 'Approved', - lastUpdatedLabel: '1 minute ago', - lastUpdated: '2024-05-16T19:59:21.635Z' - } - ]); + expect(result).toEqual([]); }); }); diff --git a/frontend/src/components/ChartContainer/index.tsx b/frontend/src/components/ChartContainer/index.tsx index d54d99ba..98f390e4 100644 --- a/frontend/src/components/ChartContainer/index.tsx +++ b/frontend/src/components/ChartContainer/index.tsx @@ -1,8 +1,6 @@ import React from 'react'; import './ChartContainer.scss' import ChartTitle from '../ChartTitle'; -import { Button } from '@carbon/react'; -import * as Icons from '@carbon/icons-react'; type Props = { children?: React.ReactNode; @@ -24,26 +22,6 @@ function ChartContainer({ children, title, description }: Props): JSX.Element {
-
-
{children? (
diff --git a/frontend/src/components/OpeningHistory/index.tsx b/frontend/src/components/OpeningHistory/index.tsx index 936968d4..1bf5cb8e 100644 --- a/frontend/src/components/OpeningHistory/index.tsx +++ b/frontend/src/components/OpeningHistory/index.tsx @@ -1,15 +1,10 @@ import React, { useState, useEffect } from "react"; -import { - ProgressIndicator, - ProgressStep -} from '@carbon/react'; - import History from '../../types/History'; -import statusClass from '../../utils/HistoryStatus'; import FavoriteButton from '../FavoriteButton'; +import EmptySection from "../EmptySection"; import './styles.scss'; import { useNotification } from '../../contexts/NotificationProvider'; -import { fetchOpeningTrends, deleteOpeningFavorite } from "../../services/OpeningFavoriteService"; +import { fetchOpeningFavourites, deleteOpeningFavorite } from "../../services/OpeningFavouriteService"; const OpeningHistory: React.FC = () => { @@ -17,20 +12,18 @@ const OpeningHistory: React.FC = () => { const [histories, setHistories] = useState([]); const loadTrends = async () => { - const history = await fetchOpeningTrends(); + const history = await fetchOpeningFavourites(); setHistories(history?.map(item => ({ id: item, steps: [] })) || []); }; useEffect(() => { loadTrends(); },[]); - const handleFavoriteChange = async (newStatus: boolean, openingId: number) => { try { if(!newStatus){ await deleteOpeningFavorite(openingId); displayNotification({ - title: 'Favorite Removed', - subTitle: `Opening Id ${openingId} removed from favorites`, + title: `Opening Id ${openingId} unfavourited`, type: 'success', dismissIn: 8000, onClose: () => {} @@ -49,50 +42,45 @@ const OpeningHistory: React.FC = () => { }; return ( - -
-
- {histories.map((history, index) => ( -
-
-
-
-
- handleFavoriteChange(newStatus, history.id)} - /> +
+
+ {histories && histories.length > 0 ? + histories.map((history, index) => ( +
+
+
+
+
+ handleFavoriteChange(newStatus, history.id)} + /> +
+ Opening ID +   + {history.id}
- {`Opening Id ${history.id}`}
- - {history.steps.map((step) => { - const status = statusClass(step.status); - return ( - - ); - })} -
+ )): +
+
- ))} + } +
-
-); + ); }; export default OpeningHistory; diff --git a/frontend/src/components/OpeningHistory/styles.scss b/frontend/src/components/OpeningHistory/styles.scss index 5195f2ce..fc714186 100644 --- a/frontend/src/components/OpeningHistory/styles.scss +++ b/frontend/src/components/OpeningHistory/styles.scss @@ -13,9 +13,6 @@ p.activity-history-header { min-width: 11rem; height: 100%; } -.activity-history-container{ - overflow-x: scroll; -} .activity-history-box > li.#{vars.$bcgov-prefix}--progress-step, .activity-history-box > li > button.#{vars.$bcgov-prefix}--progress-step-button { @@ -30,3 +27,11 @@ p.activity-history-header { .favorite-icon{ margin-right: 4px; } + +.trend-title{ + font-weight: bold; +} + +.debug { + border: 1px solid red; +} \ No newline at end of file diff --git a/frontend/src/components/OpeningMetricsTab/index.tsx b/frontend/src/components/OpeningMetricsTab/index.tsx index 7325af4b..435ccca8 100644 --- a/frontend/src/components/OpeningMetricsTab/index.tsx +++ b/frontend/src/components/OpeningMetricsTab/index.tsx @@ -3,7 +3,6 @@ import './styles.scss'; import SectionTitle from "../SectionTitle"; import BarChartGrouped from "../BarChartGrouped"; import ChartContainer from "../ChartContainer"; -import DoughnutChartView from "../DoughnutChartView"; import OpeningHistory from "../OpeningHistory"; import MyRecentActions from "../MyRecentActions"; @@ -42,15 +41,7 @@ const OpeningMetricsTab: React.FC = () => {
-
- - - -
-
+
= ({ try{ setOpeningFavorite(parseInt(openingId)); displayNotification({ - title: "Success", - subTitle: `Following "OpeningID ${openingId}"`, + title: `Opening Id ${openingId} favourited`, + subTitle: 'You can follow this opening ID on your dashboard', type: "success", buttonLabel: "Go to track openings", onClose: () => { diff --git a/frontend/src/services/OpeningFavoriteService.ts b/frontend/src/services/OpeningFavouriteService.ts similarity index 87% rename from frontend/src/services/OpeningFavoriteService.ts rename to frontend/src/services/OpeningFavouriteService.ts index 5c85d5e3..ea10ed14 100644 --- a/frontend/src/services/OpeningFavoriteService.ts +++ b/frontend/src/services/OpeningFavouriteService.ts @@ -5,7 +5,7 @@ import { env } from '../env'; const backendUrl = env.VITE_BACKEND_URL; /** - * Fetches the submission trends/favorites from the backend. + * Fetches the submission trends/favourites from the backend. * * This function sends a GET request to the backend API to retrieve the user favorite openings. * It includes an authorization token in the request headers. @@ -13,10 +13,10 @@ const backendUrl = env.VITE_BACKEND_URL; * @returns {Promise} A promise that resolves to an array of numbers representing the opening ids. * If the response data is not an array, it returns an empty array. */ -export const fetchOpeningTrends = async (): Promise =>{ +export const fetchOpeningFavourites = async (): Promise =>{ const authToken = getAuthIdToken(); const response = await axios.get( - `${backendUrl}/api/openings/favorites`, { + `${backendUrl}/api/openings/favourites`, { headers: { Authorization: `Bearer ${authToken}` } @@ -40,7 +40,7 @@ export const fetchOpeningTrends = async (): Promise =>{ export const setOpeningFavorite = async (openingId: number): Promise => { const authToken = getAuthIdToken(); const response = await axios.put( - `${backendUrl}/api/openings/favorites/${openingId}`, null, { + `${backendUrl}/api/openings/favourites/${openingId}`, null, { headers: { Authorization: `Bearer ${authToken}` } @@ -61,7 +61,7 @@ export const setOpeningFavorite = async (openingId: number): Promise => { export const deleteOpeningFavorite = async (openingId: number): Promise => { const authToken = getAuthIdToken(); const response = await axios.delete( - `${backendUrl}/api/openings/favorites/${openingId}`, { + `${backendUrl}/api/openings/favourites/${openingId}`, { headers: { Authorization: `Bearer ${authToken}` } diff --git a/frontend/src/services/OpeningService.ts b/frontend/src/services/OpeningService.ts index 3ae05474..7ec438f7 100644 --- a/frontend/src/services/OpeningService.ts +++ b/frontend/src/services/OpeningService.ts @@ -164,17 +164,7 @@ export function fetchRecentActions(): RecentAction[] { // Temporarily use the sample data for testing // const { data } = response; - const data: RecentAction[] = [ - { - "activityType": "Update", - "openingId": "1541297", - "statusCode": "APP", - "statusDescription": "Approved", - "lastUpdatedLabel": "1 minute ago", - "lastUpdated": "2024-05-16T19:59:21.635Z" - } - // Add more sample objects here if needed - ]; + const data: RecentAction[] = []; if (Array.isArray(data)) { // Transforming response data into a format consumable by the component diff --git a/frontend/src/types/NotificationType.ts b/frontend/src/types/NotificationType.ts index 0e0e3bd5..f526cb84 100644 --- a/frontend/src/types/NotificationType.ts +++ b/frontend/src/types/NotificationType.ts @@ -1,6 +1,6 @@ export interface NotificationContent { title: string; - subTitle: string; + subTitle?: string; buttonLabel?: string; dismissIn?: number; type: 'info' | 'error' | 'success' | 'warning' | 'info-square' | 'error-square' | 'warning-alt';