@websolutespa/bom-mixer-hooks 3.1.0 → 3.1.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 CHANGED
@@ -1,5 +1,7 @@
1
1
  # @websolutespa/bom-mixer-hooks
2
2
 
3
+ ## 3.1.1
4
+
3
5
  ## 3.1.0
4
6
 
5
7
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@websolutespa/bom-mixer-hooks",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
4
  "description": "Mixer Hooks module of the BOM Repository",
5
5
  "keywords": [
6
6
  "bom",
@@ -46,10 +46,10 @@
46
46
  "@types/react-dom": "19.2.7",
47
47
  "@types/styled-components": "5.1.34",
48
48
  "@vitest/coverage-istanbul": "5.0.5",
49
- "@websolutespa/bom-cli": "^3.1.0",
50
- "@websolutespa/eslint-config": "^3.1.0",
51
- "@websolutespa/test": "^3.1.0",
52
- "@websolutespa/tsconfig": "^3.1.0",
49
+ "@websolutespa/bom-cli": "^3.1.1",
50
+ "@websolutespa/eslint-config": "^3.1.1",
51
+ "@websolutespa/test": "^3.1.1",
52
+ "@websolutespa/tsconfig": "^3.1.1",
53
53
  "eslint": "10.13.0",
54
54
  "npm-run-all": "4.1.5",
55
55
  "react": "19.2.9",
@@ -89,12 +89,12 @@
89
89
  },
90
90
  "types": "./dist/index.d.ts",
91
91
  "main": "./dist/index.js",
92
- "source": "./src/index.ts",
93
92
  "exports": {
94
93
  ".": {
95
94
  "import": "./dist/index.js",
96
95
  "types": "./dist/index.d.ts",
97
96
  "default": "./dist/index.js"
98
97
  }
99
- }
98
+ },
99
+ "source": "./src/index.ts"
100
100
  }
