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,246 @@
|
||||
import { render } from "@testing-library/react";
|
||||
import Footer from "./Footer";
|
||||
import fc from "fast-check";
|
||||
|
||||
describe("Property 2: Social Links Open in New Tab", () => {
|
||||
it('should have target="_blank" and rel="noopener noreferrer" for all social links', () => {
|
||||
/**
|
||||
* **Validates: Requirements 2.7**
|
||||
*
|
||||
* Property: For any social media link in the Footer component, it should have
|
||||
* target="_blank" and rel="noopener noreferrer" attributes to open in a new tab
|
||||
* securely. This ensures consistent behavior across all social links and prevents
|
||||
* security vulnerabilities (tabnabbing).
|
||||
*/
|
||||
fc.assert(
|
||||
fc.property(
|
||||
fc.constant(null), // No random input needed, testing static component
|
||||
() => {
|
||||
const { container } = render(<Footer />);
|
||||
|
||||
// Find all social media links (they are within the "Connect" section)
|
||||
const socialLinks = container.querySelectorAll(
|
||||
'a[aria-label*="Visit"]',
|
||||
);
|
||||
|
||||
// Verify we found social links
|
||||
if (socialLinks.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check each social link
|
||||
let allLinksValid = true;
|
||||
|
||||
socialLinks.forEach((link) => {
|
||||
const target = link.getAttribute("target");
|
||||
const rel = link.getAttribute("rel");
|
||||
|
||||
// Both attributes must be present and correct
|
||||
if (target !== "_blank" || rel !== "noopener noreferrer") {
|
||||
allLinksValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
return allLinksValid;
|
||||
},
|
||||
),
|
||||
{ numRuns: 100 },
|
||||
);
|
||||
});
|
||||
|
||||
it("should consistently apply security attributes across all social link variations", () => {
|
||||
/**
|
||||
* **Validates: Requirements 2.7**
|
||||
*
|
||||
* Property: For any subset of social links rendered, each link should maintain
|
||||
* the security attributes (target="_blank" and rel="noopener noreferrer").
|
||||
* This property verifies that the security pattern is consistently applied
|
||||
* regardless of which social platforms are included.
|
||||
*/
|
||||
fc.assert(
|
||||
fc.property(
|
||||
fc.integer({ min: 0, max: 10 }), // Random seed for variation
|
||||
() => {
|
||||
const { container } = render(<Footer />);
|
||||
|
||||
// Get all anchor tags in the footer
|
||||
const allLinks = container.querySelectorAll("footer a");
|
||||
|
||||
// Filter to only social links (those with SVG icons and aria-labels about visiting profiles)
|
||||
const socialLinks = Array.from(allLinks).filter((link) => {
|
||||
const ariaLabel = link.getAttribute("aria-label");
|
||||
const hasSvg = link.querySelector("svg") !== null;
|
||||
return ariaLabel && ariaLabel.includes("Visit") && hasSvg;
|
||||
});
|
||||
|
||||
// Verify we found social links
|
||||
if (socialLinks.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that ALL social links have the required attributes
|
||||
return socialLinks.every((link) => {
|
||||
const target = link.getAttribute("target");
|
||||
const rel = link.getAttribute("rel");
|
||||
return target === "_blank" && rel === "noopener noreferrer";
|
||||
});
|
||||
},
|
||||
),
|
||||
{ numRuns: 100 },
|
||||
);
|
||||
});
|
||||
|
||||
it("should not apply new tab attributes to non-social links", () => {
|
||||
/**
|
||||
* **Validates: Requirements 2.7**
|
||||
*
|
||||
* Property: Only social media links should have target="_blank". Other links
|
||||
* in the footer (like email) should not force new tab behavior. This ensures
|
||||
* the new tab behavior is intentionally applied only where appropriate.
|
||||
*/
|
||||
fc.assert(
|
||||
fc.property(fc.constant(null), () => {
|
||||
const { container } = render(<Footer />);
|
||||
|
||||
// Get all anchor tags in the footer
|
||||
const allLinks = container.querySelectorAll("footer a");
|
||||
|
||||
// Separate social links from other links
|
||||
const socialLinks: Element[] = [];
|
||||
const otherLinks: Element[] = [];
|
||||
|
||||
allLinks.forEach((link) => {
|
||||
const ariaLabel = link.getAttribute("aria-label");
|
||||
const hasSvg = link.querySelector("svg") !== null;
|
||||
|
||||
if (ariaLabel && ariaLabel.includes("Visit") && hasSvg) {
|
||||
socialLinks.push(link);
|
||||
} else {
|
||||
otherLinks.push(link);
|
||||
}
|
||||
});
|
||||
|
||||
// All social links should have target="_blank"
|
||||
const allSocialLinksHaveTarget = socialLinks.every((link) => {
|
||||
return link.getAttribute("target") === "_blank";
|
||||
});
|
||||
|
||||
// Non-social links (like email) should NOT have target="_blank"
|
||||
const noOtherLinksHaveTarget = otherLinks.every((link) => {
|
||||
return link.getAttribute("target") !== "_blank";
|
||||
});
|
||||
|
||||
return allSocialLinksHaveTarget && noOtherLinksHaveTarget;
|
||||
}),
|
||||
{ numRuns: 100 },
|
||||
);
|
||||
});
|
||||
|
||||
it("should maintain security attributes after multiple renders", () => {
|
||||
/**
|
||||
* **Validates: Requirements 2.7**
|
||||
*
|
||||
* Property: For any number of component renders, social links should consistently
|
||||
* maintain their security attributes. This verifies that the attributes are not
|
||||
* accidentally removed or modified during re-renders.
|
||||
*/
|
||||
fc.assert(
|
||||
fc.property(
|
||||
fc.integer({ min: 1, max: 5 }), // Test 1-5 renders
|
||||
(numRenders) => {
|
||||
let allRendersValid = true;
|
||||
|
||||
for (let i = 0; i < numRenders; i++) {
|
||||
const { container, unmount } = render(<Footer />);
|
||||
|
||||
// Find social links
|
||||
const socialLinks = container.querySelectorAll(
|
||||
'a[aria-label*="Visit"]',
|
||||
);
|
||||
|
||||
// Check each link
|
||||
socialLinks.forEach((link) => {
|
||||
const target = link.getAttribute("target");
|
||||
const rel = link.getAttribute("rel");
|
||||
|
||||
if (target !== "_blank" || rel !== "noopener noreferrer") {
|
||||
allRendersValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
unmount();
|
||||
}
|
||||
|
||||
return allRendersValid;
|
||||
},
|
||||
),
|
||||
{ numRuns: 100 },
|
||||
);
|
||||
});
|
||||
|
||||
it("should have both target and rel attributes present together", () => {
|
||||
/**
|
||||
* **Validates: Requirements 2.7**
|
||||
*
|
||||
* Property: For any social link, if target="_blank" is present, then
|
||||
* rel="noopener noreferrer" must also be present. These attributes work
|
||||
* together to provide secure new tab behavior, and one without the other
|
||||
* creates a security vulnerability.
|
||||
*/
|
||||
fc.assert(
|
||||
fc.property(fc.constant(null), () => {
|
||||
const { container } = render(<Footer />);
|
||||
|
||||
// Find all links with target="_blank"
|
||||
const linksWithTarget = container.querySelectorAll(
|
||||
'footer a[target="_blank"]',
|
||||
);
|
||||
|
||||
// Verify we found links
|
||||
if (linksWithTarget.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Each link with target="_blank" must have rel="noopener noreferrer"
|
||||
return Array.from(linksWithTarget).every((link) => {
|
||||
const rel = link.getAttribute("rel");
|
||||
return rel === "noopener noreferrer";
|
||||
});
|
||||
}),
|
||||
{ numRuns: 100 },
|
||||
);
|
||||
});
|
||||
|
||||
it("should have exactly the expected number of social links with security attributes", () => {
|
||||
/**
|
||||
* **Validates: Requirements 2.7**
|
||||
*
|
||||
* Property: The Footer should render a specific number of social links (based on
|
||||
* the socialLinks array), and all of them should have the security attributes.
|
||||
* This ensures no links are accidentally missing the attributes.
|
||||
*/
|
||||
fc.assert(
|
||||
fc.property(fc.constant(null), () => {
|
||||
const { container } = render(<Footer />);
|
||||
|
||||
// Find all social links with security attributes
|
||||
const secureLinks = container.querySelectorAll(
|
||||
'footer a[target="_blank"][rel="noopener noreferrer"]',
|
||||
);
|
||||
|
||||
// Find all social links (with aria-label containing "Visit")
|
||||
const allSocialLinks = container.querySelectorAll(
|
||||
'footer a[aria-label*="Visit"]',
|
||||
);
|
||||
|
||||
// The number of secure links should equal the number of social links
|
||||
// This ensures ALL social links have the security attributes
|
||||
return (
|
||||
secureLinks.length === allSocialLinks.length &&
|
||||
allSocialLinks.length > 0
|
||||
);
|
||||
}),
|
||||
{ numRuns: 100 },
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user