@dr.pogodin/react-utils 1.38.0 → 1.39.0

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.
Files changed (47) hide show
  1. package/build/development/index.js +0 -9
  2. package/build/development/index.js.map +1 -1
  3. package/build/development/shared/components/Button/index.js +4 -0
  4. package/build/development/shared/components/Button/index.js.map +1 -1
  5. package/build/development/shared/components/Input/index.js +2 -0
  6. package/build/development/shared/components/Input/index.js.map +1 -1
  7. package/build/development/shared/components/Modal/index.js +4 -1
  8. package/build/development/shared/components/Modal/index.js.map +1 -1
  9. package/build/development/shared/components/selectors/NativeDropdown/index.js +2 -0
  10. package/build/development/shared/components/selectors/NativeDropdown/index.js.map +1 -1
  11. package/build/development/shared/components/selectors/common.js.map +1 -1
  12. package/build/development/shared/utils/jest/index.js +31 -0
  13. package/build/development/shared/utils/jest/index.js.map +1 -1
  14. package/build/development/style.css +8 -8
  15. package/build/development/web.bundle.js +10 -20
  16. package/build/production/index.js +1 -1
  17. package/build/production/index.js.map +1 -1
  18. package/build/production/shared/components/Button/index.js +1 -1
  19. package/build/production/shared/components/Button/index.js.map +1 -1
  20. package/build/production/shared/components/Input/index.js +1 -1
  21. package/build/production/shared/components/Input/index.js.map +1 -1
  22. package/build/production/shared/components/Modal/index.js +2 -2
  23. package/build/production/shared/components/Modal/index.js.map +1 -1
  24. package/build/production/shared/components/selectors/NativeDropdown/index.js +2 -2
  25. package/build/production/shared/components/selectors/NativeDropdown/index.js.map +1 -1
  26. package/build/production/shared/components/selectors/common.js.map +1 -1
  27. package/build/production/shared/utils/jest/index.js +4 -3
  28. package/build/production/shared/utils/jest/index.js.map +1 -1
  29. package/build/production/style.css +1 -1
  30. package/build/production/style.css.map +1 -1
  31. package/build/production/web.bundle.js +1 -1
  32. package/build/production/web.bundle.js.map +1 -1
  33. package/build/types-code/index.d.ts +0 -1
  34. package/build/types-code/shared/components/Button/index.d.ts +1 -0
  35. package/build/types-code/shared/components/Input/index.d.ts +1 -0
  36. package/build/types-code/shared/components/Modal/index.d.ts +4 -1
  37. package/build/types-code/shared/components/selectors/common.d.ts +1 -0
  38. package/build/types-code/shared/utils/jest/index.d.ts +4 -0
  39. package/package.json +6 -6
  40. package/src/index.ts +0 -2
  41. package/src/shared/components/Button/index.tsx +5 -1
  42. package/src/shared/components/Input/index.tsx +3 -0
  43. package/src/shared/components/Modal/index.tsx +9 -2
  44. package/src/shared/components/Throbber/theme.scss +5 -5
  45. package/src/shared/components/selectors/NativeDropdown/index.tsx +2 -0
  46. package/src/shared/components/selectors/common.ts +1 -0
  47. package/src/shared/utils/jest/index.tsx +48 -0
@@ -2,7 +2,6 @@ import 'styles/global.scss';
2
2
  import type ServerT from './server';
3
3
  declare const server: (typeof ServerT) | null;
4
4
  declare const client: any;
5
- export { default as api } from 'axios';
6
5
  export { type AsyncCollectionLoaderT, type AsyncDataEnvelopeT, type AsyncDataLoaderT, type ForceT, type UseAsyncDataOptionsT, type UseAsyncDataResT, type UseGlobalStateResT, type ValueOrInitializerT, getGlobalState, GlobalStateProvider, newAsyncDataEnvelope, useAsyncCollection, useAsyncData, useGlobalState, withGlobalStateType, } from '@dr.pogodin/react-global-state';
7
6
  export * from './shared/components';
8
7
  export { type Listener, type Theme, config, Barrier, Emitter, isomorphy, getSsrContext, Semaphore, splitComponent, themed, ThemeProvider, time, webpack, withRetries, } from './shared/utils';
@@ -9,6 +9,7 @@ type PropsT = {
9
9
  onMouseDown?: React.MouseEventHandler;
10
10
  openNewTab?: boolean;
11
11
  replace?: boolean;
12
+ testId?: string;
12
13
  theme: Theme<'active' | 'button' | 'disabled'>;
13
14
  to?: object | string;
14
15
  };
@@ -2,6 +2,7 @@ import { type Theme } from '@dr.pogodin/react-themes';
2
2
  type ThemeKeyT = 'container' | 'input' | 'label';
