Skip to content

Commit 2612c3f

Browse files
author
Daniil Yankouski
committed
Added services|entities|dao|mobx-store samples. Started code coverage by unit tests
1 parent 76c9ed2 commit 2612c3f

File tree

79 files changed

+749
-187
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+749
-187
lines changed

configs/jest/config.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ type JestConfig = Partial<
99
transform: Record<string, string>;
1010
};
1111

12+
process.env.TZ = 'UTC'
13+
1214
const config: JestConfig = {
13-
collectCoverageFrom: ['<rootDir>/src/{domain,data-access,core}/**/*.ts'],
15+
collectCoverageFrom: ['<rootDir>/src/{domain,data,presentation,core}/**/*.ts'],
1416
coverageDirectory: 'coverage',
1517
coveragePathIgnorePatterns: [
1618
'<rootDir>/node_modules',
1719
'<rootDir>/configs',
20+
'<rootDir>/assets',
1821
'<rootDir>/src/presentation/web/index.tsx',
1922
'<rootDir>/src/presentation/web/app.component.tsx',
2023
'.d.ts',
@@ -32,6 +35,11 @@ const config: JestConfig = {
3235
'ts-jest': {
3336
tsconfig: '<rootDir>/tsconfig.json',
3437
},
38+
window: {
39+
document: {
40+
cookie: ''
41+
}
42+
},
3543
...BuildDefine,
3644
},
3745
moduleDirectories: ['<rootDir>/node_modules', '<rootDir>/node_modules/@types', '<rootDir>/src'],

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"email": "[email protected]",
66
"url": "https://vk.com/qdanik"
77
},
8-
"version": "2.1.0",
8+
"version": "2.2.0",
99
"main": "src/presentation/web/index.tsx",
1010
"scripts": {
1111
"vite": "vite --config ./configs/vite/config.ts",
@@ -35,6 +35,7 @@
3535
"mobx-react": "7.3.0",
3636
"react": "17.0.2",
3737
"react-dom": "17.0.2",
38+
"react-helmet": "^6.1.0",
3839
"react-hook-form": "7.27.1",
3940
"react-router": "6.2.2",
4041
"react-router-dom": "6.2.2",

src/containers/containers.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { baseAdapters } from './base';
21
import { Container } from './config';
2+
import { coreModules } from './core';
33

4-
export const containers = new Container({
4+
export const container = new Container({
55
autoBindInjectable: true,
66
});
77

8-
containers.load(baseAdapters);
8+
container.load(coreModules);
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,18 @@ import {
1010
SessionStorageAdapter,
1111
ReactHookFormAdapter,
1212
I18nextAdapter,
13+
WebLoggerAdapter,
1314
} from 'core/adapters';
1415
import { FormType } from 'core/form';
1516
import { HttpClientAdapterType, HttpClientType } from 'core/http';
1617
import { I18nType } from 'core/i18n';
18+
import { LoggerType } from 'core/logger';
19+
import { MobxStoreImpl, MobxStoreType } from 'core/mobx-store';
1720
import { StorageType, CookieStorageName, LocalStorageName, SessionStorageName } from 'core/storage';
1821

19-
export const baseAdapters = new ContainerModule(bind => {
22+
export const coreModules = new ContainerModule(bind => {
23+
bind(LoggerType).to(WebLoggerAdapter);
24+
bind(MobxStoreType).to(MobxStoreImpl).inSingletonScope();
2025
bind(StorageType).to(BrowserCookieAdapter).whenTargetNamed(CookieStorageName);
2126
bind(StorageType).to(LocalStorageAdapter).whenTargetNamed(LocalStorageName);
2227
bind(StorageType).to(SessionStorageAdapter).whenTargetNamed(SessionStorageName);

src/containers/index.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
1-
import { containers } from './containers';
2-
3-
export const AppContainer = containers;
1+
export { container as AppContainer } from './containers';
Lines changed: 176 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,32 @@
1-
import axios, { AxiosRequestConfig } from 'axios';
2-
import { containers } from 'containers/containers';
3-
import { AxiosAdapter } from 'core/adapters';
4-
import { HttpClientType } from 'core/http';
1+
import axios, { AxiosPromise, AxiosRequestConfig } from 'axios';
2+
import { AbortPromise } from 'core/http';
53
import { mockData, mockUrl } from './axios.adapter.mock';
4+
import { AxiosAbortAdapter } from '../axios-abort.adapter';
5+
import { AxiosMemoAdapter } from '../axios-memo.adapter';
6+
import { AxiosAdapter } from '../axios.adapter';
67

78
jest.mock('axios', () => {
89
const mockAxios = {
10+
defaults: {
11+
adapter: jest.fn(config => {
12+
if (!config) {
13+
throw new Error('mockAdapterError');
14+
}
15+
16+
return Promise.resolve();
17+
}),
18+
},
919
delete: jest.fn(() => Promise.resolve()),
1020
get: jest.fn(() => Promise.resolve()),
1121
head: jest.fn(() => Promise.resolve()),
22+
interceptors: {
23+
request: {
24+
use: jest.fn(() => Promise.resolve()),
25+
},
26+
response: {
27+
use: jest.fn(() => Promise.resolve()),
28+
},
29+
},
1230
options: jest.fn(() => Promise.resolve()),
1331
patch: jest.fn(() => Promise.resolve()),
1432
post: jest.fn(() => Promise.resolve()),
@@ -21,19 +39,29 @@ jest.mock('axios', () => {
2139
};
2240
});
2341

24-
describe('AxiosAdapterFake', () => {
42+
describe('AxiosAdapters', () => {
2543
let axiosAdapter: AxiosAdapter;
44+
let axiosAbortAdapter: AxiosAbortAdapter;
45+
let axiosMemoAdapter: AxiosMemoAdapter;
2646
let axiosConfig: AxiosRequestConfig;
2747

28-
describe('should execute methods with correct arguments', () => {
48+
beforeEach(() => {
49+
axiosAbortAdapter = new AxiosAbortAdapter();
50+
axiosMemoAdapter = new AxiosMemoAdapter(axiosAbortAdapter);
51+
axiosAdapter = new AxiosAdapter(axiosMemoAdapter);
52+
});
53+
54+
afterEach(() => {
55+
jest.restoreAllMocks();
56+
});
57+
58+
describe('AxiosAdapter', () => {
2959
beforeEach(() => {
30-
containers.snapshot();
31-
axiosAdapter = containers.get(HttpClientType) as AxiosAdapter;
60+
axiosAdapter.initialize();
3261
axiosConfig = axiosAdapter.getConfig();
3362
});
3463

3564
afterEach(() => {
36-
containers.restore();
3765
jest.restoreAllMocks();
3866
});
3967

@@ -42,46 +70,178 @@ describe('AxiosAdapterFake', () => {
4270
expect(axios.create).toHaveBeenCalledWith(axiosConfig);
4371
});
4472

45-
it('delete', async () => {
73+
it('send DELETE request', async () => {
4674
await axiosAdapter.delete(mockUrl, axiosConfig);
4775

4876
expect(axios.delete).toHaveBeenCalledWith(mockUrl, axiosConfig);
4977
});
5078

51-
it('get', async () => {
79+
it('send GET request', async () => {
5280
await axiosAdapter.get(mockUrl, axiosConfig);
5381

5482
expect(axios.get).toHaveBeenCalledWith(mockUrl, axiosConfig);
5583
});
5684

57-
it('head', async () => {
85+
it('send HEAD request', async () => {
5886
await axiosAdapter.head(mockUrl, axiosConfig);
5987

6088
expect(axios.head).toHaveBeenCalledWith(mockUrl, axiosConfig);
6189
});
6290

63-
it('options', async () => {
91+
it('send OPTIONS request', async () => {
6492
await axiosAdapter.options(mockUrl, axiosConfig);
6593

6694
expect(axios.options).toHaveBeenCalledWith(mockUrl, axiosConfig);
6795
});
6896

69-
it('patch', async () => {
97+
it('send PATCH request', async () => {
7098
await axiosAdapter.patch(mockUrl, mockData, axiosConfig);
7199

72100
expect(axios.patch).toHaveBeenCalledWith(mockUrl, mockData, axiosConfig);
73101
});
74102

75-
it('post', async () => {
103+
it('send POST request', async () => {
76104
await axiosAdapter.post(mockUrl, mockData, axiosConfig);
77105

78106
expect(axios.post).toHaveBeenCalledWith(mockUrl, mockData, axiosConfig);
79107
});
80108

81-
it('put', async () => {
109+
it('send PUT request', async () => {
82110
await axiosAdapter.put(mockUrl, mockData, axiosConfig);
83111

84112
expect(axios.put).toHaveBeenCalledWith(mockUrl, mockData, axiosConfig);
85113
});
114+
115+
it('set REQUEST interceptors', () => {
116+
const callback = () => null;
117+
axiosAdapter.setRequestInterceptors(callback, callback);
118+
119+
expect(axios.interceptors.request.use).toHaveBeenCalledWith(callback, callback);
120+
});
121+
122+
it('set RESPONSE interceptors', () => {
123+
const callback = () => null;
124+
axiosAdapter.setResponseInterceptors(callback, callback);
125+
126+
expect(axios.interceptors.response.use).toHaveBeenCalledWith(callback, callback);
127+
});
128+
});
129+
130+
describe('AxiosMemoAdapter', () => {
131+
let spyAxiosAbortExecute;
132+
133+
beforeEach(() => {
134+
spyAxiosAbortExecute = jest.spyOn(axiosAbortAdapter, 'execute').mockImplementation(
135+
(): AxiosPromise<string> =>
136+
Promise.resolve({
137+
config: {},
138+
data: 'response',
139+
headers: {},
140+
status: 200,
141+
statusText: 'OK',
142+
}),
143+
);
144+
});
145+
146+
afterEach(() => {
147+
spyAxiosAbortExecute.mockReset();
148+
spyAxiosAbortExecute.mockRestore();
149+
});
150+
151+
it('shouldn`t put request into cache', async () => {
152+
const spyGetKey = jest.spyOn(AxiosMemoAdapter, 'getKey').mockImplementation(() => 'key');
153+
const config: AxiosRequestConfig = {
154+
data: { key: 'value' },
155+
method: 'GET',
156+
params: { pages: 2 },
157+
url: 'example.com',
158+
};
159+
await axiosMemoAdapter.execute(config);
160+
161+
expect(spyAxiosAbortExecute).toHaveBeenCalledWith(config);
162+
expect(spyGetKey).toHaveBeenLastCalledWith(config);
163+
164+
await axiosMemoAdapter.execute(config);
165+
166+
expect(spyAxiosAbortExecute).toHaveBeenCalledTimes(2);
167+
expect(spyGetKey).toHaveBeenCalledTimes(2);
168+
spyGetKey.mockReset();
169+
spyGetKey.mockRestore();
170+
});
171+
172+
it('should put request into cache', async () => {
173+
const config: AxiosRequestConfig = {
174+
data: { key: 'value' },
175+
headers: { cached: true },
176+
method: 'GET',
177+
params: { pages: 2 },
178+
url: 'example.com',
179+
};
180+
await axiosMemoAdapter.execute(config);
181+
182+
expect(spyAxiosAbortExecute).toHaveBeenCalledWith(config);
183+
184+
await axiosMemoAdapter.execute(config);
185+
186+
expect(spyAxiosAbortExecute).toHaveBeenCalledTimes(1);
187+
188+
await axiosMemoAdapter.execute({
189+
...config,
190+
headers: {},
191+
});
192+
193+
expect(spyAxiosAbortExecute).toHaveBeenCalledTimes(2);
194+
});
195+
196+
it('should catch an error', async () => {
197+
spyAxiosAbortExecute = jest
198+
.spyOn(axiosAbortAdapter, 'execute')
199+
.mockImplementation((): AxiosPromise<string> => {
200+
throw new Error('mockExecuteError');
201+
});
202+
const config: AxiosRequestConfig = {
203+
data: { key: 'value' },
204+
headers: { cached: true },
205+
method: 'GET',
206+
params: { pages: 2 },
207+
url: 'example.com',
208+
};
209+
try {
210+
await axiosMemoAdapter.execute(config);
211+
} catch (error) {
212+
expect(error).toBeInstanceOf(Error);
213+
expect(error.message).toBe('mockExecuteError');
214+
}
215+
});
216+
});
217+
218+
describe('AxiosAbortAdapter', () => {
219+
it('should execute adapter with default axios adapter', async () => {
220+
const spyAbortController = jest.spyOn(AbortController.prototype, 'abort');
221+
const config: AxiosRequestConfig = {
222+
data: { key: 'value' },
223+
method: 'GET',
224+
params: { pages: 2 },
225+
url: 'example.com',
226+
};
227+
const response: AbortPromise<any> = axiosAbortAdapter.execute(config);
228+
expect(response?.abort).toBeInstanceOf(Function);
229+
response.abort();
230+
expect(spyAbortController).toHaveBeenCalled();
231+
await response;
232+
expect(axios.defaults.adapter).toBeCalledWith(config);
233+
spyAbortController.mockReset();
234+
spyAbortController.mockRestore();
235+
});
236+
237+
it('should execute adapter and catch an error', async () => {
238+
try {
239+
const config: AxiosRequestConfig = null;
240+
await axiosAbortAdapter.execute(config);
241+
} catch (error) {
242+
expect(error).toBeInstanceOf(Error);
243+
expect(error.message).toBe('mockAdapterError');
244+
}
245+
});
86246
});
87247
});

src/core/adapters/axios/axios-abort.adapter.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
import httpAdapter from 'axios/lib/adapters/http';
2-
import { AxiosAdapter, AxiosRequestConfig } from 'axios';
1+
import axios, { AxiosRequestConfig } from 'axios';
32
import { Injectable } from 'containers/config';
43
import { AbortPromise, HttpClientAdapter } from 'core/http';
54

65
export const AxiosAbortName = Symbol('AxiosAbort');
76

87
@Injectable()
98
export class AxiosAbortAdapter implements HttpClientAdapter<AxiosRequestConfig> {
10-
execute: AxiosAdapter = (config: AxiosRequestConfig): AbortPromise<any> => {
9+
execute = (config: AxiosRequestConfig): AbortPromise<any> => {
1110
const controller = new AbortController();
1211
try {
13-
const request = httpAdapter(config);
12+
const request: AbortPromise<any> = axios.defaults.adapter(config);
1413
request.abort = () => controller.abort();
1514

1615
return request;

src/core/adapters/axios/axios-memo.adapter.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AxiosAdapter, AxiosRequestConfig } from 'axios';
1+
import { AxiosRequestConfig } from 'axios';
22
import { Inject, Injectable, Named } from 'containers/config';
33
import { AbortPromise, HttpClientAdapter, HttpClientAdapterType } from 'core/http';
44
import { AxiosAbortName } from './axios-abort.adapter';
@@ -24,18 +24,26 @@ export class AxiosMemoAdapter implements HttpClientAdapter<AxiosRequestConfig> {
2424
});
2525
}
2626

27-
execute: AxiosAdapter = (config: AxiosRequestConfig): AbortPromise<any> => {
27+
execute = (config: AxiosRequestConfig): AbortPromise<any> => {
2828
const key = AxiosMemoAdapter.getKey(config);
2929
try {
30-
if (!this._requests.has(key)) {
31-
this._requests.set(key, this._abortAdapter.execute(config));
30+
const execute = () => this._abortAdapter.execute(config);
31+
32+
if (config.headers?.cached) {
33+
if (!this._requests.has(key)) {
34+
this._requests.set(key, execute());
35+
}
36+
37+
return this._requests.get(key);
38+
}
39+
40+
if (this._requests.has(key)) {
41+
this._requests.delete(key);
3242
}
3343

34-
return this._requests.get(key);
44+
return execute();
3545
} catch (e) {
3646
return Promise.reject(e);
37-
} finally {
38-
this._requests.delete(key);
3947
}
4048
};
4149
}

0 commit comments

Comments
 (0)