@websolutespa/bom-mixer-hooks 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +7 -5
- package/src/index.ts +60 -0
- package/src/useApi/useApi.test.tsx +193 -0
- package/src/useApi/useApi.ts +58 -0
- package/src/useBodyScroll/useBodyScroll.test.ts +112 -0
- package/src/useBodyScroll/useBodyScroll.ts +89 -0
- package/src/useBreakpoint/useBreakpoint.test.tsx +45 -0
- package/src/useBreakpoint/useBreakpoint.ts +44 -0
- package/src/useCart/useCart.test.tsx +89 -0
- package/src/useCart/useCart.tsx +132 -0
- package/src/useCheckout/useCheckout.test.tsx +53 -0
- package/src/useCheckout/useCheckout.tsx +95 -0
- package/src/useClickAnyWhere/useClickAnyWhere.test.ts +15 -0
- package/src/useClickAnyWhere/useClickAnyWhere.ts +9 -0
- package/src/useClickOut/useClickOut.test.tsx +78 -0
- package/src/useClickOut/useClickOut.ts +25 -0
- package/src/useCurrency/useCurrency.test.tsx +33 -0
- package/src/useCurrency/useCurrency.ts +42 -0
- package/src/useCurrentState/useCurrentState.test.tsx +56 -0
- package/src/useCurrentState/useCurrentState.ts +22 -0
- package/src/useDateTimeFormat/useDateTimeFormat.test.tsx +47 -0
- package/src/useDateTimeFormat/useDateTimeFormat.ts +22 -0
- package/src/useDebounce/useDebounce.test.ts +49 -0
- package/src/useDebounce/useDebounce.ts +15 -0
- package/src/useDebounceCallback/useDebounceCallback.test.ts +34 -0
- package/src/useDebounceCallback/useDebounceCallback.ts +102 -0
- package/src/useDebugChangedProps/useDebugChangedProps.test.tsx +21 -0
- package/src/useDebugChangedProps/useDebugChangedProps.ts +17 -0
- package/src/useDomObserver/useDomObserver.test.ts +22 -0
- package/src/useDomObserver/useDomObserver.ts +26 -0
- package/src/useDrawer/useDrawer.test.ts +17 -0
- package/src/useDrawer/useDrawer.ts +16 -0
- package/src/useElementSize/useElementSize.test.ts +108 -0
- package/src/useElementSize/useElementSize.ts +34 -0
- package/src/useEventListener/useEventListener.test.ts +128 -0
- package/src/useEventListener/useEventListener.ts +43 -0
- package/src/useFilters/filter.service.ts +86 -0
- package/src/useFilters/filter.ts +152 -0
- package/src/useFilters/useFilters.test.ts +48 -0
- package/src/useFilters/useFilters.ts +44 -0
- package/src/useGenericContext/useGenericContext.test.tsx +19 -0
- package/src/useGenericContext/useGenericContext.ts +18 -0
- package/src/useGeolocation/geolocation.service.ts +66 -0
- package/src/useGeolocation/useGeolocation.test.ts +29 -0
- package/src/useGeolocation/useGeolocation.ts +56 -0
- package/src/useHttp/useHttp.test.tsx +192 -0
- package/src/useHttp/useHttp.ts +52 -0
- package/src/useIdle/useIdle.test.ts +31 -0
- package/src/useIdle/useIdle.ts +79 -0
- package/src/useInfiniteLoader/useInfiniteLoader.test.ts +41 -0
- package/src/useInfiniteLoader/useInfiniteLoader.ts +46 -0
- package/src/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.ts +3 -0
- package/src/useKeyboard/codes.ts +94 -0
- package/src/useKeyboard/helper.ts +31 -0
- package/src/useKeyboard/useKeyboard.test.tsx +163 -0
- package/src/useKeyboard/useKeyboard.ts +91 -0
- package/src/useLabel/useLabel.test.tsx +68 -0
- package/src/useLabel/useLabel.tsx +43 -0
- package/src/useLayout/useLayout.test.tsx +32 -0
- package/src/useLayout/useLayout.tsx +19 -0
- package/src/useModal/useModal.test.ts +19 -0
- package/src/useModal/useModal.ts +16 -0
- package/src/useMounted/useMounted.test.tsx +34 -0
- package/src/useMounted/useMounted.ts +25 -0
- package/src/useNumber/useNumber.test.tsx +36 -0
- package/src/useNumber/useNumber.ts +15 -0
- package/src/usePage/usePage.test.tsx +31 -0
- package/src/usePage/usePage.tsx +19 -0
- package/src/usePagination/pagination.service.ts +23 -0
- package/src/usePagination/usePagination.test.ts +64 -0
- package/src/usePagination/usePagination.ts +47 -0
- package/src/usePortal/usePortal.test.ts +24 -0
- package/src/usePortal/usePortal.ts +34 -0
- package/src/usePrevious/usePrevious.test.ts +21 -0
- package/src/usePrevious/usePrevious.ts +9 -0
- package/src/useRealSize/useRealSize.ts +41 -0
- package/src/useResize/useResize.test.ts +31 -0
- package/src/useResize/useResize.ts +18 -0
- package/src/useSSR/useSSR.test.tsx +16 -0
- package/src/useSSR/useSSR.ts +23 -0
- package/src/useScroll/useScroll.test.tsx +25 -0
- package/src/useScroll/useScroll.ts +30 -0
- package/src/useScrollTo/useScrollTo.test.tsx +105 -0
- package/src/useScrollTo/useScrollTo.ts +41 -0
- package/src/useSearchParams/useSearchParams.test.ts +59 -0
- package/src/useSearchParams/useSearchParams.ts +123 -0
- package/src/useSearchParamsEncoded/useSearchParamsEncoded.test.ts +58 -0
- package/src/useSearchParamsEncoded/useSearchParamsEncoded.ts +202 -0
- package/src/useSorting/useSorting.test.ts +40 -0
- package/src/useSorting/useSorting.ts +99 -0
- package/src/useSwiperIndex/useSwiperIndex.ts +30 -0
- package/src/useSyncedRef/useSyncedRef.test.ts +16 -0
- package/src/useSyncedRef/useSyncedRef.ts +18 -0
- package/src/useUI/useUI.test.ts +16 -0
- package/src/useUI/useUI.ts +44 -0
- package/src/useUnit/useUnit.test.tsx +33 -0
- package/src/useUnit/useUnit.ts +5 -0
- package/src/useUnmountEffect/useUnmountEffect.test.ts +15 -0
- package/src/useUnmountEffect/useUnmountEffect.ts +16 -0
- package/src/useUser/useUser.test.ts +22 -0
- package/src/useUser/useUser.ts +20 -0
- package/src/useWindowSize/useWindowSize.test.ts +48 -0
- package/src/useWindowSize/useWindowSize.ts +31 -0
package/CHANGELOG.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -380,7 +380,7 @@ function useBreakpoint() {
|
|
|
380
380
|
throw new Error("useBreakpoint must be used within ThemeProvider");
|
|
381
381
|
}
|
|
382
382
|
const breakpoints = Object.keys(theme.mediaQuery);
|
|
383
|
-
const values = Object.values(theme.mediaQuery);
|
|
383
|
+
const values = Object.values(theme.mediaQuery).map((x) => parseInt(String(x)));
|
|
384
384
|
const breakpoint = (0, import_react7.useMemo)(() => {
|
|
385
385
|
const breakpoint2 = values.reduce((p, c, i) => {
|
|
386
386
|
if (c < windowSize.width) {
|
package/dist/index.mjs
CHANGED
|
@@ -263,7 +263,7 @@ function useBreakpoint() {
|
|
|
263
263
|
throw new Error("useBreakpoint must be used within ThemeProvider");
|
|
264
264
|
}
|
|
265
265
|
const breakpoints = Object.keys(theme.mediaQuery);
|
|
266
|
-
const values = Object.values(theme.mediaQuery);
|
|
266
|
+
const values = Object.values(theme.mediaQuery).map((x) => parseInt(String(x)));
|
|
267
267
|
const breakpoint = useMemo(() => {
|
|
268
268
|
const breakpoint2 = values.reduce((p, c, i) => {
|
|
269
269
|
if (c < windowSize.width) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@websolutespa/bom-mixer-hooks",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Mixer Hooks module of the BOM Repository",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bom",
|
|
@@ -58,14 +58,16 @@
|
|
|
58
58
|
"sideEffects": false,
|
|
59
59
|
"prepack": {
|
|
60
60
|
"types": "./dist/index.d.ts",
|
|
61
|
-
"main": "./dist/index.
|
|
62
|
-
"module": "./dist/index.mjs"
|
|
61
|
+
"main": "./dist/index.mjs",
|
|
62
|
+
"module": "./dist/index.mjs",
|
|
63
|
+
"source": "./src/index.ts"
|
|
63
64
|
},
|
|
64
65
|
"postpack": {
|
|
65
66
|
"types": "./src/index.ts",
|
|
66
67
|
"main": "./src/index.ts"
|
|
67
68
|
},
|
|
68
69
|
"types": "./dist/index.d.ts",
|
|
69
|
-
"main": "./dist/index.
|
|
70
|
-
"module": "./dist/index.mjs"
|
|
70
|
+
"main": "./dist/index.mjs",
|
|
71
|
+
"module": "./dist/index.mjs",
|
|
72
|
+
"source": "./src/index.ts"
|
|
71
73
|
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export * from './useApi/useApi';
|
|
2
|
+
export * from './useBodyScroll/useBodyScroll';
|
|
3
|
+
export * from './useBreakpoint/useBreakpoint';
|
|
4
|
+
export * from './useCart/useCart';
|
|
5
|
+
export * from './useCheckout/useCheckout';
|
|
6
|
+
export * from './useClickAnyWhere/useClickAnyWhere';
|
|
7
|
+
export * from './useClickOut/useClickOut';
|
|
8
|
+
export * from './useCurrency/useCurrency';
|
|
9
|
+
export * from './useCurrentState/useCurrentState';
|
|
10
|
+
export * from './useDateTimeFormat/useDateTimeFormat';
|
|
11
|
+
export * from './useDebounce/useDebounce';
|
|
12
|
+
export * from './useDebounceCallback/useDebounceCallback';
|
|
13
|
+
export * from './useDebugChangedProps/useDebugChangedProps';
|
|
14
|
+
export * from './useDomObserver/useDomObserver';
|
|
15
|
+
export * from './useDrawer/useDrawer';
|
|
16
|
+
export * from './useElementSize/useElementSize';
|
|
17
|
+
export * from './useEventListener/useEventListener';
|
|
18
|
+
export * from './useFilters/filter';
|
|
19
|
+
export * from './useFilters/filter.service';
|
|
20
|
+
export * from './useFilters/useFilters';
|
|
21
|
+
export * from './useGenericContext/useGenericContext';
|
|
22
|
+
export * from './useGeolocation/geolocation.service';
|
|
23
|
+
export * from './useGeolocation/useGeolocation';
|
|
24
|
+
export * from './useIdle/useIdle';
|
|
25
|
+
export * from './useInfiniteLoader/useInfiniteLoader';
|
|
26
|
+
export * from './useIsomorphicLayoutEffect/useIsomorphicLayoutEffect';
|
|
27
|
+
export * from './useKeyboard/useKeyboard';
|
|
28
|
+
export * from './useLabel/useLabel';
|
|
29
|
+
export * from './useLayout/useLayout';
|
|
30
|
+
export * from './useModal/useModal';
|
|
31
|
+
export * from './useMounted/useMounted';
|
|
32
|
+
export * from './useNumber/useNumber';
|
|
33
|
+
export * from './usePage/usePage';
|
|
34
|
+
export * from './usePagination/pagination.service';
|
|
35
|
+
export * from './usePagination/usePagination';
|
|
36
|
+
export * from './usePortal/usePortal';
|
|
37
|
+
export * from './usePrevious/usePrevious';
|
|
38
|
+
export * from './useRealSize/useRealSize';
|
|
39
|
+
export * from './useResize/useResize';
|
|
40
|
+
export * from './useScroll/useScroll';
|
|
41
|
+
export * from './useScrollTo/useScrollTo';
|
|
42
|
+
export * from './useSearchParams/useSearchParams';
|
|
43
|
+
export * from './useSearchParamsEncoded/useSearchParamsEncoded';
|
|
44
|
+
export * from './useSorting/useSorting';
|
|
45
|
+
export * from './useSSR/useSSR';
|
|
46
|
+
export * from './useSwiperIndex/useSwiperIndex';
|
|
47
|
+
export * from './useSyncedRef/useSyncedRef';
|
|
48
|
+
export * from './useUI/useUI';
|
|
49
|
+
export * from './useUnit/useUnit';
|
|
50
|
+
export * from './useUnmountEffect/useUnmountEffect';
|
|
51
|
+
export * from './useUser/useUser';
|
|
52
|
+
export * from './useWindowSize/useWindowSize';
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
*
|
|
56
|
+
* useful hooks
|
|
57
|
+
* https://streamich.github.io/react-use
|
|
58
|
+
* https://github.com/react-hookz/web
|
|
59
|
+
*
|
|
60
|
+
*/
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
2
|
+
import { render, renderHook, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react';
|
|
3
|
+
import { LayoutDefaults } from '@websolutespa/bom-mixer-mock';
|
|
4
|
+
import { getApiUrl } from '@websolutespa/bom-mixer-store';
|
|
5
|
+
import { LayoutProvider } from '../useLayout/useLayout';
|
|
6
|
+
import { useApi, useApiDelete, useApiFetch, useApiGet, useApiPatch, useApiPost, useApiPut } from './useApi';
|
|
7
|
+
|
|
8
|
+
const wrapper = (props: { children: React.ReactNode }) => (
|
|
9
|
+
<LayoutProvider layout={LayoutDefaults}>{props.children}</LayoutProvider>
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
const defaultResponse = {
|
|
13
|
+
ok: true,
|
|
14
|
+
status: 200,
|
|
15
|
+
json: () => Promise.resolve(null),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function mockFetch(
|
|
19
|
+
method: string = 'GET',
|
|
20
|
+
result: any = { message: 'ok' },
|
|
21
|
+
overrideResponse: Partial<typeof defaultResponse> = {}
|
|
22
|
+
) {
|
|
23
|
+
const endpoint = '/endpoint';
|
|
24
|
+
const url = `${getApiUrl(endpoint)}?locale=en&market=ww`;
|
|
25
|
+
const payload = { data: 'data' };
|
|
26
|
+
const response = {
|
|
27
|
+
...defaultResponse,
|
|
28
|
+
...overrideResponse,
|
|
29
|
+
json: () => Promise.resolve(result),
|
|
30
|
+
} as Response;
|
|
31
|
+
global.fetch = jest.fn(() =>
|
|
32
|
+
Promise.resolve(response)
|
|
33
|
+
);
|
|
34
|
+
const request: {
|
|
35
|
+
body?: string;
|
|
36
|
+
headers: { [key: string]: string },
|
|
37
|
+
method: string;
|
|
38
|
+
} = {
|
|
39
|
+
headers: {
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
},
|
|
42
|
+
method,
|
|
43
|
+
};
|
|
44
|
+
if (['POST', 'PATCH', 'PUT'].includes(method)) {
|
|
45
|
+
request.body = JSON.stringify(payload);
|
|
46
|
+
}
|
|
47
|
+
return { endpoint, url, payload, result, request, response };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe('useApi', () => {
|
|
51
|
+
const ENV = process.env;
|
|
52
|
+
const FETCH = global.fetch;
|
|
53
|
+
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
jest.resetModules();
|
|
56
|
+
jest.resetAllMocks();
|
|
57
|
+
jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
58
|
+
process.env = {
|
|
59
|
+
NODE_ENV: 'test',
|
|
60
|
+
NEXT_PUBLIC_URL: 'http://localhost:3000',
|
|
61
|
+
NEXT_PUBLIC_MOCK_FALLBACK_URL: 'http://localhost:3000',
|
|
62
|
+
NEXT_PUBLIC_API: '/api',
|
|
63
|
+
NEXT_PUBLIC_GOOGLE_MAP_API_KEY: 'AIzaSyDvGw6iAoKdRv8mmaC9GeT-LWLPQtA8p60',
|
|
64
|
+
STORE_URL: 'http://localhost:4000',
|
|
65
|
+
STORE_API: '/api',
|
|
66
|
+
STORE_BEARER: '7584f15d12972fb1e7695e998dd5e4c754f46c74d06a08e8d76f556adcd045e48cd52bed6faac098784471ac273ee40243194b9cff7eba3c4a768f8f41d2d51959a7f767b943c7f0170f6e5f632db523803c357083bf7a7bf03ee8e0df2d8ce5cd52e0211283b34d7781313da775018a9e950433d2b6faf711c20e5a63b25243',
|
|
67
|
+
STORE_STRATEGY: 'mock',
|
|
68
|
+
SECRET_COOKIE_PASSWORD: '2gyZ3GDw3LHZQKDhPmPDL3sjREVRXPr8',
|
|
69
|
+
HOOKS_SECRET: '7584f15d12972fb1e7695e998dd5e4',
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
afterAll(() => {
|
|
74
|
+
process.env = ENV;
|
|
75
|
+
global.fetch = FETCH;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('should throw without provider', () => {
|
|
79
|
+
expect(() => {
|
|
80
|
+
renderHook(() => useApi());
|
|
81
|
+
}).toThrow('useGenericContext must be used within a Provider');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('should not throw with provider', () => {
|
|
85
|
+
expect(() => {
|
|
86
|
+
renderHook(() => useApi(), { wrapper });
|
|
87
|
+
}).not.toThrow();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('api.get should return ok', async () => {
|
|
91
|
+
const { result } = renderHook(() => useApi(), { wrapper });
|
|
92
|
+
const mock = mockFetch();
|
|
93
|
+
const response = await result.current.get(mock.endpoint);
|
|
94
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
95
|
+
expect(fetch).toHaveBeenCalledWith(mock.url, mock.request);
|
|
96
|
+
expect(response).toBe(mock.result);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('api.post should return ok', async () => {
|
|
100
|
+
const { result } = renderHook(() => useApi(), { wrapper });
|
|
101
|
+
const mock = mockFetch('POST');
|
|
102
|
+
const response = await result.current.post(mock.endpoint, mock.payload);
|
|
103
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
104
|
+
expect(fetch).toHaveBeenCalledWith(mock.url, mock.request);
|
|
105
|
+
expect(response).toBe(mock.result);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('api.patch should return ok', async () => {
|
|
109
|
+
const { result } = renderHook(() => useApi(), { wrapper });
|
|
110
|
+
const mock = mockFetch('PATCH');
|
|
111
|
+
const response = await result.current.patch(mock.endpoint, mock.payload);
|
|
112
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
113
|
+
expect(fetch).toHaveBeenCalledWith(mock.url, mock.request);
|
|
114
|
+
expect(response).toBe(mock.result);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('api.put should return ok', async () => {
|
|
118
|
+
const { result } = renderHook(() => useApi(), { wrapper });
|
|
119
|
+
const mock = mockFetch('PUT');
|
|
120
|
+
const response = await result.current.put(mock.endpoint, mock.payload);
|
|
121
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
122
|
+
expect(fetch).toHaveBeenCalledWith(mock.url, mock.request);
|
|
123
|
+
expect(response).toBe(mock.result);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('api.delete should return ok', async () => {
|
|
127
|
+
const { result } = renderHook(() => useApi(), { wrapper });
|
|
128
|
+
const mock = mockFetch('DELETE');
|
|
129
|
+
const response = await result.current.delete(mock.endpoint);
|
|
130
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
131
|
+
expect(fetch).toHaveBeenCalledWith(mock.url, mock.request);
|
|
132
|
+
expect(response).toBe(mock.result);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('useApiGet expect result ok', TestMethod('GET'));
|
|
136
|
+
|
|
137
|
+
test('useApiPost expect result ok', TestMethod('POST'));
|
|
138
|
+
|
|
139
|
+
test('useApiPatch expect result ok', TestMethod('PATCH'));
|
|
140
|
+
|
|
141
|
+
test('useApiPut expect result ok', TestMethod('PUT'));
|
|
142
|
+
|
|
143
|
+
test('useApiDelete expect result ok', TestMethod('DELETE'));
|
|
144
|
+
|
|
145
|
+
function TestMethod(method?: string) {
|
|
146
|
+
const useHook = (mock: ReturnType<typeof mockFetch>) => {
|
|
147
|
+
switch (method) {
|
|
148
|
+
case 'GET':
|
|
149
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
150
|
+
return useApiGet(mock.endpoint);
|
|
151
|
+
case 'POST':
|
|
152
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
153
|
+
return useApiPost(mock.endpoint, mock.payload);
|
|
154
|
+
case 'PATCH':
|
|
155
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
156
|
+
return useApiPatch(mock.endpoint, mock.payload);
|
|
157
|
+
case 'PUT':
|
|
158
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
159
|
+
return useApiPut(mock.endpoint, mock.payload);
|
|
160
|
+
case 'DELETE':
|
|
161
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
162
|
+
return useApiDelete(mock.endpoint);
|
|
163
|
+
default:
|
|
164
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
165
|
+
return useApiFetch(mock.endpoint);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
return async () => {
|
|
169
|
+
const mock = mockFetch(method);
|
|
170
|
+
const Wrapper = () => {
|
|
171
|
+
const [response, loading, error, setError] = useHook(mock);
|
|
172
|
+
return (
|
|
173
|
+
<>
|
|
174
|
+
{loading && <div data-testid="loading">loading</div>}
|
|
175
|
+
{response && <div data-testid="result">{JSON.stringify(response)}</div>}
|
|
176
|
+
{error && <div data-testid="error">{JSON.stringify(error)}</div>}
|
|
177
|
+
</>
|
|
178
|
+
);
|
|
179
|
+
};
|
|
180
|
+
render(
|
|
181
|
+
<LayoutProvider layout={LayoutDefaults}><Wrapper /></LayoutProvider>
|
|
182
|
+
);
|
|
183
|
+
expect(screen.getByTestId('loading')).toHaveTextContent('loading');
|
|
184
|
+
await waitForElementToBeRemoved(() => screen.getByTestId('loading'));
|
|
185
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
186
|
+
expect(fetch).toHaveBeenCalledWith(mock.url, mock.request);
|
|
187
|
+
const result = await waitFor(() => screen.getByTestId('result'));
|
|
188
|
+
expect(result).toHaveTextContent(JSON.stringify(mock.result));
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
});
|
|
193
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { FetchRequestOptions } from '@websolutespa/bom-core';
|
|
2
|
+
import { apiDelete, apiFetch, apiGet, apiPatch, apiPost, apiPut } from '@websolutespa/bom-mixer-store';
|
|
3
|
+
import { useHttpFetch } from '../useHttp/useHttp';
|
|
4
|
+
import { useLayout } from '../useLayout/useLayout';
|
|
5
|
+
|
|
6
|
+
export function useApiFetch<T = any>(pathname: string, options: FetchRequestOptions = {}) {
|
|
7
|
+
const layout = useLayout();
|
|
8
|
+
return useHttpFetch<T>(pathname, options, apiFetch, layout);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function useApiGet<T = any>(pathname: string, options: FetchRequestOptions = {}) {
|
|
12
|
+
return useApiFetch<T>(pathname, { ...options, method: 'GET' });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function useApiPost<T = any>(pathname: string, payload: unknown, options: FetchRequestOptions = {}) {
|
|
16
|
+
return useApiFetch<T>(pathname, { ...options, method: 'POST', body: payload ? JSON.stringify(payload) : undefined });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function useApiPut<T = any>(pathname: string, payload: unknown, options: FetchRequestOptions = {}) {
|
|
20
|
+
return useApiFetch<T>(pathname, { ...options, method: 'PUT', body: payload ? JSON.stringify(payload) : undefined });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function useApiPatch<T = any>(pathname: string, payload: unknown, options: FetchRequestOptions = {}) {
|
|
24
|
+
return useApiFetch<T>(pathname, { ...options, method: 'PATCH', body: payload ? JSON.stringify(payload) : undefined });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function useApiDelete<T = any>(pathname: string, options: FetchRequestOptions = {}) {
|
|
28
|
+
return useApiFetch<T>(pathname, { ...options, method: 'DELETE' });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type Api = {
|
|
32
|
+
get: <T = any>(pathname: string, options?: FetchRequestOptions) => Promise<T>;
|
|
33
|
+
post: <T = any>(pathname: string, payload?: unknown, options?: FetchRequestOptions) => Promise<T>;
|
|
34
|
+
put: <T = any>(pathname: string, payload?: unknown, options?: FetchRequestOptions) => Promise<T>;
|
|
35
|
+
patch: <T = any>(pathname: string, payload?: unknown, options?: FetchRequestOptions) => Promise<T>;
|
|
36
|
+
delete: <T = any>(pathname: string, options?: FetchRequestOptions) => Promise<T>;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function useApi(): Api {
|
|
40
|
+
const layout = useLayout();
|
|
41
|
+
return {
|
|
42
|
+
get: async <T = any>(pathname: string, options: FetchRequestOptions = {}) => {
|
|
43
|
+
return apiGet<T>(pathname, options, layout);
|
|
44
|
+
},
|
|
45
|
+
post: async <T = any>(pathname: string, payload?: unknown, options: FetchRequestOptions = {}) => {
|
|
46
|
+
return apiPost<T>(pathname, payload, options, layout);
|
|
47
|
+
},
|
|
48
|
+
put: async <T = any>(pathname: string, payload?: unknown, options: FetchRequestOptions = {}) => {
|
|
49
|
+
return apiPut<T>(pathname, payload, options, layout);
|
|
50
|
+
},
|
|
51
|
+
patch: async <T = any>(pathname: string, payload?: unknown, options: FetchRequestOptions = {}) => {
|
|
52
|
+
return apiPatch<T>(pathname, payload, options, layout);
|
|
53
|
+
},
|
|
54
|
+
delete: async <T = any>(pathname: string, options: FetchRequestOptions = {}) => {
|
|
55
|
+
return apiDelete<T>(pathname, options, layout);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// readme https://testing-library.com/
|
|
2
|
+
import { act, renderHook } from '@testing-library/react';
|
|
3
|
+
import { createRef, RefObject } from 'react';
|
|
4
|
+
import { useBodyScroll } from './useBodyScroll';
|
|
5
|
+
|
|
6
|
+
describe('useBodyScroll', () => {
|
|
7
|
+
jest.useFakeTimers();
|
|
8
|
+
|
|
9
|
+
it('should work correctly', () => {
|
|
10
|
+
const ref = createRef<HTMLDivElement>();
|
|
11
|
+
(ref as any).current = document.createElement('div');
|
|
12
|
+
const { result } = renderHook(() => useBodyScroll(ref));
|
|
13
|
+
let [hidden, setHidden] = result.current;
|
|
14
|
+
expect(hidden).toBe(false);
|
|
15
|
+
act(() => setHidden(true));
|
|
16
|
+
[hidden, setHidden] = result.current;
|
|
17
|
+
expect(hidden).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should set overflow', async () => {
|
|
21
|
+
const ref = createRef<HTMLDivElement>();
|
|
22
|
+
(ref as any).current = document.createElement('div');
|
|
23
|
+
const element = ref.current as HTMLDivElement;
|
|
24
|
+
const { result } = renderHook(() => useBodyScroll(ref));
|
|
25
|
+
act(() => result.current[1](true));
|
|
26
|
+
expect(element.style.overflow).toEqual('hidden');
|
|
27
|
+
act(() => result.current[1](false));
|
|
28
|
+
act(() => jest.advanceTimersByTime(10));
|
|
29
|
+
expect(element.style.overflow).not.toEqual('hidden');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('the last value of overflow should be recovered after setHidden', async () => {
|
|
33
|
+
const ref = createRef<HTMLDivElement>();
|
|
34
|
+
const div = document.createElement('div');
|
|
35
|
+
div.style.overflow = 'scroll';
|
|
36
|
+
(ref as any).current = div;
|
|
37
|
+
const element = ref.current as HTMLDivElement;
|
|
38
|
+
const { result } = renderHook(() => useBodyScroll(ref));
|
|
39
|
+
let [hidden, setHidden] = result.current;
|
|
40
|
+
expect(element.style.overflow).toEqual('scroll');
|
|
41
|
+
act(() => setHidden(true));
|
|
42
|
+
expect(element.style.overflow).toEqual('hidden');
|
|
43
|
+
[hidden, setHidden] = result.current;
|
|
44
|
+
act(() => setHidden(false));
|
|
45
|
+
act(() => jest.advanceTimersByTime(10));
|
|
46
|
+
expect(element.style.overflow).toEqual('scroll');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should work correctly with multiple element', async () => {
|
|
50
|
+
const ref1 = createRef<HTMLDivElement>();
|
|
51
|
+
(ref1 as any).current = document.createElement('div');
|
|
52
|
+
const element1 = ref1.current as HTMLDivElement;
|
|
53
|
+
const { result } = renderHook(() => useBodyScroll(ref1));
|
|
54
|
+
const ref2 = createRef<HTMLDivElement>();
|
|
55
|
+
(ref2 as any).current = document.createElement('div');
|
|
56
|
+
const element2 = ref2.current as HTMLDivElement;
|
|
57
|
+
const { result: result2 } = renderHook(() => useBodyScroll(ref2));
|
|
58
|
+
act(() => result.current[1](true));
|
|
59
|
+
act(() => result2.current[1](true));
|
|
60
|
+
expect(element1.style.overflow).toEqual('hidden');
|
|
61
|
+
expect(element2.style.overflow).toEqual('hidden');
|
|
62
|
+
act(() => result.current[1](false));
|
|
63
|
+
act(() => result2.current[1](false));
|
|
64
|
+
act(() => jest.advanceTimersByTime(10));
|
|
65
|
+
expect(element1.style.overflow).toEqual('');
|
|
66
|
+
expect(element2.style.overflow).toEqual('');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should work correctly with options', async () => {
|
|
70
|
+
const ref = createRef<HTMLDivElement>();
|
|
71
|
+
(ref as any).current = document.createElement('div');
|
|
72
|
+
const element = ref.current as HTMLDivElement;
|
|
73
|
+
const { result } = renderHook(() => useBodyScroll(ref, { delayReset: 300 }));
|
|
74
|
+
const [hidden, setHidden] = result.current;
|
|
75
|
+
act(() => setHidden(true));
|
|
76
|
+
expect(element.style.overflow).toEqual('hidden');
|
|
77
|
+
act(() => setHidden(false));
|
|
78
|
+
act(() => jest.advanceTimersByTime(10));
|
|
79
|
+
expect(element.style.overflow).toEqual('hidden');
|
|
80
|
+
act(() => jest.advanceTimersByTime(100));
|
|
81
|
+
expect(element.style.overflow).toEqual('hidden');
|
|
82
|
+
act(() => jest.advanceTimersByTime(250));
|
|
83
|
+
expect(element.style.overflow).not.toEqual('hidden');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should work correctly when set element repeatedly', () => {
|
|
87
|
+
let _ref: RefObject<HTMLDivElement> | null = null;
|
|
88
|
+
const ref = createRef<HTMLDivElement>();
|
|
89
|
+
(ref as any).current = document.createElement('div');
|
|
90
|
+
_ref = ref;
|
|
91
|
+
const element = ref.current as HTMLDivElement;
|
|
92
|
+
const { result, rerender } = renderHook(() => useBodyScroll(_ref));
|
|
93
|
+
let [hidden, setHidden] = result.current;
|
|
94
|
+
act(() => setHidden(true));
|
|
95
|
+
expect(element.style.overflow).toEqual('hidden');
|
|
96
|
+
// Force tigger rerender at the same value
|
|
97
|
+
_ref = createRef<HTMLDivElement>();
|
|
98
|
+
rerender();
|
|
99
|
+
_ref = ref;
|
|
100
|
+
rerender();
|
|
101
|
+
[hidden, setHidden] = result.current;
|
|
102
|
+
act(() => setHidden(true));
|
|
103
|
+
expect(element.style.overflow).toEqual('hidden');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should set body when missing all params', () => {
|
|
107
|
+
const { result } = renderHook(() => useBodyScroll());
|
|
108
|
+
const [hidden, setHidden] = result.current;
|
|
109
|
+
act(() => setHidden(true));
|
|
110
|
+
expect(document.body.style.overflow).toEqual('hidden');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/* eslint-disable react-hooks/rules-of-hooks */
|
|
2
|
+
import { Dispatch, RefObject, SetStateAction, useEffect, useRef, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
const defaultOptions: BodyScrollOptions = {
|
|
5
|
+
scrollLayer: false,
|
|
6
|
+
delayReset: 0,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const elementStack = new Map<HTMLElement, ElementStackItem>();
|
|
10
|
+
|
|
11
|
+
export function useBodyScroll(elementRef?: RefObject<HTMLElement> | null, options?: BodyScrollOptions): [boolean, Dispatch<SetStateAction<boolean>>] {
|
|
12
|
+
if (typeof document === 'undefined') {
|
|
13
|
+
return [false, (t: boolean) => t] as any;
|
|
14
|
+
}
|
|
15
|
+
elementRef = elementRef || useRef<HTMLElement>(document.body);
|
|
16
|
+
const [hidden, setHidden] = useState<boolean>(false);
|
|
17
|
+
const safeOptions = {
|
|
18
|
+
...defaultOptions,
|
|
19
|
+
...(options || {}),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!elementRef || !elementRef.current) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const lastOverflow = elementRef.current.style.overflow;
|
|
27
|
+
if (hidden) {
|
|
28
|
+
if (elementStack.has(elementRef.current)) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const paddingRight = getOwnerPaddingRight(elementRef.current);
|
|
32
|
+
const scrollbarWidth = getOwnerScrollbarWidth(elementRef.current);
|
|
33
|
+
// console.log('paddingRight', paddingRight, 'scrollbarWidth', scrollbarWidth);
|
|
34
|
+
elementStack.set(elementRef.current, {
|
|
35
|
+
overflow: lastOverflow,
|
|
36
|
+
paddingRight: elementRef.current.style.paddingRight,
|
|
37
|
+
});
|
|
38
|
+
elementRef.current.style.setProperty('--locked-padding-right', `${paddingRight + scrollbarWidth}px`);
|
|
39
|
+
elementRef.current.style.overflow = 'hidden';
|
|
40
|
+
elementRef.current.style.paddingRight = 'var(--locked-padding-right)';
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// reset element overflow
|
|
45
|
+
if (!elementStack.has(elementRef.current)) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const reset = (element: HTMLElement) => {
|
|
50
|
+
const store = elementStack.get(element) as ElementStackItem;
|
|
51
|
+
if (!store) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
element.style.setProperty('--locked-padding-right', '0');
|
|
55
|
+
element.style.overflow = store.overflow;
|
|
56
|
+
element.style.paddingRight = store.paddingRight;
|
|
57
|
+
elementStack.delete(element);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const timer = window.setTimeout(() => {
|
|
61
|
+
reset(elementRef!.current!);
|
|
62
|
+
window.clearTimeout(timer);
|
|
63
|
+
}, safeOptions.delayReset);
|
|
64
|
+
|
|
65
|
+
}, [hidden, elementRef]);
|
|
66
|
+
|
|
67
|
+
return [hidden, setHidden];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function getOwnerPaddingRight(element: Element): number {
|
|
71
|
+
const owner = element?.ownerDocument || document;
|
|
72
|
+
const view = owner.defaultView || window;
|
|
73
|
+
return Number.parseInt(view.getComputedStyle(element).paddingRight, 10) || 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function getOwnerScrollbarWidth(element: Element): number {
|
|
77
|
+
const doc = element?.ownerDocument || document;
|
|
78
|
+
return Math.abs(window.innerWidth - doc.documentElement.clientWidth);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type ElementStackItem = {
|
|
82
|
+
overflow: string;
|
|
83
|
+
paddingRight: string;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export type BodyScrollOptions = {
|
|
87
|
+
scrollLayer?: boolean;
|
|
88
|
+
delayReset?: number;
|
|
89
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { act, renderHook } from '@testing-library/react';
|
|
2
|
+
import { ThemeDefaults } from '@websolutespa/bom-mixer-mock';
|
|
3
|
+
import { ThemeProvider } from 'styled-components';
|
|
4
|
+
import { useBreakpoint } from './useBreakpoint';
|
|
5
|
+
|
|
6
|
+
const windowResize = (dimension: 'width' | 'height', value: number): void => {
|
|
7
|
+
if (dimension === 'width') {
|
|
8
|
+
window.innerWidth = value;
|
|
9
|
+
}
|
|
10
|
+
if (dimension === 'height') {
|
|
11
|
+
window.innerHeight = value;
|
|
12
|
+
}
|
|
13
|
+
window.dispatchEvent(new Event('resize'));
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const wrapper = (props: { children: React.ReactNode }) => (
|
|
17
|
+
<ThemeProvider theme={ThemeDefaults}>{props.children}</ThemeProvider>
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
describe('useBreakpoint', () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('should throw without provider', () => {
|
|
26
|
+
expect(() => {
|
|
27
|
+
renderHook(() => useBreakpoint());
|
|
28
|
+
}).toThrow('useBreakpoint must be used within ThemeProvider');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('should not throw with provider', () => {
|
|
32
|
+
expect(() => {
|
|
33
|
+
renderHook(() => useBreakpoint(), { wrapper });
|
|
34
|
+
}).not.toThrow();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should work correctly', () => {
|
|
38
|
+
const { result } = renderHook(() => useBreakpoint(), { wrapper });
|
|
39
|
+
expect(result.current).toStrictEqual({ key: 'sm', min: 768, max: 1024 });
|
|
40
|
+
act(() => {
|
|
41
|
+
windowResize('width', 1920);
|
|
42
|
+
});
|
|
43
|
+
expect(result.current).toStrictEqual({ key: 'xl', min: 1600, max: Number.POSITIVE_INFINITY });
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useContext, useMemo } from 'react';
|
|
2
|
+
import { ThemeContext } from 'styled-components';
|
|
3
|
+
import { useWindowSize } from '../useWindowSize/useWindowSize';
|
|
4
|
+
|
|
5
|
+
type BreakpointType = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
6
|
+
|
|
7
|
+
type Breakpoint = {
|
|
8
|
+
key: BreakpointType;
|
|
9
|
+
min: number;
|
|
10
|
+
max: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function useBreakpoint(): Breakpoint {
|
|
14
|
+
// todo
|
|
15
|
+
const windowSize = useWindowSize();
|
|
16
|
+
|
|
17
|
+
// const [breakpoint, setBreakpoint] = useState<Breakpoint>({ key: 'xs', min: 0, max: 0 });
|
|
18
|
+
|
|
19
|
+
const theme = useContext(ThemeContext);
|
|
20
|
+
if (!theme) {
|
|
21
|
+
throw new Error('useBreakpoint must be used within ThemeProvider');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const breakpoints: BreakpointType[] = Object.keys(theme.mediaQuery) as BreakpointType[];
|
|
25
|
+
const values: number[] = Object.values(theme.mediaQuery).map(x => parseInt(String(x)));
|
|
26
|
+
|
|
27
|
+
const breakpoint = useMemo(() => {
|
|
28
|
+
const breakpoint = values.reduce((p, c, i) => {
|
|
29
|
+
if (c < windowSize.width) {
|
|
30
|
+
p.key = breakpoints[i];
|
|
31
|
+
p.min = c;
|
|
32
|
+
if (i + 1 < values.length) {
|
|
33
|
+
p.max = values[i + 1];
|
|
34
|
+
} else {
|
|
35
|
+
p.max = Number.POSITIVE_INFINITY;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return p;
|
|
39
|
+
}, { key: 'xs' as BreakpointType, min: -Number.NEGATIVE_INFINITY, max: -Number.NEGATIVE_INFINITY });
|
|
40
|
+
return breakpoint;
|
|
41
|
+
}, [windowSize.width]);
|
|
42
|
+
|
|
43
|
+
return breakpoint;
|
|
44
|
+
}
|