TRIVIAL: Add xray-core-subnet-mimicry article (#4)
This commit is contained in:
1 parent
49b545f1b4
commit
ea35a3d9f5
21 files changed
+785
-79
No files matched your search
@@ -0,0 +1,20 @@
|
||||
import type React from 'react';
|
||||
|
||||
/**
|
||||
* Safely casts a React ref to a mutable ref object.
|
||||
* This is a controlled escape hatch for situations where TypeScript's
|
||||
* built-in Ref types are too restrictive (e.g., when syncing refs manually).
|
||||
*
|
||||
* @param ref - The ref to cast
|
||||
* @returns The casted mutable ref
|
||||
* @example
|
||||
* ```tsx
|
||||
* const mutableRef = castMutableRef<HTMLDivElement>(ref);
|
||||
* mutableRef.current = someElement;
|
||||
* ```
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function castMutableRef<T>(ref: any): React.MutableRefObject<T | null> {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return ref as React.MutableRefObject<T | null>;
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
export { castRef } from './castRef/castRef';
|
||||
export { castMutableRef } from './castRef/castMutableRef';
|
||||
export { genericMemo } from './genericMemo/genericMemo';
|
||||
export { useResizeObserver } from './useResizeObserver/useResizeObserver';
|
||||
export { useIntersectionObserver } from './useIntersectionObserver/useIntersectionObserver';
|
||||
export { useScroll } from './useScroll/useScroll';
|
||||
export { useLocalStorage } from './useLocalStorage/useLocalStorage';
|
||||
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
|
||||
import { useIntersectionObserver } from './useIntersectionObserver';
|
||||
|
||||
describe('useIntersectionObserver', () => {
|
||||
let observerCallback: IntersectionObserverCallback;
|
||||
let observeMock = vi.fn();
|
||||
let unobserveMock = vi.fn();
|
||||
let disconnectMock = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
observeMock = vi.fn();
|
||||
unobserveMock = vi.fn();
|
||||
disconnectMock = vi.fn();
|
||||
|
||||
class MockIntersectionObserver {
|
||||
constructor(callback: IntersectionObserverCallback, options?: IntersectionObserverInit) {
|
||||
observerCallback = callback;
|
||||
this.root = options?.root ?? null;
|
||||
this.rootMargin = options?.rootMargin ?? '';
|
||||
this.thresholds = options?.threshold
|
||||
? Array.isArray(options.threshold)
|
||||
? options.threshold
|
||||
: [options.threshold]
|
||||
: [0];
|
||||
}
|
||||
root: Element | Document | null;
|
||||
rootMargin: string;
|
||||
thresholds: readonly number[];
|
||||
takeRecords = vi.fn();
|
||||
observe = observeMock;
|
||||
unobserve = unobserveMock;
|
||||
disconnect = disconnectMock;
|
||||
}
|
||||
|
||||
vi.stubGlobal('IntersectionObserver', MockIntersectionObserver);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return initial state as not intersecting', () => {
|
||||
const ref = { current: document.createElement('div') };
|
||||
const { result } = renderHook(() => useIntersectionObserver(ref));
|
||||
expect(result.current).toBe(false);
|
||||
});
|
||||
|
||||
it('should call observe on mount', () => {
|
||||
const element = document.createElement('div');
|
||||
const ref = { current: element };
|
||||
renderHook(() => useIntersectionObserver(ref));
|
||||
|
||||
expect(observeMock).toHaveBeenCalledWith(element);
|
||||
});
|
||||
|
||||
it('should update isIntersecting when observer triggers', () => {
|
||||
const element = document.createElement('div');
|
||||
const ref = { current: element };
|
||||
const { result } = renderHook(() => useIntersectionObserver(ref));
|
||||
|
||||
const entries = [
|
||||
{
|
||||
isIntersecting: true,
|
||||
target: element,
|
||||
},
|
||||
] as unknown as IntersectionObserverEntry[];
|
||||
|
||||
act(() => {
|
||||
observerCallback(entries, {} as IntersectionObserver);
|
||||
});
|
||||
|
||||
expect(result.current).toBe(true);
|
||||
});
|
||||
|
||||
it('should disconnect after first intersection if once is true', () => {
|
||||
const element = document.createElement('div');
|
||||
const ref = { current: element };
|
||||
renderHook(() => useIntersectionObserver(ref, { once: true }));
|
||||
|
||||
const entries = [
|
||||
{
|
||||
isIntersecting: true,
|
||||
target: element,
|
||||
},
|
||||
] as unknown as IntersectionObserverEntry[];
|
||||
|
||||
act(() => {
|
||||
observerCallback(entries, {} as IntersectionObserver);
|
||||
});
|
||||
|
||||
expect(disconnectMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not disconnect after first intersection if once is false', () => {
|
||||
const element = document.createElement('div');
|
||||
const ref = { current: element };
|
||||
renderHook(() => useIntersectionObserver(ref, { once: false }));
|
||||
|
||||
const entries = [
|
||||
{
|
||||
isIntersecting: true,
|
||||
target: element,
|
||||
},
|
||||
] as unknown as IntersectionObserverEntry[];
|
||||
|
||||
act(() => {
|
||||
observerCallback(entries, {} as IntersectionObserver);
|
||||
});
|
||||
|
||||
expect(disconnectMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call disconnect on unmount', () => {
|
||||
const element = document.createElement('div');
|
||||
const ref = { current: element };
|
||||
const { unmount } = renderHook(() => useIntersectionObserver(ref));
|
||||
|
||||
unmount();
|
||||
|
||||
expect(disconnectMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not observe if ref is null', () => {
|
||||
const ref = { current: null };
|
||||
renderHook(() => useIntersectionObserver(ref));
|
||||
|
||||
expect(observeMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return true immediately if IntersectionObserver is not available', () => {
|
||||
vi.stubGlobal('IntersectionObserver', undefined);
|
||||
const ref = { current: document.createElement('div') };
|
||||
const { result } = renderHook(() => useIntersectionObserver(ref));
|
||||
expect(result.current).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
/** Options for useIntersectionObserver */
|
||||
export interface UseIntersectionObserverOptions extends IntersectionObserverInit {
|
||||
/** Whether to disconnect after the first intersection */
|
||||
once?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that observes if an element is intersecting with its root.
|
||||
*
|
||||
* @param ref - A React ref to the element to observe.
|
||||
* @param options - IntersectionObserver configuration.
|
||||
* @returns Boolean indicating if the element is currently intersecting.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const MyComponent = () => {
|
||||
* const ref = useRef<HTMLDivElement>(null);
|
||||
* const isVisible = useIntersectionObserver(ref);
|
||||
*
|
||||
* return <div ref={ref}>{isVisible ? 'I am visible!' : 'Scroll down...'}</div>;
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
export const useIntersectionObserver = <T extends HTMLElement>(
|
||||
ref: React.RefObject<T | null>,
|
||||
{ once = true, ...options }: UseIntersectionObserverOptions = {}
|
||||
): boolean => {
|
||||
const [isIntersecting, setIntersecting] = useState(false);
|
||||
const { root, rootMargin, threshold } = options;
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof IntersectionObserver === 'undefined') {
|
||||
setIntersecting(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const observeTarget = ref.current;
|
||||
if (!observeTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
setIntersecting(entry.isIntersecting);
|
||||
if (entry.isIntersecting && once) {
|
||||
observer.disconnect();
|
||||
}
|
||||
},
|
||||
{ root, rootMargin, threshold }
|
||||
);
|
||||
|
||||
observer.observe(observeTarget);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [ref, once, root, rootMargin, threshold]);
|
||||
|
||||
return isIntersecting;
|
||||
};
|
||||
Reference in new issue
Block a user