3
3
  declare const _default: import("@dr.pogodin/react-themes").ThemedComponent<React.InputHTMLAttributes<HTMLInputElement> & {
4
4
  label?: React.ReactNode;
5
+ testId?: string;
5
6
  theme: Theme<ThemeKeyT>;
6
7
  } & React.RefAttributes<HTMLInputElement>>;
7
8
  export default _default;
@@ -3,10 +3,13 @@ import { type Theme } from '@dr.pogodin/react-themes';
3
3
  type PropsT = {
4
4
  cancelOnScrolling?: boolean;
5
5
  children?: ReactNode;
6
- containerStyle?: React.CSSProperties;
7
6
  dontDisableScrolling?: boolean;
8
7
  onCancel?: () => void;
8
+ style?: React.CSSProperties;
9
+ testId?: string;
9
10
  theme: Theme<'container' | 'overlay'>;
11
+ /** @deprecated */
12
+ containerStyle?: React.CSSProperties;
10
13
  };
11
14
  /**
12
15
  * The `<Modal>` component implements a simple themeable modal window, wrapped
@@ -11,6 +11,7 @@ export type PropsT<NameT, OnChangeT = React.ChangeEventHandler<HTMLSelectElement
11
11
  label?: React.ReactNode;
12
12
  onChange?: OnChangeT;
13
13
  options?: Readonly<OptionsT<NameT>>;
14
+ testId?: string;
14
15
  theme: Theme<ThemeKeyT>;
15
16
  value?: ValueT;
16
17
  };
@@ -1,3 +1,4 @@
1
+ import type { AxiosRequestConfig, AxiosResponse } from 'axios';
1
2
  import { type ReactNode, act } from 'react';
2
3
  import { type RenderResult } from '@testing-library/react';
3
4
  /**
@@ -25,6 +26,8 @@ export declare function unmockClientSide(): void;
25
26
  * @return {string}
26
27
  */
27
28
  export declare function getMockUuid(seed?: number): string;