@@ -1,193 +1,193 @@
1
- import { render, renderHook, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react';
2
- import { ILayout } from '@websolutespa/bom-core';
3
- import { getLocalApiUrl } from '@websolutespa/bom-mixer-store';
4
- import { afterAll, beforeEach, describe, expect, test, vi } from 'vitest';
5
- import { LayoutProvider } from '../useLayout/useLayout';
6
- import { LayoutDefaults } from '../useLayout/useLayout.default';
7
- import { useApi, useApiDelete, useApiFetch, useApiGet, useApiPatch, useApiPost, useApiPut } from './useApi';
8
-
9
- const wrapper = (props: { children: React.ReactNode }) => (
10
- <LayoutProvider layout={LayoutDefaults as ILayout}>{props.children}</LayoutProvider>
11
- );
12
-
13
- const defaultResponse = {
14
- ok: true,
15
- status: 200,
16
- json: () => Promise.resolve(null),
17
- };
18
-
19
- function mockFetch(
20
- method: string = 'GET',
21
- result: any = { message: 'ok' },
22
- overrideResponse: Partial<typeof defaultResponse> = {}
23
- ) {
24
- const endpoint = '/endpoint';
25
- const url = `${getLocalApiUrl(endpoint)}?locale=en&market=ww`;
26
- const payload = { data: 'data' };
27
- const response = {
28
- ...defaultResponse,
29
- ...overrideResponse,
30
- json: () => Promise.resolve(result),
31
- } as Response;
32
- global.fetch = vi.fn(() =>
33
- Promise.resolve(response)
34
- );
35
- const request: {
36
- body?: string;
37
- headers: { [key: string]: string },
38
- method: string;
39
- } = {
40
- headers: {
41
- 'Content-Type': 'application/json',
42
- },
43
- method,
44
- };
45
- if (['POST', 'PATCH', 'PUT'].includes(method)) {
46
- request.body = JSON.stringify(payload);
47
- }
48
- return { endpoint, url, payload, result, request, response };
49
- }
50
-
51
- describe('useApi', () => {
52
- const ENV = process.env;
53
- const FETCH = global.fetch;
54
-
55
- beforeEach(() => {
56
- vi.resetModules();
57
- vi.resetAllMocks();
58
- vi.spyOn(console, 'error').mockImplementation(() => { });
59
- vi.spyOn(console, 'log').mockImplementation(() => { });
60
- process.env = {
61
- MIXER_SECRET: 'MIXER_SECRET_32_CHARS_LONG_12345',
62
- NEXT_PUBLIC_API: '/api',
63
- NEXT_PUBLIC_MOCK_FALLBACK_URL: 'http://localhost:3000',
64
- NEXT_PUBLIC_URL: 'http://localhost:3000',
65
- NODE_ENV: 'test',
66
- STORE_API_KEY: 'STORE_API_KEY',
67
- STORE_API: '/bowl/api',
68
- STORE_STRATEGY: 'mock',
69
- STORE_URL: 'http://localhost:4000',
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 as ILayout}><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
-
1
+ import { render, renderHook, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react';
2
+ import { ILayout } from '@websolutespa/bom-core';
3
+ import { getLocalApiUrl } from '@websolutespa/bom-mixer-store';
4
+ import { afterAll, beforeEach, describe, expect, test, vi } from 'vitest';
5
+ import { LayoutProvider } from '../useLayout/useLayout';
6
+ import { LayoutDefaults } from '../useLayout/useLayout.default';
7
+ import { useApi, useApiDelete, useApiFetch, useApiGet, useApiPatch, useApiPost, useApiPut } from './useApi';
8
+
9
+ const wrapper = (props: { children: React.ReactNode }) => (
10
+ <LayoutProvider layout={LayoutDefaults as ILayout}>{props.children}</LayoutProvider>
11
+ );
12
+
13
+ const defaultResponse = {
14
+ ok: true,
15
+ status: 200,
16
+ json: () => Promise.resolve(null),
17
+ };
18
+
19
+ function mockFetch(
20
+ method: string = 'GET',
21
+ result: any = { message: 'ok' },
22
+ overrideResponse: Partial<typeof defaultResponse> = {}
23
+ ) {
24
+ const endpoint = '/endpoint';
25
+ const url = `${getLocalApiUrl(endpoint)}?locale=en&market=ww`;
26
+ const payload = { data: 'data' };
27
+ const response = {
28
+ ...defaultResponse,
29
+ ...overrideResponse,
30
+ json: () => Promise.resolve(result),
31
+ } as Response;
32
+ global.fetch = vi.fn(() =>
33
+ Promise.resolve(response)
34
+ );
35
+ const request: {
36
+ body?: string;
37
+ headers: { [key: string]: string },
38
+ method: string;
39
+ } = {
40
+ headers: {
41
+ 'Content-Type': 'application/json',
42
+ },
43
+ method,
44
+ };
45
+ if (['POST', 'PATCH', 'PUT'].includes(method)) {
46
+ request.body = JSON.stringify(payload);
47
+ }
48
+ return { endpoint, url, payload, result, request, response };
49
+ }
50
+
51
+ describe('useApi', () => {
52
+ const ENV = process.env;
53
+ const FETCH = global.fetch;
54
+
55
+ beforeEach(() => {
56
+ vi.resetModules();
57
+ vi.resetAllMocks();
58
+ vi.spyOn(console, 'error').mockImplementation(() => { });
59
+ vi.spyOn(console, 'log').mockImplementation(() => { });
60
+ process.env = {
61
+ MIXER_SECRET: 'MIXER_SECRET_32_CHARS_LONG_12345',
62
+ NEXT_PUBLIC_API: '/api',
63
+ NEXT_PUBLIC_MOCK_FALLBACK_URL: 'http://localhost:3000',
64
+ NEXT_PUBLIC_URL: 'http://localhost:3000',
65
+ NODE_ENV: 'test',
66
+ STORE_API_KEY: 'STORE_API_KEY',
67
+ STORE_API: '/bowl/api',
68
+ STORE_STRATEGY: 'mock',
69
+ STORE_URL: 'http://localhost:4000',
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 as ILayout}><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
+