diff --git a/src/components/layout/FancyCursor.test.tsx b/src/components/layout/FancyCursor.test.tsx index 6b433d6..da5d2d3 100644 --- a/src/components/layout/FancyCursor.test.tsx +++ b/src/components/layout/FancyCursor.test.tsx @@ -86,26 +86,22 @@ describe('FancyCursor', () => { document.body.removeChild(link); }); - it('uses fallback elementFromPoint if hoveredElement is null', () => { + it('does not call elementFromPoint on mousemove', () => { // Reset elementFromPoint mock const mockElementFromPoint = vi.fn(); // @ts-ignore document.elementFromPoint = mockElementFromPoint; - // We need to remount to reset internal state if any. - // However, 'hoveredElement' variable was defined INSIDE the component in my edit. - // Let's double check that. - render(); - // Trigger mousemove WITHOUT prior mouseover. - // This should trigger the fallback. + // Trigger mousemove fireEvent.mouseMove(window, { clientX: 10, clientY: 10 }); act(() => { vi.runAllTimers(); }); - expect(mockElementFromPoint).toHaveBeenCalledWith(10, 10); + // Should NOT be called in the optimized version + expect(mockElementFromPoint).not.toHaveBeenCalled(); }); }); diff --git a/src/components/layout/FancyCursor.tsx b/src/components/layout/FancyCursor.tsx index 27d6c5c..501d293 100644 --- a/src/components/layout/FancyCursor.tsx +++ b/src/components/layout/FancyCursor.tsx @@ -19,10 +19,24 @@ export const FancyCursor = memo(() => { if (!cursor) return; let animationFrameId: number; - let hoveredElement: Element | null = null; + + const updateCursorState = (el: Element | null) => { + const classList = cursor.classList; + + // Coercing to boolean with `!!` is a micro-optimization. + const isResize = !!el?.closest('#custom-resize-handle'); + const isText = !!el?.closest('input[type="text"], input[type="email"], textarea, [contenteditable="true"]'); + const isLink = !!el?.closest('a, button, [role="button"], input[type="submit"], input[type="button"]'); + + // These classes are toggled based on the hovered element. + // The actual visual styles are defined in your CSS files. + classList.toggle('resize-hover', isResize); + classList.toggle('text-hover', isText && !isResize); + classList.toggle('link-hover', isLink && !isText && !isResize); + }; const handleMouseOver = (e: MouseEvent) => { - hoveredElement = e.target as Element; + updateCursorState(e.target as Element); }; // The mouse move handler is throttled with requestAnimationFrame to ensure @@ -35,25 +49,6 @@ export const FancyCursor = memo(() => { // This shifts the cursor element by half its own width and height, // which effectively centers it on the pointer without affecting the visuals. cursor.style.transform = `translate3d(${e.clientX}px, ${e.clientY}px, 0) translate(-50%, -50%)`; - - // Use the cached hovered element if available, otherwise fallback to elementFromPoint - // This fallback is mostly for the initial state before any mouseover events - if (!hoveredElement) { - hoveredElement = document.elementFromPoint(e.clientX, e.clientY); - } - const el = hoveredElement; - const classList = cursor.classList; - - // Coercing to boolean with `!!` is a micro-optimization. - const isResize = !!el?.closest('#custom-resize-handle'); - const isText = !!el?.closest('input[type="text"], input[type="email"], textarea, [contenteditable="true"]'); - const isLink = !!el?.closest('a, button, [role="button"], input[type="submit"], input[type="button"]'); - - // These classes are toggled based on the hovered element. - // The actual visual styles are defined in your CSS files. - classList.toggle('resize-hover', isResize); - classList.toggle('text-hover', isText && !isResize); - classList.toggle('link-hover', isLink && !isText && !isResize); }); }; diff --git a/src/components/sections/Hero.test.tsx b/src/components/sections/Hero.test.tsx index 540ce68..dbe6cf4 100644 --- a/src/components/sections/Hero.test.tsx +++ b/src/components/sections/Hero.test.tsx @@ -1,3 +1,4 @@ +// @vitest-environment jsdom import { render, fireEvent, act, cleanup } from "@testing-library/react"; import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { Hero } from "./Hero";