29
+ export type AxiosRequestHandlerT = (config: AxiosRequestConfig) => Partial<AxiosResponse> | null | undefined;
30
+ export declare function mockAxios(handlers: AxiosRequestHandlerT[]): any;
28
31
  /**
29
32
  * Advances mock timers, and mock date by the specified time.
30
33
  * @param {number} time Time step [ms].
@@ -34,6 +37,7 @@ export declare function getMockUuid(seed?: number): string;
34
37
  export declare function mockTimer(time: number): Promise<void>;
35
38
  export type MountedSceneT = HTMLElement & {
36
39
  destroy: () => void;
40
+ snapshot: () => void;
37
41
  };
38
42
  /**
39
43
  * Mounts `scene` to the DOM, and returns the root scene element.
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.38.0",
2
+ "version": "1.39.0",
3
3
  "bin": {
4
4
  "react-utils-build": "bin/build.js",
5
5
  "react-utils-setup": "bin/setup.js"
@@ -30,14 +30,14 @@
30
30
  "lodash": "^4.17.21",
31
31
  "morgan": "^1.10.0",
32
32
  "node-forge": "^1.3.1",
33
- "qs": "^6.12.2",
33
+ "qs": "^6.12.3",
34
34
  "raf": "^3.4.1",
35
35
  "react": "^18.3.1",
36
36
  "react-dom": "^18.3.1",
37
37
  "react-helmet": "^6.1.0",
38
38
  "react-router-dom": "^6.24.1",
39
39
  "request-ip": "^3.3.0",
40
- "rimraf": "^5.0.7",
40
+ "rimraf": "^6.0.0",
41
41
  "serialize-javascript": "^6.0.2",
42
42
  "serve-favicon": "^2.5.0",
43
43
  "source-map-support": "^0.5.21",
@@ -59,7 +59,7 @@
59
59
  "@dr.pogodin/babel-plugin-transform-assets": "^1.2.2",
60
60
  "@dr.pogodin/babel-preset-svgr": "^1.8.0",
61
61
  "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
62
- "@testing-library/dom": "^10.3.0",
62
+ "@testing-library/dom": "^10.3.1",
63
63
  "@testing-library/react": "^16.0.0",
64
64
  "@testing-library/user-event": "^14.5.2",
65
65
  "@tsconfig/recommended": "^1.0.7",
@@ -113,7 +113,7 @@
113
113
  "react-refresh": "^0.14.2",
114
114
  "regenerator-runtime": "^0.14.1",
115
115
  "resolve-url-loader": "^5.0.0",
116
- "sass": "^1.77.6",
116
+ "sass": "^1.77.7",
117
117
  "sass-loader": "^14.2.1",
118
118
  "sitemap": "^8.0.0",
119
119
  "stylelint": "^16.6.1",
@@ -123,7 +123,7 @@
123
123
  "tstyche": "^2.0.0",
124
124
  "typed-scss-modules": "^8.0.1",
125
125
  "typescript": "^5.5.3",
126
- "typescript-eslint": "^7.15.0",
126
+ "typescript-eslint": "^7.16.0",
127
127
  "webpack": "^5.92.1",
128
128
  "webpack-dev-middleware": "^7.2.1",
129
129
  "webpack-hot-middleware": "^2.26.1",
package/src/index.ts CHANGED
@@ -8,8 +8,6 @@ const server = webpack.requireWeak('./server', __dirname) as (typeof ServerT) |
8
8
 
9
9
  const client = server ? undefined : require('./client').default;
10
10
 
11
- export { default as api } from 'axios';
12
-
13
11
  export {
14
12
  type AsyncCollectionLoaderT,
15
13
  type AsyncDataEnvelopeT,
@@ -17,6 +17,7 @@ type PropsT = {
17
17
  onMouseDown?: React.MouseEventHandler;
18
18
  openNewTab?: boolean;
19
19
  replace?: boolean;
20
+ testId?: string;
20
21
  theme: Theme<'active' | 'button' | 'disabled'>;
21
22
  // TODO: It needs a more precise typing of the object option.
22
23
  to?: object | string;
@@ -32,6 +33,7 @@ export const BaseButton: React.FunctionComponent<PropsT> = ({
32
33
  onMouseDown,
33
34
  openNewTab,
34
35
  replace,
36
+ testId,
35
37
  theme,
36
38
  to,
37
39
  }) => {
@@ -40,7 +42,7 @@ export const BaseButton: React.FunctionComponent<PropsT> = ({
40
42
  if (disabled) {
41
43
  if (theme.disabled) className += ` ${theme.disabled}`;
42
44
  return (
43
- <div className={className}>
45
+ <div className={className} data-testid={testId}>
44
46
  {children}
45
47
  </div>
46
48
  );
@@ -49,6 +51,7 @@ export const BaseButton: React.FunctionComponent<PropsT> = ({
49
51
  return (
50
52
  <Link
51
53
  className={className}
54
+ data-testid={testId}
52
55
  enforceA={enforceA}
53
56
  onClick={onClick}
54
57
  onMouseDown={onMouseDown}
@@ -64,6 +67,7 @@ export const BaseButton: React.FunctionComponent<PropsT> = ({
64
67
  return (
65
68
  <div
66
69
  className={className}
70
+ data-testid={testId}
67
71
  onClick={onClick}
68
72
  onKeyDown={onClick && ((e) => {
69
73
  if (e.key === 'Enter') onClick(e);
@@ -11,6 +11,7 @@ type ThemeKeyT =
11
11
 
12
12
  type PropsT = React.InputHTMLAttributes<HTMLInputElement> & {
13
13
  label?: React.ReactNode;
14
+ testId?: string;
14
15
  theme: Theme<ThemeKeyT>;
15
16
  };
16
17
 
@@ -26,6 +27,7 @@ type PropsT = React.InputHTMLAttributes<HTMLInputElement> & {
26
27
  const Input = forwardRef<HTMLInputElement, PropsT>((
27
28
  {
28
29
  label,
30
+ testId,
29
31
  theme,
30
32
  ...rest
31
33
  }: PropsT,
@@ -35,6 +37,7 @@ const Input = forwardRef<HTMLInputElement, PropsT>((
35
37
  { label === undefined ? null : <div className={theme.label}>{label}</div> }
36
38
  <input
37
39
  className={theme.input}
40
+ data-testid={testId}
38
41
  ref={ref}
39
42
  {...rest} // eslint-disable-line react/jsx-props-no-spreading
40
43
  />
@@ -17,10 +17,14 @@ import S from './styles.scss';
17
17
  type PropsT = {
18
18
  cancelOnScrolling?: boolean;
19
19
  children?: ReactNode;
20
- containerStyle?: React.CSSProperties;
21
20
  dontDisableScrolling?: boolean;
22
21
  onCancel?: () => void;
22
+ style?: React.CSSProperties;
23
+ testId?: string;
23
24
  theme: Theme<'container' | 'overlay'>;
25
+
26
+ /** @deprecated */
27
+ containerStyle?: React.CSSProperties;
24
28
  };
25
29
 
