Skip to content

реализованы тесты для хука useChildrenMeasure #17

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@worksolutions/react-utils",
"private": false,
"version": "1.2.65",
"version": "1.2.66",
"description": "",
"types": "dist/esm/index.d.ts",
"main": "dist/cjs/index.js",
50 changes: 30 additions & 20 deletions src/hooks/useChildrenMeasure.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,41 @@
import React from "react";
import React, { useEffect } from "react";
import { htmlCollectionToArray } from "@worksolutions/utils";

export function computeRelativeMeasures(childrenRects: DOMRect[], parentRect: DOMRect) {
return childrenRects.map((childrenRect) => {
const x = childrenRect.x - parentRect.x;
const y = childrenRect.y - parentRect.y;

return {
toJSON: () => "",
width: childrenRect.width,
height: childrenRect.height,
x,
y,
left: x,
top: y,
bottom: y + childrenRect.height,
right: x + childrenRect.width,
};
});
}

export function useChildrenMeasure(useResizeObserver = false) {
const elementRef = React.useRef<HTMLElement | null>(null);
const resizeObserverRef = React.useRef<ResizeObserver>(null!);

const [measures, setMeasures] = React.useState<DOMRect[] | null>(null);
const [relativeMeasures, setRelativeMeasures] = React.useState<DOMRect[] | null>(null);
const elementRef = React.useRef<HTMLElement | null>(null);

const update = React.useCallback(() => {
if (!elementRef.current) return;
const childrenRects = htmlCollectionToArray(elementRef.current.children).map((element) =>
element.getBoundingClientRect(),
);

const parentRect = elementRef.current.getBoundingClientRect();
setMeasures(childrenRects);
setRelativeMeasures(
childrenRects.map((childrenRect) => {
const x = childrenRect.x - parentRect.x;
const y = childrenRect.y - parentRect.y;
return {
toJSON: () => "",
width: childrenRect.width,
height: childrenRect.height,
x,
y,
left: x,
top: y,
bottom: y + childrenRect.height,
right: x + childrenRect.width,
};
}),
);
setRelativeMeasures(computeRelativeMeasures(childrenRects, parentRect));
}, []);

const initRef = React.useCallback(
@@ -38,13 +44,17 @@ export function useChildrenMeasure(useResizeObserver = false) {
if (!element) return;

if (useResizeObserver) {
new ResizeObserver(update).observe(element);
resizeObserverRef.current = new ResizeObserver(update);
resizeObserverRef.current.observe(element);
return;
}

update();
},
[update, useResizeObserver],
);

useEffect(() => () => resizeObserverRef.current?.disconnect(), []);

return { measures, relativeMeasures, initRef, update };
}
2 changes: 1 addition & 1 deletion src/hooks/useTimer.ts
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ export function useTimer({
onSuccess?: () => void;
}) {
const forceUpdate = useForceUpdate();
const timerRef = useRef<number>(null!);
const timerRef = useRef<any>(null!);
const valueRef = useRef(initialValue() || 0);

const start = useCallback(
Original file line number Diff line number Diff line change
@@ -36,7 +36,7 @@ const Template: ComponentStory<typeof Demo> = (props) => {
};

export default {
title: "Hooks/useChildrenWidthDetector",
title: "Hooks/useChildrenMeasure",
component: Demo,
argTypes: {
useResizeObserver: {
@@ -58,7 +58,7 @@ export default {
},
} as ComponentMeta<typeof Demo>;

export const useChildrenWidthDetectorInfo = Template.bind({});
useChildrenWidthDetectorInfo.args = {};
export const useChildrenMeasureInfo = Template.bind({});
useChildrenMeasureInfo.args = {};

useChildrenWidthDetectorInfo.storyName = "useChildrenWidthDetector";
useChildrenMeasureInfo.storyName = "useChildrenMeasure";
98 changes: 98 additions & 0 deletions src/tests/useChildrenMeasure.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React from "react";
import { renderHook } from "@testing-library/react-hooks";
import { act } from "react-dom/test-utils";
import { htmlCollectionToArray } from "@worksolutions/utils";

import { ResizeObserverMocker } from "./utils/ResizeObserverMocker";
import { useChildrenMeasure } from "../hooks";

const mockRect: DOMRect = {
x: 10,
y: 10,
width: 200,
height: 200,
top: 100,
bottom: 0,
left: 100,
right: 0,
toJSON: () => "",
};

const defaultEmptyArray = new Array(4).fill(undefined);

const $rootTestElement = document.createElement("div");
$rootTestElement.getBoundingClientRect = jest.fn(() => {
const newMockRect = { ...mockRect };
newMockRect.x = 10;
newMockRect.y = 10;

return newMockRect;
});

defaultEmptyArray.forEach(() => {
const $childElement = document.createElement("div");
$rootTestElement.appendChild($childElement);
});

function rewriteChildrenRect(rect: DOMRect = ResizeObserverMocker.defaultDOMRect.contentRect) {
htmlCollectionToArray($rootTestElement.children).forEach(($childElement: HTMLElement) => {
$childElement.getBoundingClientRect = jest.fn(() => rect);
});
}

describe("useChildrenMeasure", () => {
test("useChildrenMeasure is defined", () => {
expect(useChildrenMeasure).toBeDefined();
});

test("useChildrenMeasure should get default values", () => {
const { result } = renderHook(() => useChildrenMeasure());
rewriteChildrenRect();

act(() => result.current.initRef($rootTestElement));
expect(result.current.measures).toMatchObject(
defaultEmptyArray.map(() => ResizeObserverMocker.defaultDOMRect.contentRect),
);
});

test("useChildrenMeasure should get children rect", () => {
const { result } = renderHook(() => useChildrenMeasure());
rewriteChildrenRect(mockRect);

act(() => result.current.initRef($rootTestElement));

expect(result.current.measures).toMatchObject(defaultEmptyArray.map(() => mockRect));
});

test("useChildrenMeasure should track changes in childrens", () => {
const resizeObserverMocker = new ResizeObserverMocker();
resizeObserverMocker.setResizeObserverToWindow();
rewriteChildrenRect();

const { result } = renderHook(() => useChildrenMeasure(true));
act(() => result.current.initRef($rootTestElement));
act(() => result.current.update());

expect(result.current.measures).toMatchObject(
defaultEmptyArray.map(() => ResizeObserverMocker.defaultDOMRect.contentRect),
);

rewriteChildrenRect(mockRect);
act(() => resizeObserverMocker.listener());

expect(result.current.measures).toMatchObject(defaultEmptyArray.map(() => mockRect));
});

test("disconnect should call when component unmount", () => {
const resizeObserverMocker = new ResizeObserverMocker();
resizeObserverMocker.setResizeObserverToWindow();
rewriteChildrenRect();

const { result, unmount } = renderHook(() => useChildrenMeasure(true));
act(() => result.current.initRef($rootTestElement));

unmount();

expect(resizeObserverMocker.disconnect).toBeCalled();
});
});
66 changes: 66 additions & 0 deletions src/tests/utils/ResizeObserverMocker/ResizeObserverMocker.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { ResizeObserverMocker } from "./index";

const globalState = {
domRect: {},
};

const mockRect: DOMRect = {
x: 1,
y: 2,
width: 200,
height: 200,
top: 100,
bottom: 0,
left: 100,
right: 0,
toJSON: () => "",
};

const newMockRect = { ...mockRect, width: 9999 };

describe("ResizeObserverMocker", () => {
test("ResizeObserverMocker should be defined", () => {
expect(ResizeObserverMocker).toBeDefined();
});

test("method in resizeObserverMocker should set default DOMRect", () => {
const resizeObserverMocker = new ResizeObserverMocker();

resizeObserverMocker.setResizeObserverToWindow();
const resizeObserverCallback: any = jest.fn((data: DOMRect) => (globalState.domRect = data));
new ResizeObserver(resizeObserverCallback);

resizeObserverMocker.listener(ResizeObserverMocker.defaultDOMRect);

expect(globalState.domRect).toMatchObject(ResizeObserverMocker.defaultDOMRect);
});

test("multiple updates listener", () => {
const resizeObserverMocker = new ResizeObserverMocker();

resizeObserverMocker.setResizeObserverToWindow();
const resizeObserverCallback: any = jest.fn((data: DOMRect) => (globalState.domRect = data));
new ResizeObserver(resizeObserverCallback);

resizeObserverMocker.listener(ResizeObserverMocker.defaultDOMRect);
expect(globalState.domRect).toMatchObject(ResizeObserverMocker.defaultDOMRect);

resizeObserverMocker.listener(mockRect);
expect(globalState.domRect).toMatchObject(mockRect);

resizeObserverMocker.listener(newMockRect);
expect(globalState.domRect).toMatchObject(newMockRect);
});

test("methods in resizeObserverMocker should be called", () => {
const resizeObserverMocker = new ResizeObserverMocker();

resizeObserverMocker.disconnect();
resizeObserverMocker.unobserve();
resizeObserverMocker.observe();

expect(resizeObserverMocker.disconnect).toBeCalled();
expect(resizeObserverMocker.unobserve).toBeCalled();
expect(resizeObserverMocker.observe).toBeCalled();
});
});
51 changes: 51 additions & 0 deletions src/tests/utils/ResizeObserverMocker/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ResizeObserverMethodsNames } from "./types";

const Global = window || global;
export class ResizeObserverMocker {
static defaultDOMRect: { contentRect: DOMRect } = {
contentRect: {
x: 0,
y: 0,
bottom: 0,
height: 0,
left: 0,
right: 0,
top: 0,
width: 0,
toJSON: () => "",
},
};

constructor() {
this.setListener = this.setListener.bind(this);
}

observe = jest.fn();
unobserve = jest.fn();
disconnect = jest.fn();
listener: any = undefined as any;

implementResizeObserverMethod(methodName: ResizeObserverMethodsNames, mockMethod: jest.Mock<any, any>) {
this[methodName] = mockMethod;
}

setResizeObserverToWindow() {
const setListener = this.setListener.bind(this);
const { observe, unobserve, disconnect } = this;

// @ts-ignore
Global.ResizeObserver = class ResizeObserver {
constructor(ls: any) {
setListener(ls);
}
};

Global.ResizeObserver.prototype.observe = observe;
Global.ResizeObserver.prototype.unobserve = unobserve;
Global.ResizeObserver.prototype.disconnect = disconnect;
}

private setListener(ls: ResizeObserverCallback) {
this.listener = ls;
}
}
6 changes: 6 additions & 0 deletions src/tests/utils/ResizeObserverMocker/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export enum ResizeObserverMethodsNames {
observe = "observe",
unobserve = "unobserve",
disconnect = "disconnect",
listener = "listener",
}