@finema/core 1.4.25 → 1.4.27
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/dist/module.json +1 -1
- package/dist/module.mjs +3 -2
- package/dist/runtime/components/Form/FieldWrapper.vue +3 -1
- package/dist/runtime/components/Form/Fields.vue +7 -0
- package/dist/runtime/components/Form/InputUploadFileClassicAuto/index.vue +181 -0
- package/dist/runtime/components/Form/InputUploadFileClassicAuto/types.d.ts +13 -0
- package/dist/runtime/components/Form/InputUploadFileClassicAuto/types.mjs +0 -0
- package/dist/runtime/components/Form/types.d.ts +4 -2
- package/dist/runtime/components/Form/types.mjs +1 -0
- package/dist/runtime/composables/useUpload.d.ts +15 -0
- package/dist/runtime/composables/useUpload.mjs +16 -0
- package/dist/runtime/plugin.mjs +13 -0
- package/dist/runtime/types/config.d.ts +1 -1
- package/dist/runtime/ui.config/index.d.ts +1 -0
- package/dist/runtime/ui.config/index.mjs +1 -0
- package/dist/runtime/ui.config/table.mjs +2 -1
- package/dist/runtime/ui.config/uploadFileInputClassicAuto.d.ts +8 -0
- package/dist/runtime/ui.config/uploadFileInputClassicAuto.mjs +8 -0
- package/dist/runtime/utils/ArrayHelper.d.ts +7 -0
- package/dist/runtime/utils/ArrayHelper.mjs +21 -0
- package/dist/runtime/utils/ArrayHelper.spec.d.ts +1 -0
- package/dist/runtime/utils/ArrayHelper.spec.mjs +112 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { defineNuxtModule, createResolver, installModule, addPlugin, addComponen
|
|
|
2
2
|
import 'lodash-es';
|
|
3
3
|
|
|
4
4
|
const name = "@finema/core";
|
|
5
|
-
const version = "1.4.
|
|
5
|
+
const version = "1.4.27";
|
|
6
6
|
|
|
7
7
|
const colors = {
|
|
8
8
|
black: "#20243E",
|
|
@@ -159,7 +159,8 @@ const table = {
|
|
|
159
159
|
class: "-m-1.5 text-gray-700 font-normal"
|
|
160
160
|
},
|
|
161
161
|
loadingState: {
|
|
162
|
-
label: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E42\u0E2B\u0E25\u0E14..."
|
|
162
|
+
label: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E42\u0E2B\u0E25\u0E14...",
|
|
163
|
+
icon: "i-svg-spinners:180-ring-with-bg"
|
|
163
164
|
},
|
|
164
165
|
emptyState: {
|
|
165
166
|
label: "\u0E44\u0E21\u0E48\u0E1E\u0E1A\u0E02\u0E49\u0E2D\u0E21\u0E39\u0E25"
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
:name="name"
|
|
5
5
|
:description="description"
|
|
6
6
|
:hint="hint"
|
|
7
|
-
:size="size"
|
|
7
|
+
:size="size as FormGroupSize"
|
|
8
8
|
:data-testid="name"
|
|
9
9
|
:help="help"
|
|
10
10
|
:error="errorMessage"
|
|
@@ -14,8 +14,10 @@
|
|
|
14
14
|
<slot />
|
|
15
15
|
</UFormGroup>
|
|
16
16
|
</template>
|
|
17
|
+
|
|
17
18
|
<script lang="ts" setup>
|
|
18
19
|
import { type IFieldProps } from '#core/components/Form/types'
|
|
20
|
+
import { type FormGroupSize } from '#ui/types/form-group'
|
|
19
21
|
|
|
20
22
|
defineProps<IFieldProps>()
|
|
21
23
|
</script>
|
|
@@ -91,6 +91,13 @@
|
|
|
91
91
|
v-bind="getFieldBinding(option)"
|
|
92
92
|
v-on="option.on ?? {}"
|
|
93
93
|
/>
|
|
94
|
+
<FormInputUploadFileClassicAuto
|
|
95
|
+
v-else-if="option.type === INPUT_TYPES.UPLOAD_FILE_CLASSIC_AUTO"
|
|
96
|
+
:class="option.class"
|
|
97
|
+
:form="form"
|
|
98
|
+
v-bind="getFieldBinding(option)"
|
|
99
|
+
v-on="option.on ?? {}"
|
|
100
|
+
/>
|
|
94
101
|
</template>
|
|
95
102
|
</div>
|
|
96
103
|
</template>
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<FieldWrapper v-bind="wrapperProps">
|
|
3
|
+
<div :class="[ui.base]">
|
|
4
|
+
<input
|
|
5
|
+
ref="fileInput"
|
|
6
|
+
type="file"
|
|
7
|
+
class="hidden"
|
|
8
|
+
:accept="acceptFile"
|
|
9
|
+
:required="isRequired"
|
|
10
|
+
:disabled="isDisabled"
|
|
11
|
+
@change="handleChange"
|
|
12
|
+
/>
|
|
13
|
+
<div :class="[ui.wrapper]">
|
|
14
|
+
<div :class="[ui.selectFileBox]">
|
|
15
|
+
<Button size="2xs" @click="handleOpenFile">{{ selectFileLabel || 'Choose File' }}</Button>
|
|
16
|
+
<p :class="ui.placeholder">
|
|
17
|
+
{{ selectedFile?.name ?? placeholder ?? 'No file chosen' }}
|
|
18
|
+
</p>
|
|
19
|
+
<Badge v-if="selectedFile" size="xs" variant="outline"> {{ selectedFileSize }} MB </Badge>
|
|
20
|
+
</div>
|
|
21
|
+
<div v-if="selectedFile">
|
|
22
|
+
<Icon
|
|
23
|
+
v-if="upload.status.value.isSuccess"
|
|
24
|
+
name="heroicons:check-circle-20-solid"
|
|
25
|
+
class="text-success"
|
|
26
|
+
/>
|
|
27
|
+
<Icon
|
|
28
|
+
v-if="upload.status.value.isError"
|
|
29
|
+
name="heroicons:x-circle-20-solid"
|
|
30
|
+
class="text-danger"
|
|
31
|
+
/>
|
|
32
|
+
<Icon
|
|
33
|
+
v-if="upload.status.value.isLoading"
|
|
34
|
+
name="i-svg-spinners:180-ring-with-bg"
|
|
35
|
+
class="text-primary"
|
|
36
|
+
/>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
<img v-if="imagePreviewURL && value" :src="imagePreviewURL" alt="" :class="ui.previewURL" />
|
|
41
|
+
</FieldWrapper>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<script lang="tsx" setup>
|
|
45
|
+
import {
|
|
46
|
+
_isEmpty,
|
|
47
|
+
computed,
|
|
48
|
+
ref,
|
|
49
|
+
StringHelper,
|
|
50
|
+
toRef,
|
|
51
|
+
useUI,
|
|
52
|
+
useUiConfig,
|
|
53
|
+
useWatchTrue,
|
|
54
|
+
} from '#imports'
|
|
55
|
+
import { type IUploadFileProps } from './types'
|
|
56
|
+
import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
|
|
57
|
+
import { useFieldHOC } from '#core/composables/useForm'
|
|
58
|
+
import { type IUploadRequest, useUploadLoader } from '#core/composables/useUpload'
|
|
59
|
+
import { uploadFileInputClassicAuto } from '#core/ui.config'
|
|
60
|
+
import i18next from 'i18next'
|
|
61
|
+
|
|
62
|
+
const config = useUiConfig<typeof uploadFileInputClassicAuto>(
|
|
63
|
+
uploadFileInputClassicAuto,
|
|
64
|
+
'uploadFileInput'
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
const props = withDefaults(defineProps<IUploadFileProps>(), {})
|
|
68
|
+
|
|
69
|
+
const { wrapperProps, handleChange: onChange, setErrors, value } = useFieldHOC<string>(props)
|
|
70
|
+
|
|
71
|
+
const request: IUploadRequest = {
|
|
72
|
+
pathURL: props.uploadPathURL,
|
|
73
|
+
requestOptions: props.requestOptions,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const upload = useUploadLoader(request)
|
|
77
|
+
|
|
78
|
+
const fileInput = ref<HTMLInputElement>()
|
|
79
|
+
const selectedFile = ref<File | undefined>()
|
|
80
|
+
const percent = ref<number>(0)
|
|
81
|
+
|
|
82
|
+
const selectedFileSize = computed(() => ((selectedFile.value?.size || 0) / 1000 / 1000).toFixed(2))
|
|
83
|
+
const acceptFileSize = computed(() => props.maxSize)
|
|
84
|
+
const acceptFileSizeMb = computed(() => ((acceptFileSize.value || 0) / 1024).toFixed(2))
|
|
85
|
+
const acceptFile = computed(() =>
|
|
86
|
+
typeof props.accept === 'string' ? props.accept : props.accept?.join(',')
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
const { ui } = useUI('breadcrumb', toRef(props, 'ui'), config)
|
|
90
|
+
|
|
91
|
+
const handleOpenFile = () => {
|
|
92
|
+
fileInput.value?.click()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const handleChange = (e: Event) => {
|
|
96
|
+
const file = (e.target as HTMLInputElement).files?.[0]
|
|
97
|
+
const result = handleCheckFileCondition(file)
|
|
98
|
+
|
|
99
|
+
if (result && file) {
|
|
100
|
+
selectedFile.value = file
|
|
101
|
+
const formData = new FormData()
|
|
102
|
+
|
|
103
|
+
formData.append('file', file)
|
|
104
|
+
upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const handleCheckFileCondition = (file: File | undefined): boolean => {
|
|
109
|
+
if (!file) return false
|
|
110
|
+
const accept = checkAcceptFile(file)
|
|
111
|
+
|
|
112
|
+
if (!accept) {
|
|
113
|
+
setErrors(i18next.t('custom:invalid_file_type'))
|
|
114
|
+
|
|
115
|
+
return false
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const maxSize = checkMaxSize(file)
|
|
119
|
+
|
|
120
|
+
if (!maxSize) {
|
|
121
|
+
setErrors(i18next.t('custom:invalid_file_size', { size: acceptFileSizeMb.value }))
|
|
122
|
+
|
|
123
|
+
return false
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
setErrors('')
|
|
127
|
+
|
|
128
|
+
return true
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const checkAcceptFile = (file: File): boolean => {
|
|
132
|
+
let fileType = ''
|
|
133
|
+
|
|
134
|
+
if (_isEmpty(file.type)) {
|
|
135
|
+
fileType = file.name.split('.').pop() || ''
|
|
136
|
+
} else {
|
|
137
|
+
fileType = file.type.split('/').pop() || ''
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return acceptFile.value ? acceptFile.value.includes(fileType) : true
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const checkMaxSize = (file: File): boolean => {
|
|
144
|
+
if (acceptFileSize.value) {
|
|
145
|
+
return file.size / 1000 <= acceptFileSize.value
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return true
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const onUploadProgress = (progressEvent: ProgressEvent) => {
|
|
152
|
+
percent.value = (Math.floor((progressEvent.loaded * 100) / progressEvent.total) || 0) * 0.8
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const onDownloadProgress = (progressEvent: ProgressEvent) => {
|
|
156
|
+
if (progressEvent.total === 0) {
|
|
157
|
+
percent.value = 100
|
|
158
|
+
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
percent.value = (Math.floor((progressEvent.loaded * 100) / progressEvent.total) || 0) * 0.2 + 80
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
useWatchTrue(
|
|
166
|
+
() => upload.status.value.isSuccess,
|
|
167
|
+
() => {
|
|
168
|
+
if (upload.data.value?.url) {
|
|
169
|
+
value.value = upload.data.value?.url
|
|
170
|
+
onChange(upload.data.value?.url)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
useWatchTrue(
|
|
176
|
+
() => upload.status.value.isError,
|
|
177
|
+
() => {
|
|
178
|
+
setErrors(StringHelper.getError(upload.status.value.errorData))
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
</script>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type AxiosRequestConfig } from 'axios';
|
|
2
|
+
import { type IFieldProps, type IFormFieldBase, type INPUT_TYPES } from '../types';
|
|
3
|
+
export interface IUploadFileProps extends IFieldProps {
|
|
4
|
+
requestOptions: Omit<AxiosRequestConfig, 'baseURL'> & {
|
|
5
|
+
baseURL: string;
|
|
6
|
+
};
|
|
7
|
+
uploadPathURL: string;
|
|
8
|
+
selectFileLabel?: string;
|
|
9
|
+
accept?: string[] | string;
|
|
10
|
+
maxSize?: number;
|
|
11
|
+
imagePreviewURL?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export type IUploadFileField = IFormFieldBase<INPUT_TYPES.UPLOAD_FILE_CLASSIC_AUTO, IUploadFileProps, never>;
|
|
File without changes
|
|
@@ -9,6 +9,7 @@ import { type IToggleField } from '#core/components/Form/InputToggle/types';
|
|
|
9
9
|
import { type ITextareaField } from '#core/components/Form/InputTextarea/types';
|
|
10
10
|
import { type IDateTimeField } from '#core/components/Form/InputDateTime/date_time_field.types';
|
|
11
11
|
import { type IUploadFileClassicField } from '#core/components/Form/InputUploadFileClassic/types';
|
|
12
|
+
import { type IUploadFileField } from '#core/components/Form/InputUploadFileClassicAuto/types';
|
|
12
13
|
export declare const enum INPUT_TYPES {
|
|
13
14
|
TEXT = "TEXT",
|
|
14
15
|
TEXTAREA = "TEXTAREA",
|
|
@@ -21,7 +22,8 @@ export declare const enum INPUT_TYPES {
|
|
|
21
22
|
CHECKBOX = "CHECKBOX",
|
|
22
23
|
DATE_TIME = "DATE_TIME",
|
|
23
24
|
DATE = "DATE",
|
|
24
|
-
UPLOAD_FILE_CLASSIC = "UPLOAD_FILE_CLASSIC"
|
|
25
|
+
UPLOAD_FILE_CLASSIC = "UPLOAD_FILE_CLASSIC",
|
|
26
|
+
UPLOAD_FILE_CLASSIC_AUTO = "UPLOAD_FILE_CLASSIC_AUTO"
|
|
25
27
|
}
|
|
26
28
|
export interface IFieldProps {
|
|
27
29
|
form?: FormContext;
|
|
@@ -50,4 +52,4 @@ export interface IFormFieldBase<I extends INPUT_TYPES, P extends IFieldProps, O>
|
|
|
50
52
|
props: P;
|
|
51
53
|
on?: O;
|
|
52
54
|
}
|
|
53
|
-
export type IFormField = ITextField | IStaticField | ICheckboxField | IRadioField | ISelectField | IToggleField | ITextareaField | IDateTimeField | IUploadFileClassicField;
|
|
55
|
+
export type IFormField = ITextField | IStaticField | ICheckboxField | IRadioField | ISelectField | IToggleField | ITextareaField | IDateTimeField | IUploadFileClassicField | IUploadFileField;
|
|
@@ -11,5 +11,6 @@ export var INPUT_TYPES = /* @__PURE__ */ ((INPUT_TYPES2) => {
|
|
|
11
11
|
INPUT_TYPES2["DATE_TIME"] = "DATE_TIME";
|
|
12
12
|
INPUT_TYPES2["DATE"] = "DATE";
|
|
13
13
|
INPUT_TYPES2["UPLOAD_FILE_CLASSIC"] = "UPLOAD_FILE_CLASSIC";
|
|
14
|
+
INPUT_TYPES2["UPLOAD_FILE_CLASSIC_AUTO"] = "UPLOAD_FILE_CLASSIC_AUTO";
|
|
14
15
|
return INPUT_TYPES2;
|
|
15
16
|
})(INPUT_TYPES || {});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type AxiosRequestConfig } from 'axios';
|
|
2
|
+
export interface IUploadData {
|
|
3
|
+
name: string;
|
|
4
|
+
url: string;
|
|
5
|
+
path: string;
|
|
6
|
+
size: number;
|
|
7
|
+
content_type: string;
|
|
8
|
+
}
|
|
9
|
+
export interface IUploadRequest {
|
|
10
|
+
requestOptions: Omit<AxiosRequestConfig, 'baseURL'> & {
|
|
11
|
+
baseURL: string;
|
|
12
|
+
};
|
|
13
|
+
pathURL: string;
|
|
14
|
+
}
|
|
15
|
+
export declare const useUploadLoader: (request: IUploadRequest) => import("../helpers/apiObjectHelper").IUseObjectLoader<IUploadData, any, Record<string, any>>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useObjectLoader } from "./loaderObject.mjs";
|
|
2
|
+
export const useUploadLoader = (request) => {
|
|
3
|
+
return useObjectLoader({
|
|
4
|
+
method: "post",
|
|
5
|
+
url: request.pathURL,
|
|
6
|
+
getRequestOptions: (_data, opts) => ({
|
|
7
|
+
...request.requestOptions,
|
|
8
|
+
headers: {
|
|
9
|
+
...request.requestOptions.headers,
|
|
10
|
+
"Content-Type": "multipart/form-data"
|
|
11
|
+
},
|
|
12
|
+
onDownloadProgress: opts.data?.onDownloadProgress,
|
|
13
|
+
onUploadProgress: opts.data?.onUploadProgress
|
|
14
|
+
})
|
|
15
|
+
});
|
|
16
|
+
};
|
package/dist/runtime/plugin.mjs
CHANGED
|
@@ -121,7 +121,20 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
121
121
|
set: "set"
|
|
122
122
|
}
|
|
123
123
|
},
|
|
124
|
+
custom: {
|
|
125
|
+
invalid_file_type: "\u0E1B\u0E23\u0E30\u0E40\u0E20\u0E17\u0E44\u0E1F\u0E25\u0E4C\u0E44\u0E21\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07",
|
|
126
|
+
invalid_file_size: "\u0E02\u0E19\u0E32\u0E14\u0E44\u0E1F\u0E25\u0E4C\u0E40\u0E01\u0E34\u0E19\u0E01\u0E27\u0E48\u0E32\u0E17\u0E35\u0E48\u0E01\u0E33\u0E2B\u0E19\u0E14 {{size}} Mb"
|
|
127
|
+
},
|
|
124
128
|
en
|
|
129
|
+
},
|
|
130
|
+
en: {
|
|
131
|
+
zod: {
|
|
132
|
+
...en
|
|
133
|
+
},
|
|
134
|
+
custom: {
|
|
135
|
+
invalid_file_type: "Invalid file type",
|
|
136
|
+
invalid_file_size: "Invalid exceed of file size {{size}} Mb"
|
|
137
|
+
}
|
|
125
138
|
}
|
|
126
139
|
}
|
|
127
140
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export type UIComponentList = 'modal' | 'slideover' | 'dropdown' | 'icon' | 'button' | 'buttonGroup' | 'tabs' | 'card' | 'breadcrumb' | 'badge' | 'input' | 'pagination' | 'notification';
|
|
1
|
+
export type UIComponentList = 'modal' | 'slideover' | 'dropdown' | 'icon' | 'button' | 'buttonGroup' | 'tabs' | 'card' | 'breadcrumb' | 'badge' | 'input' | 'pagination' | 'notification' | 'uploadFileInput';
|
|
@@ -33,7 +33,8 @@ export const table = {
|
|
|
33
33
|
class: "-m-1.5 text-gray-700 font-normal"
|
|
34
34
|
},
|
|
35
35
|
loadingState: {
|
|
36
|
-
label: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E42\u0E2B\u0E25\u0E14..."
|
|
36
|
+
label: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E42\u0E2B\u0E25\u0E14...",
|
|
37
|
+
icon: "i-svg-spinners:180-ring-with-bg"
|
|
37
38
|
},
|
|
38
39
|
emptyState: {
|
|
39
40
|
label: "\u0E44\u0E21\u0E48\u0E1E\u0E1A\u0E02\u0E49\u0E2D\u0E21\u0E39\u0E25"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const uploadFileInputClassicAuto = {
|
|
2
|
+
base: "rounded-lg ring-1 ring-inset ring-gray-300 shadow-sm bg-white px-3 py-2",
|
|
3
|
+
wrapper: "flex items-center justify-between gap-4",
|
|
4
|
+
selectFileBox: "flex items-center gap-2 truncate",
|
|
5
|
+
placeholder: "truncate text-gray-400 text-sm",
|
|
6
|
+
previewURL: "mt-4 max-w-[300px] rounded",
|
|
7
|
+
default: {}
|
|
8
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type IOption } from '../types/common';
|
|
2
|
+
export declare class ArrayHelper {
|
|
3
|
+
static toOptions(data: any[], valueAttr?: string, labelAttr?: string): IOption[];
|
|
4
|
+
static toArray<T>(value: any): Array<T | any>;
|
|
5
|
+
static isEmpty(value: any): boolean;
|
|
6
|
+
static create: (length: number) => any[];
|
|
7
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { _get } from "../utils/lodash.mjs";
|
|
2
|
+
export class ArrayHelper {
|
|
3
|
+
static toOptions(data, valueAttr = "id", labelAttr = "name") {
|
|
4
|
+
return ArrayHelper.toArray(data).map((item) => {
|
|
5
|
+
const value = _get(item, valueAttr, null);
|
|
6
|
+
return {
|
|
7
|
+
value: value || null,
|
|
8
|
+
label: _get(item, labelAttr, value)
|
|
9
|
+
};
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
static toArray(value) {
|
|
13
|
+
return Array.from(value || []);
|
|
14
|
+
}
|
|
15
|
+
static isEmpty(value) {
|
|
16
|
+
return ArrayHelper.toArray(value).length === 0;
|
|
17
|
+
}
|
|
18
|
+
static create = (length) => {
|
|
19
|
+
return Array(...Array(length));
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import { ArrayHelper } from "./ArrayHelper.mjs";
|
|
3
|
+
describe("ArrayHelper", () => {
|
|
4
|
+
test("toOptions default attr", () => {
|
|
5
|
+
const data = [
|
|
6
|
+
{
|
|
7
|
+
id: "1",
|
|
8
|
+
name: "long"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
id: "2",
|
|
12
|
+
name: "long2"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: "3",
|
|
16
|
+
name: "long3"
|
|
17
|
+
}
|
|
18
|
+
];
|
|
19
|
+
expect(ArrayHelper.toOptions(data)).toEqual([
|
|
20
|
+
{
|
|
21
|
+
value: "1",
|
|
22
|
+
label: "long"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
value: "2",
|
|
26
|
+
label: "long2"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
value: "3",
|
|
30
|
+
label: "long3"
|
|
31
|
+
}
|
|
32
|
+
]);
|
|
33
|
+
});
|
|
34
|
+
test("toOptions custom attr", () => {
|
|
35
|
+
const data = [
|
|
36
|
+
{
|
|
37
|
+
ide: "1",
|
|
38
|
+
wtf: "long"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
ide: "2",
|
|
42
|
+
wtf: "long2"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
ide: "3",
|
|
46
|
+
wtf: "long3"
|
|
47
|
+
}
|
|
48
|
+
];
|
|
49
|
+
expect(ArrayHelper.toOptions(data, "ide", "wtf")).toEqual([
|
|
50
|
+
{
|
|
51
|
+
value: "1",
|
|
52
|
+
label: "long"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
value: "2",
|
|
56
|
+
label: "long2"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
value: "3",
|
|
60
|
+
label: "long3"
|
|
61
|
+
}
|
|
62
|
+
]);
|
|
63
|
+
});
|
|
64
|
+
test("toOptions custom attr nested", () => {
|
|
65
|
+
const data = [
|
|
66
|
+
{
|
|
67
|
+
ide: "1",
|
|
68
|
+
wtf: {
|
|
69
|
+
ed: "long"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
ide: "2",
|
|
74
|
+
wtf: {
|
|
75
|
+
ed: "long2"
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
ide: "3",
|
|
80
|
+
wtf: "eiei"
|
|
81
|
+
},
|
|
82
|
+
{}
|
|
83
|
+
];
|
|
84
|
+
expect(ArrayHelper.toOptions(data, "ide", "wtf.ed")).toEqual([
|
|
85
|
+
{
|
|
86
|
+
value: "1",
|
|
87
|
+
label: "long"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
value: "2",
|
|
91
|
+
label: "long2"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
value: "3",
|
|
95
|
+
label: "3"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
value: null,
|
|
99
|
+
label: null
|
|
100
|
+
}
|
|
101
|
+
]);
|
|
102
|
+
});
|
|
103
|
+
test("toArray", () => {
|
|
104
|
+
expect(ArrayHelper.toArray(null)).toEqual([]);
|
|
105
|
+
expect(ArrayHelper.toArray([])).toEqual([]);
|
|
106
|
+
expect(ArrayHelper.toArray("")).toEqual([]);
|
|
107
|
+
expect(ArrayHelper.toArray(false)).toEqual([]);
|
|
108
|
+
expect(ArrayHelper.toArray(true)).toEqual([]);
|
|
109
|
+
expect(ArrayHelper.toArray({})).toEqual([]);
|
|
110
|
+
expect(ArrayHelper.toArray([1, 2, 3])).toEqual([1, 2, 3]);
|
|
111
|
+
});
|
|
112
|
+
});
|