@websolutespa/bom-mixer-hooks 0.1.0 → 0.2.1
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 +25 -13
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/package.json +10 -9
- 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
|
@@ -1,13 +1,25 @@
|
|
|
1
|
-
# @websolutespa/bom-mixer-hooks
|
|
2
|
-
|
|
3
|
-
## 0.1
|
|
4
|
-
|
|
5
|
-
###
|
|
6
|
-
|
|
7
|
-
- Added:
|
|
8
|
-
|
|
9
|
-
## 0.0
|
|
10
|
-
|
|
11
|
-
###
|
|
12
|
-
|
|
13
|
-
- Added:
|
|
1
|
+
# @websolutespa/bom-mixer-hooks
|
|
2
|
+
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Added: source files.
|
|
8
|
+
|
|
9
|
+
## 0.2.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- Added: splat routes.
|
|
14
|
+
|
|
15
|
+
## 0.1.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- Added: @websolutespa/bom-core.
|
|
20
|
+
|
|
21
|
+
## 0.0.2
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- Added: first release.
|
package/dist/index.d.ts
CHANGED
|
@@ -217,7 +217,7 @@ type UseFiltersResultType<T> = {
|
|
|
217
217
|
itemsWithOmittedKeys: (...keys: string[]) => T[];
|
|
218
218
|
};
|
|
219
219
|
|
|
220
|
-
declare const createGenericContext: <T>() => readonly [() => NonNullable<T>, React$1.Provider<T | undefined>];
|
|
220
|
+
declare const createGenericContext: <T>(defaultContext?: T | null) => readonly [() => NonNullable<T>, React$1.Provider<T | undefined>];
|
|
221
221
|
|
|
222
222
|
/**
|
|
223
223
|
* @desc Made compatible with {GeolocationPositionError} and {PositionError} cause
|
package/dist/index.js
CHANGED
|
@@ -196,8 +196,8 @@ function useHttpFetch(pathname, options = { method: "GET" }, service = import_bo
|
|
|
196
196
|
|
|
197
197
|
// src/useGenericContext/useGenericContext.ts
|
|
198
198
|
var import_react2 = require("react");
|
|
199
|
-
var createGenericContext = () => {
|
|
200
|
-
const genericContext = (0, import_react2.createContext)(
|
|
199
|
+
var createGenericContext = (defaultContext = null) => {
|
|
200
|
+
const genericContext = (0, import_react2.createContext)(defaultContext);
|
|
201
201
|
const useGenericContext = () => {
|
|
202
202
|
const context = (0, import_react2.useContext)(genericContext);
|
|
203
203
|
if (!context) {
|
package/dist/index.mjs
CHANGED
|
@@ -79,8 +79,8 @@ function useHttpFetch(pathname, options = { method: "GET" }, service = httpFetch
|
|
|
79
79
|
|
|
80
80
|
// src/useGenericContext/useGenericContext.ts
|
|
81
81
|
import { createContext, useContext } from "react";
|
|
82
|
-
var createGenericContext = () => {
|
|
83
|
-
const genericContext = createContext(
|
|
82
|
+
var createGenericContext = (defaultContext = null) => {
|
|
83
|
+
const genericContext = createContext(defaultContext);
|
|
84
84
|
const useGenericContext = () => {
|
|
85
85
|
const context = useContext(genericContext);
|
|
86
86
|
if (!context) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@websolutespa/bom-mixer-hooks",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Mixer Hooks module of the BOM Repository",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bom",
|
|
@@ -22,12 +22,11 @@
|
|
|
22
22
|
"prepack": "bom prepack",
|
|
23
23
|
"postpack": "bom postpack"
|
|
24
24
|
},
|
|
25
|
-
"dependencies": {
|
|
25
|
+
"dependencies": {},
|
|
26
|
+
"peerDependencies": {
|
|
26
27
|
"@websolutespa/bom-core": "*",
|
|
27
28
|
"@websolutespa/bom-mixer-models": "*",
|
|
28
|
-
"@websolutespa/bom-mixer-store": "*"
|
|
29
|
-
},
|
|
30
|
-
"peerDependencies": {
|
|
29
|
+
"@websolutespa/bom-mixer-store": "*",
|
|
31
30
|
"framer-motion": ">= 7.2.1",
|
|
32
31
|
"iron-session": ">= 6.2.1",
|
|
33
32
|
"next": ">= 12.3.2",
|
|
@@ -59,14 +58,16 @@
|
|
|
59
58
|
"sideEffects": false,
|
|
60
59
|
"prepack": {
|
|
61
60
|
"types": "./dist/index.d.ts",
|
|
62
|
-
"main": "./dist/index.
|
|
63
|
-
"module": "./dist/index.mjs"
|
|
61
|
+
"main": "./dist/index.mjs",
|
|
62
|
+
"module": "./dist/index.mjs",
|
|
63
|
+
"source": "./src/index.ts"
|
|
64
64
|
},
|
|
65
65
|
"postpack": {
|
|
66
66
|
"types": "./src/index.ts",
|
|
67
67
|
"main": "./src/index.ts"
|
|
68
68
|
},
|
|
69
69
|
"types": "./dist/index.d.ts",
|
|
70
|
-
"main": "./dist/index.
|
|
71
|
-
"module": "./dist/index.mjs"
|
|
70
|
+
"main": "./dist/index.mjs",
|
|
71
|
+
"module": "./dist/index.mjs",
|
|
72
|
+
"source": "./src/index.ts"
|
|
72
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
|
+
});
|