-
diff --git a/src/pages/PortfolioPage.jsx b/src/pages/PortfolioPage.jsx
index aa9a1f6..ee01b44 100644
--- a/src/pages/PortfolioPage.jsx
+++ b/src/pages/PortfolioPage.jsx
@@ -7,46 +7,44 @@ import {
CarouselNext,
} from "@/components/ui/Carousel";
import { CTASection } from "@/components/common/CTASection";
-import Lightbox from "@/components/ui/Lightbox";
import SocialLinks from "@/components/common/SocialLinks";
-import { SEO } from "@/components/common/SEO";
+import { Seo } from "@/components/common/Seo";
import { portfolioImages } from "@/data/portfolioImages";
+import { useFullscreenGallery } from "@/hooks/useFullscreenGallery";
+import FullscreenViewer from "@/components/ui/FullscreenViewer";
const PortfolioPage = () => {
const [api, setApi] = useState(null);
const [current, setCurrent] = useState(0);
- const [lightboxIndex, setLightboxIndex] = useState(null);
- const isLightboxOpen = lightboxIndex !== null;
+ const gallery = useFullscreenGallery(portfolioImages, api);
- // Keyboard controls for carousel
+ // Carousel keyboard controls (when not fullscreen)
useEffect(() => {
- if (isLightboxOpen || !api) return;
+ if (gallery.isFullscreen || !api) return;
const handleKeyDown = (e) => {
if (e.key === "ArrowLeft") api.scrollPrev();
else if (e.key === "ArrowRight") api.scrollNext();
- else if (e.key === "Enter") setLightboxIndex(current);
+ else if (e.key === "Enter") gallery.open(current);
};
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
- }, [isLightboxOpen, api, current]);
+ }, [gallery.isFullscreen, api, current, gallery]);
// Sync carousel state
useEffect(() => {
if (!api) return;
-
const onSelect = () => setCurrent(api.selectedScrollSnap());
onSelect();
api.on("select", onSelect);
-
return () => api.off("select", onSelect);
}, [api]);
return (
-
{
);
};
diff --git a/src/pages/PortfolioPage.test.jsx b/src/pages/PortfolioPage.test.jsx
index 87ab205..5b6640f 100644
--- a/src/pages/PortfolioPage.test.jsx
+++ b/src/pages/PortfolioPage.test.jsx
@@ -1,6 +1,6 @@
-import { render, screen } from "@testing-library/react";
+import { render, screen, fireEvent, act } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
-import { describe, it, expect } from "vitest";
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import PortfolioPage from "./PortfolioPage";
const renderWithProviders = (component) => {
@@ -8,6 +8,22 @@ const renderWithProviders = (component) => {
};
describe("PortfolioPage", () => {
+ let originalRequestFullscreen;
+ let originalExitFullscreen;
+
+ beforeEach(() => {
+ originalRequestFullscreen = Element.prototype.requestFullscreen;
+ originalExitFullscreen = document.exitFullscreen;
+
+ Element.prototype.requestFullscreen = vi.fn().mockResolvedValue();
+ document.exitFullscreen = vi.fn().mockResolvedValue();
+ });
+
+ afterEach(() => {
+ Element.prototype.requestFullscreen = originalRequestFullscreen;
+ document.exitFullscreen = originalExitFullscreen;
+ });
+
it("renders the page title and description", () => {
renderWithProviders(
);
@@ -39,4 +55,45 @@ describe("PortfolioPage", () => {
"https://www.facebook.com/chrisert.pt/"
);
});
+
+ it("renders carousel navigation buttons", () => {
+ renderWithProviders(
);
+
+ expect(screen.getByRole("button", { name: /previous slide/i })).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: /next slide/i })).toBeInTheDocument();
+ });
+
+ it("renders dot indicators for navigation", () => {
+ renderWithProviders(
);
+
+ const dotButtons = screen.getAllByRole("button", { name: /ir para projeto/i });
+ expect(dotButtons.length).toBeGreaterThan(0);
+ });
+
+ it("renders CTA section", () => {
+ renderWithProviders(
);
+
+ expect(screen.getByText(/gostou do que viu/i)).toBeInTheDocument();
+ expect(screen.getByRole("link", { name: /pedir orçamento/i })).toBeInTheDocument();
+ });
+
+ it("renders fullscreen viewer container", () => {
+ renderWithProviders(
);
+
+ expect(
+ screen.getByLabelText("Visualizador de imagens em ecrã inteiro")
+ ).toBeInTheDocument();
+ });
+
+ it("opens fullscreen when clicking on image", async () => {
+ renderWithProviders(
);
+
+ const imageButtons = screen.getAllByRole("button", { name: /ver projeto/i });
+
+ await act(async () => {
+ fireEvent.click(imageButtons[0]);
+ });
+
+ expect(Element.prototype.requestFullscreen).toHaveBeenCalled();
+ });
});
diff --git a/src/pages/ServicesPage.jsx b/src/pages/ServicesPage.jsx
index bda7003..6860c28 100644
--- a/src/pages/ServicesPage.jsx
+++ b/src/pages/ServicesPage.jsx
@@ -1,11 +1,11 @@
import { CTASection } from "@/components/common/CTASection";
-import { SEO } from "@/components/common/SEO";
+import { Seo } from "@/components/common/Seo";
import { services, eticsBenefits, processSteps } from "@/data/servicesData";
const ServicesPage = () => {
return (
-