proje-react-panel 1.1.6 → 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.
@@ -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
+ });
@@ -54,15 +54,31 @@ function NestedFormFields({ input, register }: NestedFormFieldsProps) {
54
54
 
55
55
  export function FormField({ input, register, error, baseName }: FormFieldProps) {
56
56
  const fieldName: string = (baseName ? baseName.toString() + '.' : '') + input.name || '';
57
+ //TODO: support rest default values
57
58
  const renderField = () => {
58
59
  switch (input.type) {
59
60
  case 'textarea':
60
- return <textarea {...register(fieldName)} placeholder={input.placeholder} />;
61
+ return (
62
+ <textarea
63
+ defaultValue={input.defaultValue}
64
+ {...register(fieldName, {
65
+ value: input.defaultValue,
66
+ })}
67
+ placeholder={input.placeholder}
68
+ />
69
+ );
61
70
  case 'select':
62
71
  return <Select input={input} fieldName={fieldName} />;
63
72
  case 'input': {
64
73
  return (
65
- <input type={input.inputType} {...register(fieldName)} placeholder={input.placeholder} />
74
+ <input
75
+ type={input.inputType}
76
+ defaultValue={input.defaultValue}
77
+ {...register(fieldName, {
78
+ value: input.defaultValue,
79
+ })}
80
+ placeholder={input.placeholder}
81
+ />
66
82
  );
67
83
  }
68
84
  case 'file-upload':
@@ -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
  });
@@ -17,6 +17,7 @@ export interface InputOptions {
17
17
  label?: string;
18
18
  placeholder?: string;
19
19
  nestedFields?: InputConfiguration[];
20
+ defaultValue?: string;
20
21
  includeInCSV?: boolean;
21
22
  includeInJSON?: boolean;
22
23
  }
@@ -32,6 +33,7 @@ export interface InputConfiguration {
32
33
  label?: string;
33
34
  placeholder?: string;
34
35
  nestedFields?: InputConfiguration[];
36
+ defaultValue?: string;
35
37
  includeInCSV: boolean;
36
38
  includeInJSON: boolean;
37
39
  }
@@ -1,15 +1,19 @@
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?: () => Promise<{ label: string; value: T }[]>;
5
+ onSelectPreloader?: SelectPreloader<T>;
5
6
  defaultOptions?: { value: T; label: string }[];
6
7
  csvExport?: never;
8
+ defaultValue?: never;
7
9
  }
8
10
 
9
11
  export interface SelectInputConfiguration<T> extends InputConfiguration {
10
12
  type: 'select';
11
- onSelectPreloader?: () => Promise<{ label: string; value: T }[]>;
13
+ onSelectPreloader?: SelectPreloader<T>;
12
14
  defaultOptions?: { value: T; label: string }[];
15
+ csvExport?: never;
16
+ defaultValue?: never;
13
17
  }
14
18
 
15
19
  export function SelectInput<K>(options?: SelectInputOptions<K>): PropertyDecorator {
@@ -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();