proje-react-panel 1.1.7 → 1.2.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.
- package/__tests__/utils/PreloadCacheHelper.test.ts +25 -0
- package/jest.config.js +18 -0
- package/package.json +4 -2
- package/src/__tests__/utils/PreloadCacheHelper.test.ts +71 -0
- package/src/components/form/Select.tsx +1 -0
- package/src/decorators/form/inputs/SelectInput.ts +3 -2
- package/src/utils/PreloadCacheHelper.ts +54 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
import { OnResult, preloadCacheHelper } from '../../utils/PreloadCacheHelper';
|
2
|
+
import { SelectPreloader } from '../../decorators/form/inputs/SelectInput';
|
3
|
+
import { describe, expect, it, beforeEach, jest } from '@jest/globals';
|
4
|
+
|
5
|
+
interface CacheMap {
|
6
|
+
cache: Map<SelectPreloader<unknown>, { label: string; value: unknown }[]>;
|
7
|
+
asyncQueue: Map<
|
8
|
+
SelectPreloader<unknown>,
|
9
|
+
((result: { label: string; value: unknown }[]) => Promise<void>)[]
|
10
|
+
>;
|
11
|
+
}
|
12
|
+
|
13
|
+
describe('PreloadCacheHelper', () => {
|
14
|
+
beforeEach(() => {
|
15
|
+
// Clear the cache before each test
|
16
|
+
(preloadCacheHelper as unknown as CacheMap).cache = new Map();
|
17
|
+
(preloadCacheHelper as unknown as CacheMap).asyncQueue = new Map();
|
18
|
+
});
|
19
|
+
|
20
|
+
it('should maintain singleton instance', () => {
|
21
|
+
const instance1 = preloadCacheHelper;
|
22
|
+
const instance2 = preloadCacheHelper;
|
23
|
+
expect(instance1).toBe(instance2);
|
24
|
+
});
|
25
|
+
});
|
package/jest.config.js
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
/* eslint-env node */
|
2
|
+
export default {
|
3
|
+
preset: 'ts-jest',
|
4
|
+
testEnvironment: 'node',
|
5
|
+
roots: ['<rootDir>/src'],
|
6
|
+
transform: {
|
7
|
+
'^.+\\.tsx?$': 'ts-jest',
|
8
|
+
},
|
9
|
+
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
|
10
|
+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
11
|
+
moduleNameMapper: {
|
12
|
+
'^@/(.*)$': '<rootDir>/src/$1',
|
13
|
+
},
|
14
|
+
collectCoverage: true,
|
15
|
+
coverageDirectory: 'coverage',
|
16
|
+
coverageReporters: ['text', 'lcov'],
|
17
|
+
verbose: true,
|
18
|
+
};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "proje-react-panel",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.2.0",
|
4
4
|
"type": "module",
|
5
5
|
"description": "",
|
6
6
|
"author": "SEFA DEMİR",
|
@@ -10,7 +10,7 @@
|
|
10
10
|
"source": "src/index.ts",
|
11
11
|
"types": "dist/index.d.ts",
|
12
12
|
"scripts": {
|
13
|
-
"test": "
|
13
|
+
"test": "jest",
|
14
14
|
"build": "rollup -c",
|
15
15
|
"lint": "eslint src --ext .ts,.tsx"
|
16
16
|
},
|
@@ -39,6 +39,7 @@
|
|
39
39
|
"eslint-plugin-react": "^7.37.4",
|
40
40
|
"eslint-plugin-react-hooks": "^5.2.0",
|
41
41
|
"globals": "^16.0.0",
|
42
|
+
"jest": "^29.7.0",
|
42
43
|
"prettier": "^3.5.3",
|
43
44
|
"prettier-eslint": "^16.3.0",
|
44
45
|
"react": "^19.0.0",
|
@@ -53,6 +54,7 @@
|
|
53
54
|
"rollup-plugin-terser": "^7.0.2",
|
54
55
|
"rollup-plugin-typescript2": "^0.36.0",
|
55
56
|
"svgicons2svgfont": "^15.0.1",
|
57
|
+
"ts-jest": "^29.3.4",
|
56
58
|
"typescript": "^5.8.3",
|
57
59
|
"typescript-eslint": "^8.31.1",
|
58
60
|
"use-sync-external-store": "^1.4.0",
|
@@ -0,0 +1,71 @@
|
|
1
|
+
import { OnPreload, OnResult, preloadCacheHelper } from '../../utils/PreloadCacheHelper';
|
2
|
+
import { SelectPreloader } from '../../decorators/form/inputs/SelectInput';
|
3
|
+
import { describe, expect, it, beforeEach, jest } from '@jest/globals';
|
4
|
+
|
5
|
+
interface CacheMap {
|
6
|
+
cache: Map<SelectPreloader<unknown>, { label: string; value: unknown }[]>;
|
7
|
+
asyncQueue: Map<
|
8
|
+
SelectPreloader<unknown>,
|
9
|
+
((result: { label: string; value: unknown }[]) => Promise<void>)[]
|
10
|
+
>;
|
11
|
+
}
|
12
|
+
|
13
|
+
describe('PreloadCacheHelper', () => {
|
14
|
+
beforeEach(() => {
|
15
|
+
// Clear the cache before each test
|
16
|
+
(preloadCacheHelper as unknown as CacheMap).cache = new Map();
|
17
|
+
(preloadCacheHelper as unknown as CacheMap).asyncQueue = new Map();
|
18
|
+
});
|
19
|
+
|
20
|
+
// Generated by AI
|
21
|
+
it('should cache and return results for the same key', async () => {
|
22
|
+
const mockSelectPreloaderKey = jest.fn() as SelectPreloader<number>;
|
23
|
+
const mockOnPreload = jest
|
24
|
+
.fn<OnPreload<number>>()
|
25
|
+
.mockResolvedValue([{ label: 'Test', value: 1 }]);
|
26
|
+
const mockOnResult = jest.fn<OnResult<number>>().mockImplementation(() => {
|
27
|
+
return Promise.resolve();
|
28
|
+
});
|
29
|
+
|
30
|
+
await preloadCacheHelper.setOrGetCache(mockSelectPreloaderKey, mockOnResult, mockOnPreload);
|
31
|
+
await preloadCacheHelper.setOrGetCache(mockSelectPreloaderKey, mockOnResult, mockOnPreload);
|
32
|
+
|
33
|
+
expect(mockSelectPreloaderKey).toHaveBeenCalledTimes(0);
|
34
|
+
expect(mockOnPreload).toHaveBeenCalledTimes(1);
|
35
|
+
expect(mockOnResult).toHaveBeenCalledTimes(2);
|
36
|
+
});
|
37
|
+
|
38
|
+
// Generated by AI
|
39
|
+
it('should handle preload errors gracefully', async () => {
|
40
|
+
const mockSelectPreloaderKey = jest.fn() as SelectPreloader<number>;
|
41
|
+
const mockOnPreload = jest
|
42
|
+
.fn<OnPreload<number>>()
|
43
|
+
.mockRejectedValue(new Error('Preload failed'));
|
44
|
+
const mockOnResult = jest
|
45
|
+
.fn<OnResult<number>>()
|
46
|
+
.mockImplementation(async () => Promise.resolve());
|
47
|
+
|
48
|
+
await expect(
|
49
|
+
preloadCacheHelper.setOrGetCache(mockSelectPreloaderKey, mockOnResult, mockOnPreload)
|
50
|
+
).rejects.toThrow('Preload failed');
|
51
|
+
|
52
|
+
expect(mockOnResult).not.toHaveBeenCalled();
|
53
|
+
});
|
54
|
+
|
55
|
+
// Generated by AI
|
56
|
+
it('should clear async queue after successful preload', async () => {
|
57
|
+
const mockSelectPreloaderKey = jest.fn() as SelectPreloader<number>;
|
58
|
+
const mockOnPreload = jest
|
59
|
+
.fn<OnPreload<number>>()
|
60
|
+
.mockResolvedValue([{ label: 'Test', value: 1 }]);
|
61
|
+
const mockOnResult = jest
|
62
|
+
.fn<OnResult<number>>()
|
63
|
+
.mockImplementation(async () => Promise.resolve());
|
64
|
+
|
65
|
+
await preloadCacheHelper.setOrGetCache(mockSelectPreloaderKey, mockOnResult, mockOnPreload);
|
66
|
+
|
67
|
+
expect((preloadCacheHelper as unknown as CacheMap).asyncQueue.has(mockSelectPreloaderKey)).toBe(
|
68
|
+
false
|
69
|
+
);
|
70
|
+
});
|
71
|
+
});
|
@@ -23,6 +23,7 @@ export function Select<TValue>({ input, fieldName }: SelectProps) {
|
|
23
23
|
const styles = useMemo(() => darkSelectStyles<TValue>(), []);
|
24
24
|
useEffect(() => {
|
25
25
|
if (inputSelect.onSelectPreloader) {
|
26
|
+
const
|
26
27
|
inputSelect.onSelectPreloader().then(option => {
|
27
28
|
setOptions(option);
|
28
29
|
});
|
@@ -1,7 +1,8 @@
|
|
1
1
|
import { ExtendedInput, ExtendedInputOptions, InputConfiguration, InputOptions } from '../Input';
|
2
2
|
|
3
|
+
export type SelectPreloader<T> = () => Promise<{ label: string; value: T }[]>;
|
3
4
|
export interface SelectInputOptions<T> extends InputOptions {
|
4
|
-
onSelectPreloader?:
|
5
|
+
onSelectPreloader?: SelectPreloader<T>;
|
5
6
|
defaultOptions?: { value: T; label: string }[];
|
6
7
|
csvExport?: never;
|
7
8
|
defaultValue?: never;
|
@@ -9,7 +10,7 @@ export interface SelectInputOptions<T> extends InputOptions {
|
|
9
10
|
|
10
11
|
export interface SelectInputConfiguration<T> extends InputConfiguration {
|
11
12
|
type: 'select';
|
12
|
-
onSelectPreloader?:
|
13
|
+
onSelectPreloader?: SelectPreloader<T>;
|
13
14
|
defaultOptions?: { value: T; label: string }[];
|
14
15
|
csvExport?: never;
|
15
16
|
defaultValue?: never;
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import { SelectPreloader } from '../decorators/form/inputs/SelectInput';
|
2
|
+
|
3
|
+
export type OnResult<T> = (result: { label: string; value: T }[]) => Promise<void>;
|
4
|
+
export type OnPreload<T> = () => Promise<{ label: string; value: T }[]>;
|
5
|
+
|
6
|
+
class PreloadCacheHelper {
|
7
|
+
private static instance: PreloadCacheHelper;
|
8
|
+
private cache: Map<SelectPreloader<unknown>, { label: string; value: unknown }[]>;
|
9
|
+
private asyncQueue: Map<
|
10
|
+
SelectPreloader<unknown>,
|
11
|
+
((result: { label: string; value: unknown }[]) => Promise<void>)[]
|
12
|
+
>;
|
13
|
+
|
14
|
+
private constructor() {
|
15
|
+
this.cache = new Map();
|
16
|
+
}
|
17
|
+
|
18
|
+
public async setOrGetCache<T>(
|
19
|
+
key: SelectPreloader<T>,
|
20
|
+
onResult: OnResult<T>,
|
21
|
+
onPreload: OnPreload<T>
|
22
|
+
): Promise<void> {
|
23
|
+
if (this.cache.has(key)) {
|
24
|
+
onResult(this.cache.get(key) as { label: string; value: T }[]);
|
25
|
+
return;
|
26
|
+
}
|
27
|
+
if (this.asyncQueue.get(key) === undefined) {
|
28
|
+
this.asyncQueue.set(key, []);
|
29
|
+
}
|
30
|
+
const length = this.asyncQueue.get(key)!.length;
|
31
|
+
if (!this.asyncQueue.get(key)!.includes(onResult as OnResult<unknown>)) {
|
32
|
+
this.asyncQueue.get(key)?.push(onResult as OnResult<unknown>);
|
33
|
+
} else {
|
34
|
+
return;
|
35
|
+
}
|
36
|
+
if (length > 0) {
|
37
|
+
return;
|
38
|
+
}
|
39
|
+
|
40
|
+
const result = await onPreload();
|
41
|
+
this.cache.set(key, result);
|
42
|
+
this.asyncQueue.get(key)?.forEach(onResult => onResult(result));
|
43
|
+
this.asyncQueue.delete(key);
|
44
|
+
}
|
45
|
+
|
46
|
+
public static getInstance() {
|
47
|
+
if (!PreloadCacheHelper.instance) {
|
48
|
+
PreloadCacheHelper.instance = new PreloadCacheHelper();
|
49
|
+
}
|
50
|
+
return PreloadCacheHelper.instance;
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
export const preloadCacheHelper = PreloadCacheHelper.getInstance();
|