diff --git a/src/components/effects/GradientBlinds.test.tsx b/src/components/effects/GradientBlinds.test.tsx
index d75d181..fdb4c45 100644
--- a/src/components/effects/GradientBlinds.test.tsx
+++ b/src/components/effects/GradientBlinds.test.tsx
@@ -13,34 +13,37 @@ vi.mock('ogl', () => {
drawingBufferWidth: 100,
drawingBufferHeight: 100,
};
- setSize() {}
- render() {}
+ setSize() { }
+ render() { }
},
Program: class {
- remove() {}
+ remove() { }
},
Mesh: class {
- remove() {}
+ remove() { }
},
Geometry: class {
- remove() {}
+ remove() { }
},
Triangle: class {
- remove() {}
+ remove() { }
},
+
};
});
// Mock ResizeObserver
globalThis.ResizeObserver = class ResizeObserver {
- observe() {}
- unobserve() {}
- disconnect() {}
+ observe() { }
+ unobserve() { }
+ disconnect() { }
};
describe('GradientBlinds', () => {
let rafSpy: any;
let cancelRafSpy: any;
+ let addEventListenerSpy: any;
+ let removeEventListenerSpy: any;
let ioCallback: (entries: IntersectionObserverEntry[]) => void;
let observeSpy: any;
let disconnectSpy: any;
@@ -51,6 +54,8 @@ describe('GradientBlinds', () => {
});
cancelRafSpy = vi.spyOn(window, 'cancelAnimationFrame').mockImplementation((_id: number) => {
});
+ addEventListenerSpy = vi.spyOn(window, 'addEventListener');
+ removeEventListenerSpy = vi.spyOn(window, 'removeEventListener');
// Mock IntersectionObserver
observeSpy = vi.fn();
@@ -133,6 +138,44 @@ describe('GradientBlinds', () => {
expect(disconnectSpy).toHaveBeenCalled();
});
+ it('toggles pointer event listener based on visibility', () => {
+ const { unmount } = render();
+
+ // Should not listen initially (not visible)
+ // Initially not called
+ expect(addEventListenerSpy).not.toHaveBeenCalledWith('pointermove', expect.any(Function));
+
+ // Simulate on-screen
+ act(() => {
+ if (ioCallback) {
+ ioCallback([{ isIntersecting: true } as IntersectionObserverEntry]);
+ }
+ });
+ expect(addEventListenerSpy).toHaveBeenCalledWith('pointermove', expect.any(Function));
+
+ // Reset
+ addEventListenerSpy.mockClear();
+ removeEventListenerSpy.mockClear();
+
+ // Simulate off-screen
+ act(() => {
+ if (ioCallback) {
+ ioCallback([{ isIntersecting: false } as IntersectionObserverEntry]);
+ }
+ });
+ expect(removeEventListenerSpy).toHaveBeenCalledWith('pointermove', expect.any(Function));
+
+ // Simulate on-screen again
+ act(() => {
+ if (ioCallback) {
+ ioCallback([{ isIntersecting: true } as IntersectionObserverEntry]);
+ }
+ });
+ expect(addEventListenerSpy).toHaveBeenCalledWith('pointermove', expect.any(Function));
+
+ unmount();
+ expect(removeEventListenerSpy).toHaveBeenCalledWith('pointermove', expect.any(Function));
+ });
it('minimizes getBoundingClientRect calls during pointer move', () => {
const { unmount } = render();
@@ -155,8 +198,7 @@ describe('GradientBlinds', () => {
window.dispatchEvent(event);
});
- // EXPECTATION: It should NOT be called.
- // This will fail currently.
+ // EXPECTATION: It should NOT be called because the listener shouldn't be attached (not visible)
expect(spy).not.toHaveBeenCalled();
unmount();
diff --git a/src/components/effects/GradientBlinds.tsx b/src/components/effects/GradientBlinds.tsx
index 9b44f1f..48c0a71 100644
--- a/src/components/effects/GradientBlinds.tsx
+++ b/src/components/effects/GradientBlinds.tsx
@@ -330,7 +330,6 @@ void main() {
uniforms.iMouse.value = [x, y];
}
};
- window.addEventListener('pointermove', onPointerMove);
const loop = (t: number) => {
rafRef.current = requestAnimationFrame(loop);
@@ -364,11 +363,13 @@ void main() {
lastTimeRef.current = 0;
rafRef.current = requestAnimationFrame(loop);
}
+ window.addEventListener('pointermove', onPointerMove);
} else {
if (rafRef.current) {
cancelAnimationFrame(rafRef.current);
rafRef.current = null;
}
+ window.removeEventListener('pointermove', onPointerMove);
}
});
observer.observe(container);