feat: add animated components for enhanced UI/UX
- Introduced DrawLine component for drawing animations. - Added LazyRevealImage component for lazy loading images with reveal animations. - Implemented TextDiffuseReveal for animated text reveals with staggered effects. - Created TopProgressBar for a dynamic loading indicator. - Added PointCloud mock for testing animations. - Developed useReducedMotion hook to handle user preferences for reduced motion. - Centralized animation configurations in config.ts for easier management. - Defined animation variants in variants.ts for consistent animation effects. - Established AnimationProvider to supply global animation settings. - Created tests for AnimationProvider and useReducedMotion hook. - Added SVG assets for branding and UI elements. - Documented frontend review plan and hero circuit board animation plan.
This commit is contained in:
1 parent
0ab6930f92
commit
08dd046887
76 files changed
+21682
-1197
No files matched your search
@@ -0,0 +1,323 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import Footer from "./Footer";
|
||||
|
||||
describe("Footer Component - Contact Information", () => {
|
||||
it("should render contact section heading", () => {
|
||||
render(<Footer />);
|
||||
|
||||
const contactHeading = screen.getByText("Contato direto");
|
||||
expect(contactHeading).toBeInTheDocument();
|
||||
expect(contactHeading.tagName).toBe("H3");
|
||||
});
|
||||
|
||||
it("should render email address", () => {
|
||||
render(<Footer />);
|
||||
|
||||
const emailLink = screen.getByText("[email protected]");
|
||||
expect(emailLink).toBeInTheDocument();
|
||||
expect(emailLink.tagName).toBe("A");
|
||||
});
|
||||
|
||||
it("should have mailto link for email", () => {
|
||||
render(<Footer />);
|
||||
|
||||
const emailLink = screen.getByText("[email protected]");
|
||||
expect(emailLink).toHaveAttribute("href", "mailto:[email protected]");
|
||||
});
|
||||
|
||||
it("should apply correct styling classes to email link", () => {
|
||||
render(<Footer />);
|
||||
|
||||
const emailLink = screen.getByText("[email protected]");
|
||||
const classes = emailLink.className;
|
||||
|
||||
expect(classes).toContain("text-[12px]");
|
||||
expect(classes).toContain("hover:text-[var(--slash)]");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Footer Component - Social Links", () => {
|
||||
it("should render social links section heading", () => {
|
||||
render(<Footer />);
|
||||
|
||||
const connectHeading = screen.getByText("Redes profissionais");
|
||||
expect(connectHeading).toBeInTheDocument();
|
||||
expect(connectHeading.tagName).toBe("H3");
|
||||
});
|
||||
|
||||
it("should render GitHub social link", () => {
|
||||
render(<Footer />);
|
||||
|
||||
const githubLink = screen.getByLabelText("Visit GitHub profile");
|
||||
expect(githubLink).toBeInTheDocument();
|
||||
expect(githubLink.tagName).toBe("A");
|
||||
});
|
||||
|
||||
it("should render LinkedIn social link", () => {
|
||||
render(<Footer />);
|
||||
|
||||
const linkedinLink = screen.getByLabelText("Visit LinkedIn profile");
|
||||
expect(linkedinLink).toBeInTheDocument();
|
||||
expect(linkedinLink.tagName).toBe("A");
|
||||
});
|
||||
|
||||
it("should have correct href for GitHub link", () => {
|
||||
render(<Footer />);
|
||||
|
||||
const githubLink = screen.getByLabelText("Visit GitHub profile");
|
||||
expect(githubLink).toHaveAttribute(
|
||||
"href",
|
||||
"https://github.com/evandrodevbr",
|
||||
);
|
||||
});
|
||||
|
||||
it("should have correct href for LinkedIn link", () => {
|
||||
render(<Footer />);
|
||||
|
||||
const linkedinLink = screen.getByLabelText("Visit LinkedIn profile");
|
||||
expect(linkedinLink).toHaveAttribute(
|
||||
"href",
|
||||
"https://linkedin.com/in/evandrodevbr",
|
||||
);
|
||||
});
|
||||
|
||||
it('should open social links in new tab with target="_blank"', () => {
|
||||
render(<Footer />);
|
||||
|
||||
const githubLink = screen.getByLabelText("Visit GitHub profile");
|
||||
const linkedinLink = screen.getByLabelText("Visit LinkedIn profile");
|
||||
|
||||
expect(githubLink).toHaveAttribute("target", "_blank");
|
||||
expect(linkedinLink).toHaveAttribute("target", "_blank");
|
||||
});
|
||||
|
||||
it('should have rel="noopener noreferrer" for security', () => {
|
||||
render(<Footer />);
|
||||
|
||||
const githubLink = screen.getByLabelText("Visit GitHub profile");
|
||||
const linkedinLink = screen.getByLabelText("Visit LinkedIn profile");
|
||||
|
||||
expect(githubLink).toHaveAttribute("rel", "noopener noreferrer");
|
||||
expect(linkedinLink).toHaveAttribute("rel", "noopener noreferrer");
|
||||
});
|
||||
|
||||
it("should have aria-label for accessibility", () => {
|
||||
render(<Footer />);
|
||||
|
||||
const githubLink = screen.getByLabelText("Visit GitHub profile");
|
||||
const linkedinLink = screen.getByLabelText("Visit LinkedIn profile");
|
||||
|
||||
expect(githubLink).toHaveAttribute("aria-label", "Visit GitHub profile");
|
||||
expect(linkedinLink).toHaveAttribute(
|
||||
"aria-label",
|
||||
"Visit LinkedIn profile",
|
||||
);
|
||||
});
|
||||
|
||||
it("should render SVG icons for social links", () => {
|
||||
render(<Footer />);
|
||||
|
||||
const githubLink = screen.getByLabelText("Visit GitHub profile");
|
||||
const linkedinLink = screen.getByLabelText("Visit LinkedIn profile");
|
||||
|
||||
const githubSvg = githubLink.querySelector("svg");
|
||||
const linkedinSvg = linkedinLink.querySelector("svg");
|
||||
|
||||
expect(githubSvg).toBeInTheDocument();
|
||||
expect(linkedinSvg).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have aria-hidden="true" on SVG icons', () => {
|
||||
render(<Footer />);
|
||||
|
||||
const githubLink = screen.getByLabelText("Visit GitHub profile");
|
||||
const linkedinLink = screen.getByLabelText("Visit LinkedIn profile");
|
||||
|
||||
const githubSvg = githubLink.querySelector("svg");
|
||||
const linkedinSvg = linkedinLink.querySelector("svg");
|
||||
|
||||
expect(githubSvg).toHaveAttribute("aria-hidden", "true");
|
||||
expect(linkedinSvg).toHaveAttribute("aria-hidden", "true");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Footer Component - Copyright", () => {
|
||||
it("should render copyright section heading", () => {
|
||||
render(<Footer />);
|
||||
|
||||
const copyrightHeading = screen.getByText("Informacoes legais");
|
||||
expect(copyrightHeading).toBeInTheDocument();
|
||||
expect(copyrightHeading.tagName).toBe("H3");
|
||||
});
|
||||
|
||||
it("should display current year in copyright text", () => {
|
||||
render(<Footer />);
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
const copyrightText = screen.getByText(
|
||||
new RegExp(`© ${currentYear} Evandro Fonseca Junior`),
|
||||
);
|
||||
|
||||
expect(copyrightText).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should update copyright year dynamically", () => {
|
||||
// Test that the component uses new Date().getFullYear() dynamically
|
||||
// by verifying it shows the current year (not a hardcoded value)
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
render(<Footer />);
|
||||
|
||||
// Verify the copyright includes the current year
|
||||
const copyrightText = screen.getByText(
|
||||
new RegExp(`© ${currentYear} Evandro Fonseca Junior`),
|
||||
);
|
||||
expect(copyrightText).toBeInTheDocument();
|
||||
|
||||
// Verify it's not hardcoded to a specific year like 2023 or 2024
|
||||
// by checking that the year matches the actual current year
|
||||
expect(currentYear).toBeGreaterThanOrEqual(2024);
|
||||
});
|
||||
|
||||
it("should apply correct styling to copyright text", () => {
|
||||
render(<Footer />);
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
const copyrightText = screen.getByText(
|
||||
new RegExp(`© ${currentYear} Evandro Fonseca Junior`),
|
||||
);
|
||||
const classes = copyrightText.className;
|
||||
|
||||
expect(classes).toContain("text-[12px]");
|
||||
expect(classes).toContain("text-[rgba(255,255,255,0.5)]");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Footer Component - Responsive Layout", () => {
|
||||
beforeEach(() => {
|
||||
// Mock window.matchMedia for responsive tests
|
||||
Object.defineProperty(window, "matchMedia", {
|
||||
writable: true,
|
||||
value: jest.fn().mockImplementation((query) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: jest.fn(),
|
||||
removeListener: jest.fn(),
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
dispatchEvent: jest.fn(),
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
it("should have responsive grid layout classes", () => {
|
||||
const { container } = render(<Footer />);
|
||||
|
||||
const gridContainer = container.querySelector(".grid");
|
||||
expect(gridContainer).toBeInTheDocument();
|
||||
|
||||
const classes = gridContainer?.className || "";
|
||||
expect(classes).toContain("grid-cols-1");
|
||||
expect(classes).toContain("md:grid-cols-2");
|
||||
expect(classes).toContain("lg:grid-cols-3");
|
||||
});
|
||||
|
||||
it("should have responsive padding classes on container", () => {
|
||||
const { container } = render(<Footer />);
|
||||
|
||||
const innerContainer = container.querySelector(".ds-section-shell");
|
||||
expect(innerContainer).toBeInTheDocument();
|
||||
|
||||
// The ds-section class handles responsive padding via CSS
|
||||
const footer = container.querySelector("footer");
|
||||
const classes = footer?.className || "";
|
||||
expect(classes).toContain("ds-section");
|
||||
});
|
||||
|
||||
it("should have responsive gap classes on grid", () => {
|
||||
const { container } = render(<Footer />);
|
||||
|
||||
const gridContainer = container.querySelector(".grid");
|
||||
const classes = gridContainer?.className || "";
|
||||
|
||||
expect(classes).toContain("gap-sp-6");
|
||||
});
|
||||
|
||||
it("should have responsive column span for copyright section", () => {
|
||||
const { container } = render(<Footer />);
|
||||
|
||||
// Find the copyright section by its heading
|
||||
const copyrightHeading = screen.getByText("Informacoes legais");
|
||||
const copyrightSection = copyrightHeading.closest(".ds-card");
|
||||
|
||||
expect(copyrightSection).toBeInTheDocument();
|
||||
const classes = copyrightSection?.className || "";
|
||||
|
||||
expect(classes).toContain("md:col-span-2");
|
||||
expect(classes).toContain("lg:col-span-1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Footer Component - Design System Consistency", () => {
|
||||
it("should use design system kicker class for section labels", () => {
|
||||
render(<Footer />);
|
||||
|
||||
const kickers = document.querySelectorAll(".ds-kicker");
|
||||
expect(kickers.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should have ds-section class on footer element", () => {
|
||||
const { container } = render(<Footer />);
|
||||
|
||||
const footer = container.querySelector("footer");
|
||||
expect(footer).toBeInTheDocument();
|
||||
|
||||
const classes = footer?.className || "";
|
||||
expect(classes).toContain("ds-section");
|
||||
});
|
||||
|
||||
it("should have ds-section-dark class for dark background", () => {
|
||||
const { container } = render(<Footer />);
|
||||
|
||||
const footer = container.querySelector("footer");
|
||||
const classes = footer?.className || "";
|
||||
|
||||
expect(classes).toContain("ds-section-dark");
|
||||
});
|
||||
|
||||
it("should use design system shell class for container", () => {
|
||||
const { container } = render(<Footer />);
|
||||
|
||||
const shell = container.querySelector(".ds-section-shell");
|
||||
expect(shell).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Footer Component - Structure", () => {
|
||||
it("should render as footer semantic element", () => {
|
||||
const { container } = render(<Footer />);
|
||||
|
||||
const footer = container.querySelector("footer");
|
||||
expect(footer).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should have three main sections", () => {
|
||||
render(<Footer />);
|
||||
|
||||
const contactHeading = screen.getByText("Contato direto");
|
||||
const connectHeading = screen.getByText("Redes profissionais");
|
||||
const copyrightHeading = screen.getByText("Informacoes legais");
|
||||
|
||||
expect(contactHeading).toBeInTheDocument();
|
||||
expect(connectHeading).toBeInTheDocument();
|
||||
expect(copyrightHeading).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should have design system shell container", () => {
|
||||
const { container } = render(<Footer />);
|
||||
|
||||
const innerContainer = container.querySelector(".ds-section-shell");
|
||||
expect(innerContainer).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user