diff --git a/backend/src/main/java/com/greenfoxacademy/backend/controller/PetController.java b/backend/src/main/java/com/greenfoxacademy/backend/controller/PetController.java new file mode 100644 index 00000000..b8d817a9 --- /dev/null +++ b/backend/src/main/java/com/greenfoxacademy/backend/controller/PetController.java @@ -0,0 +1,26 @@ +package com.greenfoxacademy.backend.controller; + +import com.greenfoxacademy.backend.dtos.PetListResponseDto; +import com.greenfoxacademy.backend.services.pet.PetService; +import java.security.Principal; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * A REST controller that handles operations related to users' pets. + * + * @author Your Name + */ +@RequiredArgsConstructor +@RestController +public class PetController { + private final PetService petService; + + @GetMapping("/pets") + public ResponseEntity getPets(Principal owner) { + return ResponseEntity.status(HttpStatus.OK).body(petService.getOwnerPets(owner.getName())); + } +} diff --git a/backend/src/main/java/com/greenfoxacademy/backend/controller/VetController.java b/backend/src/main/java/com/greenfoxacademy/backend/controller/VetController.java index 8cb7e26c..b6854711 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/controller/VetController.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/controller/VetController.java @@ -5,6 +5,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** @@ -20,7 +21,8 @@ public class VetController { private final VetService vetService; @GetMapping("/search-vet") - public ResponseEntity searchVet() { - return ResponseEntity.status(HttpStatus.OK).body(vetService.getAll()); + public ResponseEntity searchVet(@RequestParam String word) { + + return ResponseEntity.status(HttpStatus.OK).body(vetService.getAll(word)); } } diff --git a/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetDetailsDto.java b/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetDetailsDto.java new file mode 100644 index 00000000..a7b5be97 --- /dev/null +++ b/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetDetailsDto.java @@ -0,0 +1,28 @@ +package com.greenfoxacademy.backend.dtos; + +import jakarta.validation.constraints.FutureOrPresent; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.PastOrPresent; +import java.util.Date; +import lombok.Data; + +/** + * A data transfer object for pet details. + * + * @author Your Name + */ +@Data +public class PetDetailsDto { + @NotBlank + String name; + @NotBlank + String breed; + @NotBlank + String sex; + @PastOrPresent(message = "The birth date must be in the past or present") + Date birthDate; + @PastOrPresent(message = "The last check-up date must be in the past or present") + Date lastCheckUp; + @FutureOrPresent(message = "The next check-up date must be in the future or present") + Date nextCheckUp; +} diff --git a/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetListResponseDto.java b/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetListResponseDto.java new file mode 100644 index 00000000..689b1368 --- /dev/null +++ b/backend/src/main/java/com/greenfoxacademy/backend/dtos/PetListResponseDto.java @@ -0,0 +1,12 @@ +package com.greenfoxacademy.backend.dtos; + +import java.util.List; + +/** + * A data transfer object for a list of pets. + * + */ +public record PetListResponseDto( + List pets +){ +} diff --git a/backend/src/main/java/com/greenfoxacademy/backend/models/ClinicAddress.java b/backend/src/main/java/com/greenfoxacademy/backend/models/ClinicAddress.java index 9cf5918c..c03b5f57 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/models/ClinicAddress.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/models/ClinicAddress.java @@ -1,8 +1,6 @@ package com.greenfoxacademy.backend.models; -import jakarta.persistence.Column; -import jakarta.persistence.Embeddable; -import jakarta.persistence.Id; +import jakarta.persistence.*; import lombok.Data; /** @@ -31,20 +29,18 @@ * @see jakarta.persistence.Embeddable */ -@Embeddable +@Entity +@Table(name = "_clinicAddress") @Data public class ClinicAddress { @Id private Long id; + @OneToOne + private ClinicDetails clinicDetails; private String city; @Column(length = 4) private int zip; private String street; private double longitude; private double latitude; - - public String addressToString() { - return zip + " " + city + " " + street; - } - } diff --git a/backend/src/main/java/com/greenfoxacademy/backend/models/ClinicDetails.java b/backend/src/main/java/com/greenfoxacademy/backend/models/ClinicDetails.java index 6a4dc147..b3a2c948 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/models/ClinicDetails.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/models/ClinicDetails.java @@ -1,9 +1,6 @@ package com.greenfoxacademy.backend.models; -import jakarta.persistence.Embeddable; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; +import jakarta.persistence.*; import lombok.Data; /** @@ -22,10 +19,15 @@ */ @Data -@Embeddable +@Entity +@Table(name = "_clinicDetails") public class ClinicDetails { - + @Id + private Long id; private String clinicName; + @OneToOne private ClinicAddress clinicAddress; + @OneToOne + private Vet vet; } diff --git a/backend/src/main/java/com/greenfoxacademy/backend/models/Vet.java b/backend/src/main/java/com/greenfoxacademy/backend/models/Vet.java index d99bd34c..08f807ce 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/models/Vet.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/models/Vet.java @@ -1,8 +1,6 @@ package com.greenfoxacademy.backend.models; -import jakarta.persistence.Entity; -import jakarta.persistence.Table; -import jakarta.persistence.Transient; +import jakarta.persistence.*; import java.util.Collection; import java.util.List; import lombok.AllArgsConstructor; @@ -37,6 +35,7 @@ @Entity @Table(name = "_vet") public class Vet extends User { + @OneToOne private ClinicDetails clinicDetails; @Override diff --git a/backend/src/main/java/com/greenfoxacademy/backend/repositories/ClinicAddressRepository.java b/backend/src/main/java/com/greenfoxacademy/backend/repositories/ClinicAddressRepository.java new file mode 100644 index 00000000..399a35bd --- /dev/null +++ b/backend/src/main/java/com/greenfoxacademy/backend/repositories/ClinicAddressRepository.java @@ -0,0 +1,23 @@ +package com.greenfoxacademy.backend.repositories; + +import com.greenfoxacademy.backend.models.ClinicAddress; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * ClinicAddressRepository provides database access methods for ClinicAddress entities. + * It extends JpaRepository to perform CRUD operations. + */ +public interface ClinicAddressRepository extends JpaRepository { + + /** + * Retrieves a list of clinic addresses based on partial matches for zip code, city, or street. + * + * @param zip the partial or full zip code of the clinic address. + * @param city the partial or full city name of the clinic address. + * @param street the partial or full street name of the clinic address. + * @return a list of clinic addresses matching the given criteria. + */ + List findAllByZipContainingOrCityContainingOrStreetContaining( + int zip, String city, String street); +} diff --git a/backend/src/main/java/com/greenfoxacademy/backend/repositories/ClinicDetailsRepository.java b/backend/src/main/java/com/greenfoxacademy/backend/repositories/ClinicDetailsRepository.java new file mode 100644 index 00000000..237f5688 --- /dev/null +++ b/backend/src/main/java/com/greenfoxacademy/backend/repositories/ClinicDetailsRepository.java @@ -0,0 +1,21 @@ +package com.greenfoxacademy.backend.repositories; + +import com.greenfoxacademy.backend.models.ClinicDetails; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + + +/** + * ClinicDetailsRepository provides database access methods for ClinicDetails entities. + * It extends JpaRepository to perform CRUD operations. + */ +public interface ClinicDetailsRepository extends JpaRepository { + + /** + * Retrieves a list of clinic details where the clinic name contains the specified word. + * + * @param word a partial or full word to search within clinic names. + * @return a list of clinic details matching the specified word in their names. + */ + List findAllByClinicNameContaining(String word); +} diff --git a/backend/src/main/java/com/greenfoxacademy/backend/repositories/VetRepository.java b/backend/src/main/java/com/greenfoxacademy/backend/repositories/VetRepository.java index 2fa0a7d7..08965830 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/repositories/VetRepository.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/repositories/VetRepository.java @@ -1,7 +1,30 @@ package com.greenfoxacademy.backend.repositories; import com.greenfoxacademy.backend.models.Vet; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; -public interface VetRepository extends JpaRepository { +/** + * VetRepository provides database access methods for Vet entities. + * It extends JpaRepository to perform CRUD operations. + */ +public interface VetRepository extends JpaRepository { + + /** + * Retrieves a list of vets based on the first or last name. + * + * @param firstName the first name of the vet. + * @param lastName the last name of the vet. + * @return a list of vets matching the given first or last name. + */ + List findAllByFirstNameOrLastName(String firstName, String lastName); + + /** + * Retrieves a list of vets based on the clinic name. + * + * @param clinicName the name of the clinic. + * @return a list of vets working at the clinic with the given name. + */ + List findAllByClinicDetailsClinicName(String clinicName); + } diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/pet/PetService.java b/backend/src/main/java/com/greenfoxacademy/backend/services/pet/PetService.java new file mode 100644 index 00000000..39d4c761 --- /dev/null +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/pet/PetService.java @@ -0,0 +1,16 @@ +package com.greenfoxacademy.backend.services.pet; + +import com.greenfoxacademy.backend.dtos.PetListResponseDto; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Service; + +/** + * Retrieves the pets of the specified owner. + * + * @param name The name of the owner. + * @return A response containing the owner's pets. + */ + +public interface PetService { + PetListResponseDto getOwnerPets(String name); +} diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/pet/PetServiceImpl.java b/backend/src/main/java/com/greenfoxacademy/backend/services/pet/PetServiceImpl.java new file mode 100644 index 00000000..61265fd4 --- /dev/null +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/pet/PetServiceImpl.java @@ -0,0 +1,43 @@ +package com.greenfoxacademy.backend.services.pet; + +import com.greenfoxacademy.backend.dtos.PetDetailsDto; +import com.greenfoxacademy.backend.dtos.PetListResponseDto; +import com.greenfoxacademy.backend.models.Pet; +import com.greenfoxacademy.backend.repositories.PetRepository; +import com.greenfoxacademy.backend.services.user.owner.OwnerService; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.modelmapper.ModelMapper; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +/** + * Retrieves a list of pets owned by the user with the specified email. + */ +@Service +@RequiredArgsConstructor +public class PetServiceImpl implements PetService { + private final PetRepository petRepository; + private final OwnerService ownerService; + private final ModelMapper modelMapper = new ModelMapper(); + + /** + * Retrieves a list of pets owned by the user with the specified email. + * + * @param email the email of the pet owner + * @return a {@link PetListResponseDto} containing the list of pets + * @throws UsernameNotFoundException if the user with the specified email is not found + */ + @Override + public PetListResponseDto getOwnerPets(String email) { + List petList = petRepository + .findAllByOwnerId(ownerService.findByEmail(email).getId()); + + List petDtoList = petList.stream() + .map(pet -> modelMapper.map(pet, PetDetailsDto.class)) + .collect(Collectors.toList()); + + return new PetListResponseDto(petDtoList); + } +} diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java b/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java index db491430..9e06c86d 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/user/UserServiceImpl.java @@ -15,9 +15,7 @@ import com.greenfoxacademy.backend.services.auth.AuthService; import com.greenfoxacademy.backend.services.mail.EmailService; import jakarta.transaction.Transactional; - import java.util.UUID; - import lombok.RequiredArgsConstructor; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/user/vet/VetService.java b/backend/src/main/java/com/greenfoxacademy/backend/services/user/vet/VetService.java index fe5022b9..93762f41 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/services/user/vet/VetService.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/user/vet/VetService.java @@ -1,7 +1,6 @@ package com.greenfoxacademy.backend.services.user.vet; import com.greenfoxacademy.backend.dtos.VetListResponseDto; -import org.springframework.stereotype.Service; /** * VetService defines the business logic for handling veterinarian-related data and @@ -9,8 +8,6 @@ * This interface provides methods for retrieving veterinarian data. */ -@Service public interface VetService { - - VetListResponseDto getAll(); + VetListResponseDto getAll(String word); } diff --git a/backend/src/main/java/com/greenfoxacademy/backend/services/user/vet/VetServiceImpl.java b/backend/src/main/java/com/greenfoxacademy/backend/services/user/vet/VetServiceImpl.java index e692f2e0..d41ad69b 100644 --- a/backend/src/main/java/com/greenfoxacademy/backend/services/user/vet/VetServiceImpl.java +++ b/backend/src/main/java/com/greenfoxacademy/backend/services/user/vet/VetServiceImpl.java @@ -2,28 +2,44 @@ import com.greenfoxacademy.backend.dtos.VetDetailsDto; import com.greenfoxacademy.backend.dtos.VetListResponseDto; +import com.greenfoxacademy.backend.models.ClinicAddress; +import com.greenfoxacademy.backend.models.ClinicDetails; import com.greenfoxacademy.backend.models.Vet; +import com.greenfoxacademy.backend.repositories.ClinicAddressRepository; +import com.greenfoxacademy.backend.repositories.ClinicDetailsRepository; import com.greenfoxacademy.backend.repositories.VetRepository; -import lombok.RequiredArgsConstructor; -import org.modelmapper.ModelMapper; - import java.util.List; import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.modelmapper.ModelMapper; +import org.springframework.stereotype.Service; /** * VetServiceImpl is the implementation of the VetService interface. * It provides functionality for retrieving veterinarian data. */ - +@Service @RequiredArgsConstructor public class VetServiceImpl implements VetService { private final VetRepository vetRepository; + private final ClinicAddressRepository clinicAddressRepository; + private final ClinicDetailsRepository clinicDetailsRepository; private final ModelMapper modelMapper; @Override - public VetListResponseDto getAll() { - List vetList = vetRepository.findAll(); + public VetListResponseDto getAll(String word) { + List vetList = vetRepository.findAllByFirstNameOrLastName(word, word); + + List clinicAddressList = clinicAddressRepository + .findAllByZipContainingOrCityContainingOrStreetContaining( + Integer.parseInt(word), word, word); + + vetList.addAll( clinicAddressList.stream() + .map(a -> a.getClinicDetails().getVet()).toList()); + + vetList.addAll(clinicDetailsRepository.findAllByClinicNameContaining(word) + .stream().map(ClinicDetails::getVet).toList()); List vetDtoList = vetList.stream() .map(vet -> modelMapper.map(vet, VetDetailsDto.class)) diff --git a/backend/src/test/java/com/greenfoxacademy/backend/controller/UserControllerTest.java b/backend/src/test/java/com/greenfoxacademy/backend/controller/UserControllerTest.java index 85ec8ce7..86e856fa 100644 --- a/backend/src/test/java/com/greenfoxacademy/backend/controller/UserControllerTest.java +++ b/backend/src/test/java/com/greenfoxacademy/backend/controller/UserControllerTest.java @@ -19,6 +19,8 @@ import com.greenfoxacademy.backend.repositories.OwnerRepository; import com.greenfoxacademy.backend.services.mail.EmailService; import java.util.Optional; + +import com.greenfoxacademy.backend.services.user.vet.VetService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -51,6 +53,9 @@ class UserControllerTest { @MockBean private EmailService emailService; + @MockBean + private VetService vetService; + /** * The PasswordEncoder is used to encode the password before saving it to the database. * The password is encoded using the BCrypt algorithm. diff --git a/backend/src/test/java/com/greenfoxacademy/backend/services/mail/EmailServiceImplTest.java b/backend/src/test/java/com/greenfoxacademy/backend/services/mail/EmailServiceImplTest.java index 6a154b1c..7c0c3398 100644 --- a/backend/src/test/java/com/greenfoxacademy/backend/services/mail/EmailServiceImplTest.java +++ b/backend/src/test/java/com/greenfoxacademy/backend/services/mail/EmailServiceImplTest.java @@ -6,9 +6,12 @@ import com.greenfoxacademy.backend.config.EmailConfiguration; import com.greenfoxacademy.backend.services.user.UserService; +import com.greenfoxacademy.backend.services.user.vet.VetService; import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; + import java.util.UUID; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -19,7 +22,7 @@ /** * Test class for {@link EmailServiceImpl}. - * This class is responsible for testing the email sending functionality of + * This class is responsible for testing the email sending functionality of * the EmailServiceImpl class. * It uses Mockito for mocking dependencies and verifying interactions. */ @@ -36,6 +39,9 @@ class EmailServiceImplTest { @MockBean private UserService userService; + @MockBean + private VetService vetService; + private EmailServiceImpl emailService; @BeforeEach diff --git a/backend/src/test/java/com/greenfoxacademy/backend/services/pet/PetServiceImplTest.java b/backend/src/test/java/com/greenfoxacademy/backend/services/pet/PetServiceImplTest.java new file mode 100644 index 00000000..c6531187 --- /dev/null +++ b/backend/src/test/java/com/greenfoxacademy/backend/services/pet/PetServiceImplTest.java @@ -0,0 +1,98 @@ +package com.greenfoxacademy.backend.services.pet; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.greenfoxacademy.backend.models.Owner; +import com.greenfoxacademy.backend.models.Pet; +import com.greenfoxacademy.backend.repositories.OwnerRepository; +import com.greenfoxacademy.backend.repositories.PetRepository; +import jakarta.transaction.Transactional; +import java.util.Arrays; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; + +/** + * Integration test class for PetServiceImpl. + * This class uses the following annotations: + * - {@link SpringBootTest}: Indicates that the class is a Spring Boot test that + * will start the full application context. + * - {@link AutoConfigureMockMvc}: Automatically configures MockMvc for testing web layer. + * - {@link Transactional}: Ensures that each test method runs within a transaction + * that is rolled back after the test completes. + */ +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +public class PetServiceImplTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private PetRepository petRepository; + + @Autowired + private OwnerRepository ownerRepository; + + /** + * Sets up mock data before each test. + * This method is executed before each test method in the current test class. + * It initializes mock data for owners and pets and saves them to the repository. + */ + @BeforeEach + public void setUp() { + // Set up mock data + Owner userWithPets = new Owner(); + userWithPets.setEmail("userWithPets@example.com"); + userWithPets.setPassword("Password"); + Owner userWithNoPets = new Owner(); + userWithNoPets.setEmail("userWithNoPets@example.com"); + userWithNoPets.setPassword("Password"); + + ownerRepository.saveAll(Arrays.asList(userWithPets, userWithNoPets)); + + Pet pet1 = new Pet(); + pet1.setName("Morzsi"); + pet1.setOwner(userWithPets); + Pet pet2 = new Pet(); + pet2.setName("Rusty"); + pet2.setOwner(userWithPets); + + petRepository.saveAll(Arrays.asList(pet1, pet2)); + } + + @Test + @WithMockUser(username = "userWithPets@example.com") + public void testCorrectEmailWithExistingPets() throws Exception { + mockMvc.perform(get("/pets") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.pets[0].name").value("Morzsi")) + .andExpect(jsonPath("$.pets[1].name").value("Rusty")); + } + + @Test + @WithMockUser(username = "userWithNoPets@example.com") + public void testCorrectEmailWithNoExistingPets() throws Exception { + mockMvc.perform(get("/pets") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.pets").isEmpty()); + } + + @Test + @WithMockUser(username = "nonExistingUser@example.com") + public void testIncorrectEmail() throws Exception { + mockMvc.perform(get("/pets") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().is4xxClientError()); + } +} diff --git a/frontend/src/httpClient.ts b/frontend/src/httpClient.ts index 0b6aed39..b8229481 100644 --- a/frontend/src/httpClient.ts +++ b/frontend/src/httpClient.ts @@ -71,18 +71,21 @@ const logout = () => { }; export type VetDetails = { - firstName : string - lastName : string - email : string - clinicAddress : string + firstName: string; + lastName: string; + email: string; + clinicAddress: string; }; type VetListResponse = { - vets: string[]; + vets: VetDetails[]; }; -const vetList = () => { - return httpClient.get("/search-vets"); +const vetList = async (request: string) => { + const response = await httpClient.get( + `/search-vet/?word=${request}`, + ); + return response.data; }; export { login, register, logout, updateProfile, deleteProfile, vetList }; diff --git a/frontend/src/pages/PetList.test.tsx b/frontend/src/pages/PetList.test.tsx new file mode 100644 index 00000000..d1a4dd63 --- /dev/null +++ b/frontend/src/pages/PetList.test.tsx @@ -0,0 +1,53 @@ +import { render, screen, waitFor } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { petList } from "../httpClient"; +import PetList from "./PetList"; + +// Mock the petList function +vi.mock("../httpClient", () => ({ + petList: vi.fn(), +})); + +const mockPets = [ + { + name: "Buddy", + breed: "Golden Retriever", + sex: "Male", + birthDate: new Date("2018-01-01"), + lastCheckUp: new Date("2023-01-01"), + nextCheckUp: new Date("2024-01-01"), + }, + { + name: "Mittens", + breed: "Tabby", + sex: "Female", + birthDate: new Date("2019-05-15"), + lastCheckUp: new Date("2023-05-15"), + nextCheckUp: new Date("2024-05-15"), + }, +]; + +describe("PetList Component", () => { + it("displays pets in a table when data is available", async () => { + (petList as vi.mock).mockResolvedValueOnce({ data: { pets: mockPets } }); + + render(); + + await waitFor(() => { + expect(screen.getByText("Buddy")).toBeInTheDocument(); + expect(screen.getByText("Golden Retriever")).toBeInTheDocument(); + expect(screen.getByText("Mittens")).toBeInTheDocument(); + expect(screen.getByText("Tabby")).toBeInTheDocument(); + }); + }); + + it("displays a message when no pets are registered", async () => { + (petList as vi.mock).mockResolvedValueOnce({ data: { pets: [] } }); + + render(); + + await waitFor(() => { + expect(screen.getByText("No pets registered.")).toBeInTheDocument(); + }); + }); +}); diff --git a/frontend/src/pages/PetList.tsx b/frontend/src/pages/PetList.tsx new file mode 100644 index 00000000..8b5a6994 --- /dev/null +++ b/frontend/src/pages/PetList.tsx @@ -0,0 +1,105 @@ +import { useEffect, useState } from "react"; +import { type PetDetails, petList } from "../httpClient.ts"; + +const PetList = () => { + const [pets, setPets] = useState([]); + + useEffect(() => { + petList() + .then((petsResponse) => setPets(petsResponse.data.pets)) + .catch((error) => { + console.error("Error fetching pets:", error); + }); + }, []); + + return ( + <> +

Please choose from your registered Pets!

+ {pets.length > 0 ? ( + + + + + + + + + + + {pets.map((pet, index) => ( + + + + + + + ))} + +
+ Name + + Breed + + Sex + + BirthDate +
+ {pet.name} + + {pet.breed} + + {pet.sex} + + {new Date(pet.birthDate).toDateString()} +
+ ) : ( +

No pets registered.

+ )} + + ); +}; + +export default PetList; diff --git a/frontend/src/pages/Search.test.tsx b/frontend/src/pages/Search.test.tsx new file mode 100644 index 00000000..0068faba --- /dev/null +++ b/frontend/src/pages/Search.test.tsx @@ -0,0 +1,20 @@ +import { render, screen } from "@testing-library/react"; +import { BrowserRouter as Router } from "react-router-dom"; +import { describe, expect, test } from "vitest"; +import { Search } from "./Search"; + +describe("Search component", () => { + test("has a heading 'Search'", () => { + // Arrange + render( + + + , + ); + + // Act + + // Assert + expect(screen.getByRole("heading")).toHaveTextContent("Search"); + }); +}); diff --git a/frontend/src/pages/Search.tsx b/frontend/src/pages/Search.tsx index cc5e053d..31a75c20 100644 --- a/frontend/src/pages/Search.tsx +++ b/frontend/src/pages/Search.tsx @@ -1,69 +1,36 @@ import { Button, FormControl, FormLabel, Select } from "@chakra-ui/react"; import { useEffect, useState } from "react"; -import { VetDetails, vetList} from "../httpClient" +import { type VetDetails, vetList } from "../httpClient"; function Search() { + const [searchWord, setSearchWord] = useState(""); - const [vets, setVets] = useState([]); - useEffect(() => { - vetList() - .then((vetsResponse) => setVets(vetsResponse.data.vets)) + const [vets, setVets] = useState([]); + useEffect(() => { + vetList(searchWord) + .then((vetsResponse) => setVets(vetsResponse.vets)) .catch((error) => { console.error("Error fetching vets:", error); }); - }, []); - // Commented part is for later ticket SCRUM 86 (find closest vet) - - // function geoFindMe() { - // const status = document.querySelector("#status") as HTMLElement; - // const mapLink = document.querySelector("#map-link") as HTMLAnchorElement; - - // mapLink.href = ""; - // mapLink.textContent = ""; - - // function success(position: GeolocationPosition) { - // const latitude = position.coords.latitude; - // const longitude = position.coords.longitude; - - // status.textContent = ""; - // mapLink.href = `https://www.openstreetmap.org/#map=18/${latitude}/${longitude}`; - // mapLink.textContent = `Latitude: ${latitude} °, Longitude: ${longitude} °`; - // } - - // function error() { - // status.textContent = "Unable to retrieve your location"; - // } - - // if (!navigator.geolocation) { - // status.textContent = "Geolocation is not supported by your browser"; - // } else { - // status.textContent = "Locating…"; - // navigator.geolocation.getCurrentPosition(success, error); - // } - // } - - // document.querySelector("#find-me")?.addEventListener("click", geoFindMe); - - return ( - <> - - Vet - - - - { - /*
- -
-

- -

*/} - - ); + }, [searchWord]); + + return ( + <> +

Search

+ + Vet + setSearchWord(event.target.value)} /> + + + + + ); } export { Search }; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..5c457c7f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "Pet-Clinic-GilmoreDevs", + "lockfileVersion": 3, + "requires": true, + "packages": {} +}