From 6b4f01b46c33d036b92c586cba39e19b4fbf490f Mon Sep 17 00:00:00 2001 From: Anuj Rajak Date: Mon, 8 Sep 2025 13:13:34 +0530 Subject: [PATCH] Image component --- .../src/components/ImageComponent.tsx | 61 ++++++ .../src/constants/componentsMap.ts | 2 + .../test/components/ImageComponent.test.tsx | 203 ++++++++++++++++++ 3 files changed, 266 insertions(+) create mode 100644 libs_js/next_gen_ui_react/src/components/ImageComponent.tsx create mode 100644 libs_js/next_gen_ui_react/src/test/components/ImageComponent.test.tsx diff --git a/libs_js/next_gen_ui_react/src/components/ImageComponent.tsx b/libs_js/next_gen_ui_react/src/components/ImageComponent.tsx new file mode 100644 index 00000000..36a63490 --- /dev/null +++ b/libs_js/next_gen_ui_react/src/components/ImageComponent.tsx @@ -0,0 +1,61 @@ +import { Card, CardBody, CardHeader, CardTitle } from "@patternfly/react-core"; +import React from "react"; + +interface ImageComponentProps { + component: "image"; + id: string; + image?: string | null; + title: string; + className?: string; +} + +const ImageComponent: React.FC = ({ + id, + image, + title, + className, +}) => { + return ( + + + {title} + + + {image ? ( + {title} { + const target = e.target as HTMLImageElement; + target.style.display = "none"; + target.parentElement!.innerHTML = `

Image failed to load

`; + }} + /> + ) : ( +
+ No image provided +
+ )} +
+
+ ); +}; + +export default ImageComponent; diff --git a/libs_js/next_gen_ui_react/src/constants/componentsMap.ts b/libs_js/next_gen_ui_react/src/constants/componentsMap.ts index 66242ba2..5b2759cf 100644 --- a/libs_js/next_gen_ui_react/src/constants/componentsMap.ts +++ b/libs_js/next_gen_ui_react/src/constants/componentsMap.ts @@ -10,6 +10,7 @@ import { import AccordionWrapper from "../components/AccordionWrapper"; import { CodeBlockWrapper } from "../components/CodeBloackWrapper"; import CustomLink from "../components/CustomLink"; +import ImageComponent from "../components/ImageComponent"; import ListWrapper from "../components/ListWrapper"; import Message from "../components/Message"; import OneCardWrapper from "../components/OneCardWrapper"; @@ -27,6 +28,7 @@ export const componentsMap = { codeblock: CodeBlockWrapper, text: Text, button: Button, + image: ImageComponent, list: ListWrapper, link: CustomLink, message: Message, diff --git a/libs_js/next_gen_ui_react/src/test/components/ImageComponent.test.tsx b/libs_js/next_gen_ui_react/src/test/components/ImageComponent.test.tsx new file mode 100644 index 00000000..15ce0a60 --- /dev/null +++ b/libs_js/next_gen_ui_react/src/test/components/ImageComponent.test.tsx @@ -0,0 +1,203 @@ +import { render, screen, fireEvent } from "@testing-library/react"; +import "@testing-library/jest-dom"; + +import ImageComponent from "../../components/ImageComponent"; + +const mockImageData = { + component: "image" as const, + id: "test-id", + image: + "https://image.tmdb.org/t/p/w440_and_h660_face/uXDfjJbdP4ijW5hWSBrPrlKpxab.jpg", + title: "Toy Story Poster", +}; + +describe("ImageComponent", () => { + const defaultProps = { + component: "image" as const, + id: mockImageData.id, + image: mockImageData.image, + title: mockImageData.title, + }; + + it("renders with required props", () => { + render(); + + expect(screen.getByText("Toy Story Poster")).toBeInTheDocument(); + expect( + screen.getByRole("img", { name: "Toy Story Poster" }) + ).toBeInTheDocument(); + expect(screen.getByRole("img")).toHaveAttribute("src", mockImageData.image); + }); + + it("renders image with correct attributes", () => { + render(); + + const image = screen.getByRole("img"); + expect(image).toHaveAttribute("src", mockImageData.image); + expect(image).toHaveAttribute("alt", "Toy Story Poster"); + expect(image).toHaveStyle("width: 100%"); + expect(image).toHaveStyle("height: auto"); + expect(image).toHaveStyle("object-fit: cover"); + expect(image).toHaveStyle( + "border-radius: var(--pf-global--BorderRadius--sm)" + ); + }); + + it("applies correct card styling", () => { + render(); + + const card = screen.getByRole("img").closest('[style*="max-width"]'); + expect(card).toHaveStyle("max-width: 400px"); + }); + + it("applies custom id and className", () => { + const customId = "custom-test-id"; + const customClassName = "custom-class"; + + render( + + ); + + const card = screen.getByRole("img").closest('[id="custom-test-id"]'); + expect(card).toBeInTheDocument(); + expect(card).toHaveClass(customClassName); + }); + + it("renders placeholder when image is null", () => { + render(); + + expect(screen.queryByRole("img")).not.toBeInTheDocument(); + expect(screen.getByText("No image provided")).toBeInTheDocument(); + + const placeholder = screen.getByText("No image provided"); + expect(placeholder).toHaveStyle("width: 100%"); + expect(placeholder).toHaveStyle("height: 200px"); + expect(placeholder).toHaveStyle( + "background-color: var(--pf-global--Color--200)" + ); + expect(placeholder).toHaveStyle( + "border-radius: var(--pf-global--BorderRadius--sm)" + ); + expect(placeholder).toHaveStyle("display: flex"); + expect(placeholder).toHaveStyle("align-items: center"); + expect(placeholder).toHaveStyle("justify-content: center"); + expect(placeholder).toHaveStyle("color: var(--pf-global--Color--300)"); + }); + + it("renders placeholder when image is undefined", () => { + const { image: _image, ...propsWithoutImage } = defaultProps; + void _image; // Acknowledge unused variable + + render(); + + expect(screen.queryByRole("img")).not.toBeInTheDocument(); + expect(screen.getByText("No image provided")).toBeInTheDocument(); + }); + + it("renders placeholder when image is empty string", () => { + render(); + + expect(screen.queryByRole("img")).not.toBeInTheDocument(); + expect(screen.getByText("No image provided")).toBeInTheDocument(); + }); + + it("handles image load error", () => { + render(); + + const image = screen.getByRole("img"); + expect(image).toBeInTheDocument(); + + // Simulate image load error + fireEvent.error(image); + + // After error, image should be hidden and error message should appear + expect(image).toHaveStyle("display: none"); + + // Check that error message is displayed + const errorMessage = screen.getByText("Image failed to load"); + expect(errorMessage).toBeInTheDocument(); + expect(errorMessage).toHaveStyle("color: var(--pf-global--Color--200)"); + expect(errorMessage).toHaveStyle("text-align: center"); + expect(errorMessage).toHaveStyle("padding: 20px"); + }); + + it("renders with different image URLs", () => { + const testImageUrl = "https://example.com/test-image.jpg"; + + render(); + + const image = screen.getByRole("img"); + expect(image).toHaveAttribute("src", testImageUrl); + expect(image).toHaveAttribute("alt", "Toy Story Poster"); + }); + + it("renders with different titles", () => { + const testTitle = "Different Movie Title"; + + render(); + + expect(screen.getByText(testTitle)).toBeInTheDocument(); + + const image = screen.getByRole("img"); + expect(image).toHaveAttribute("alt", testTitle); + }); + + it("renders with different IDs", () => { + const testId = "different-test-id"; + + render(); + + const card = screen.getByRole("img").closest('[id="different-test-id"]'); + expect(card).toBeInTheDocument(); + }); + + it("handles very long titles", () => { + const longTitle = + "This is a very long title that might wrap to multiple lines and should still be displayed correctly"; + + render(); + + expect(screen.getByText(longTitle)).toBeInTheDocument(); + }); + + it("handles special characters in title", () => { + const specialTitle = "Movie Title with Special Characters: @#$%^&*()"; + + render(); + + expect(screen.getByText(specialTitle)).toBeInTheDocument(); + + const image = screen.getByRole("img"); + expect(image).toHaveAttribute("alt", specialTitle); + }); + + it("applies consistent styling across different scenarios", () => { + const { rerender } = render(); + + // Test with image + const image = screen.getByRole("img"); + expect(image).toHaveStyle("width: 100%"); + expect(image).toHaveStyle("height: auto"); + expect(image).toHaveStyle("object-fit: cover"); + + // Test without image + rerender(); + const placeholder = screen.getByText("No image provided"); + expect(placeholder).toHaveStyle("width: 100%"); + expect(placeholder).toHaveStyle("height: 200px"); + }); + + it("maintains accessibility with proper alt text", () => { + render(); + + const image = screen.getByRole("img"); + expect(image).toHaveAttribute("alt", mockImageData.title); + + // Alt text should match the title + expect(image.getAttribute("alt")).toBe("Toy Story Poster"); + }); +});