26
30
  /**
@@ -40,6 +44,8 @@ const BaseModal: React.FunctionComponent<PropsT> = ({
40
44
  containerStyle,
41
45
  dontDisableScrolling,
42
46
  onCancel,
47
+ style,
48
+ testId,
43
49
  theme,
44
50
  }) => {
45
51
  const containerRef = useRef<HTMLDivElement | null>(null);
@@ -141,11 +147,12 @@ const BaseModal: React.FunctionComponent<PropsT> = ({
141
147
  <div
142
148
  aria-modal="true"
143
149
  className={theme.container}
150
+ data-testid={testId}
144
151
  onClick={(e) => e.stopPropagation()}
145
152
  onWheel={(event) => event.stopPropagation()}
146
153
  ref={containerRef}
147
154
  role="dialog"
148
- style={containerStyle}
155
+ style={style ?? containerStyle}
149
156
  >
150
157
  {children}
151
158
  </div>
@@ -1,3 +1,8 @@
1
+ @keyframes bouncing {
2
+ from { top: -0.3em; }
3
+ to { top: 0.3em; }
4
+ }
5
+
1
6
  *,
2
7
  .context,
3
8
  .ad.hoc {
@@ -6,11 +11,6 @@
6
11
  }
7
12
 
8
13
  &.circle {
9
- @keyframes bouncing {
10
- from { top: -0.3em; }
11
- to { top: 0.3em; }
12
- }
13
-
14
14
  animation: bouncing 0.4s ease-in infinite alternate;
15
15
  background: black;
16
16
  border-radius: 0.3em;
@@ -30,6 +30,7 @@ const Dropdown: React.FunctionComponent<PropsT<string>> = ({
30
30
  label,
31
31
  onChange,
32
32
  options,
33
+ testId,
33
34
  theme,
34
35
  value,
35
36
  }) => {
@@ -75,6 +76,7 @@ const Dropdown: React.FunctionComponent<PropsT<string>> = ({
75
76
  <div className={theme.dropdown}>
76
77
  <select
77
78
  className={selectClassName}
79
+ data-testid={testId}
78
80
  onChange={onChange}
79
81
  value={value}
80
82
  >
@@ -38,6 +38,7 @@ export type PropsT<
38
38
  label?: React.ReactNode;
39
39
  onChange?: OnChangeT;
40
40
  options?: Readonly<OptionsT<NameT>>;
41
+ testId?: string;
41
42
  theme: Theme<ThemeKeyT>;
42
43
  value?: ValueT;
43
44
  };
@@ -1,6 +1,12 @@
1
1
  /* global jest, document */
2
2
  /* eslint-disable import/no-extraneous-dependencies */
3
3
 
4
+ import type {
5
+ AxiosRequestConfig,
6
+ AxiosResponse,
7
+ InternalAxiosRequestConfig,
8
+ } from 'axios';
9
+
4
10
  import mockdate from 'mockdate';
5
11
  import { type ReactNode, act } from 'react';
6
12
  import { type Root, createRoot } from 'react-dom/client';
@@ -53,6 +59,43 @@ export function getMockUuid(seed = 0) {
53
59
  return `${x.slice(0, 8)}-${x.slice(8, 12)}-${x.slice(12, 16)}-${x.slice(16, 20)}-${x.slice(20)}`;
54
60
  }
55
61
 
62
+ export type AxiosRequestHandlerT =
63
+ (config: AxiosRequestConfig) => Partial<AxiosResponse> | null | undefined;
64
+
65
+ export function mockAxios(handlers: AxiosRequestHandlerT[]) {
66
+ const axios = jest.requireActual('axios');
67
+
68
+ axios.defaults.adapter = async (config: AxiosRequestConfig): Promise<AxiosResponse> => {
69
+ for (let i = 0; i < handlers.length; ++i) {
70
+ const res = handlers[i]?.(config);
71
+ if (res) {
72
+ return {
73
+ config: config as InternalAxiosRequestConfig,
74
+ data: null,
75
+ headers: {},
76
+ status: 200,
77
+ statusText: 'OK',
78
+ ...res,
79
+ };
80
+ }
81
+ }
82
+
83
+ // Fallback to the regular network request.
84
+ const res = await axios({ ...config, adapter: ['xhr', 'http', 'fetch'] });
85
+
86
+ console.warn(
87
+ 'Network request has not been mocked for a test.\n\nConfig:\n',
88
+ config,
89
+ '\n\nResult:\n',
90
+ JSON.stringify(res, null, 2),
91
+ );
92
+
93
+ return res;
94
+ };
95
+
96
+ return axios;
97
+ }
98
+
56
99
  /**
57
100
  * Advances mock timers, and mock date by the specified time.
58
101
  * @param {number} time Time step [ms].
@@ -66,6 +109,7 @@ export async function mockTimer(time: number) {
66
109
 
67
110
  export type MountedSceneT = HTMLElement & {
68
111
  destroy: () => void;
112
+ snapshot: () => void;
69
113
  };
70
114
 
71
115
  /**
@@ -89,6 +133,10 @@ export function mount(scene: ReactNode): MountedSceneT {
89
133
  res.remove();
90
134
  };
91
135
 
136
+ res.snapshot = () => {
137
+ expect(res).toMatchSnapshot();
138
+ };
139
+
92
140
  // NOTE: As it seems @testing-library may reset this flag to false
93
141
  // when it is simulating user events.
94
142
  global.IS_REACT_ACT_ENVIRONMENT = true;