@sankhyalabs/core 5.20.0-dev.8 → 5.20.0-dev.80
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/.docs/classes/Base64Utils.md +39 -0
- package/.docs/classes/Change.md +11 -11
- package/.docs/classes/ColumnFilterManager.md +145 -0
- package/.docs/classes/DataUnit.md +429 -139
- package/.docs/classes/DataUnitInMemoryLoader.md +303 -0
- package/.docs/classes/DataUnitLoaderUtils.md +151 -0
- package/.docs/classes/DateUtils.md +8 -8
- package/.docs/classes/FieldComparator.md +2 -2
- package/.docs/classes/IDBRepository.md +22 -0
- package/.docs/classes/KeyboardManager.md +99 -9
- package/.docs/classes/LockManager.md +249 -0
- package/.docs/classes/MaskFormatter.md +66 -14
- package/.docs/classes/ObjectUtils.md +189 -0
- package/.docs/classes/OverflowWatcher.md +533 -0
- package/.docs/classes/SelectionInfo.md +25 -11
- package/.docs/classes/ServiceCanceledException.md +193 -0
- package/.docs/classes/ServiceUtils.md +67 -0
- package/.docs/classes/SilentException.md +193 -0
- package/.docs/classes/StringUtils.md +33 -9
- package/.docs/classes/UserAgentUtils.md +15 -1
- package/.docs/enumerations/Action.md +41 -21
- package/.docs/enumerations/ChangeOperation.md +4 -4
- package/.docs/enumerations/LockManagerOperation.md +33 -0
- package/.docs/enumerations/OverflowDirection.md +29 -0
- package/.docs/enumerations/RECORD_DATE_FORMAT.md +27 -0
- package/.docs/enumerations/SelectionMode.md +2 -2
- package/.docs/enumerations/StorageType.md +37 -0
- package/.docs/enumerations/UserInterface.md +15 -5
- package/.docs/globals.md +25 -0
- package/.docs/interfaces/DUActionInterceptor.md +1 -1
- package/.docs/interfaces/DataUnitInMemoryLoaderConfig.md +37 -0
- package/.docs/interfaces/IRepository.md +18 -0
- package/.docs/interfaces/LoadDataRequest.md +1 -1
- package/.docs/interfaces/OverFlowWatcherParams.md +67 -0
- package/.docs/interfaces/PageRequest.md +3 -3
- package/.docs/interfaces/PaginationInfo.md +25 -0
- package/.docs/interfaces/PaginationInfoBuilderParams.md +37 -0
- package/.docs/interfaces/QuickFilter.md +3 -3
- package/.docs/interfaces/Record.md +4 -4
- package/.docs/interfaces/SavedRecord.md +5 -5
- package/.docs/interfaces/WaitingChange.md +3 -3
- package/.docs/namespaces/MaskFormatter/type-aliases/MaskCharacter.md +1 -1
- package/.docs/namespaces/MaskFormatter/variables/MaskCharacter.md +1 -1
- package/.docs/type-aliases/DataUnitEventOptions.md +17 -0
- package/.docs/type-aliases/OnOverflowCallBack.md +25 -0
- package/.docs/variables/OVERFLOWED_CLASS_NAME.md +13 -0
- package/.releaserc +1 -0
- package/bun.lockb +0 -0
- package/dist/dataunit/DataUnit.d.ts +92 -13
- package/dist/dataunit/DataUnit.js +227 -71
- package/dist/dataunit/DataUnit.js.map +1 -1
- package/dist/dataunit/DataUnitHelper.js +6 -5
- package/dist/dataunit/DataUnitHelper.js.map +1 -1
- package/dist/dataunit/formatting/PrettyFormatter.js +17 -6
- package/dist/dataunit/formatting/PrettyFormatter.js.map +1 -1
- package/dist/dataunit/loader/DataUnitInMemoryLoaderConfig.d.ts +9 -0
- package/dist/dataunit/loader/DataUnitInMemoryLoaderConfig.js +6 -0
- package/dist/dataunit/loader/DataUnitInMemoryLoaderConfig.js.map +1 -0
- package/dist/dataunit/loader/dataUnitInMemoryLoader.d.ts +25 -0
- package/dist/dataunit/loader/dataUnitInMemoryLoader.js +131 -0
- package/dist/dataunit/loader/dataUnitInMemoryLoader.js.map +1 -0
- package/dist/dataunit/loader/utils/dataUnitLoaderUtils.d.ts +20 -0
- package/dist/dataunit/loader/utils/dataUnitLoaderUtils.js +62 -0
- package/dist/dataunit/loader/utils/dataUnitLoaderUtils.js.map +1 -0
- package/dist/dataunit/loading/LoadDataRequest.d.ts +1 -1
- package/dist/dataunit/loading/PaginationInfo.d.ts +8 -0
- package/dist/dataunit/metadata/DataType.js +7 -1
- package/dist/dataunit/metadata/DataType.js.map +1 -1
- package/dist/dataunit/metadata/UnitMetadata.d.ts +1 -0
- package/dist/dataunit/metadata/UnitMetadata.js +1 -0
- package/dist/dataunit/metadata/UnitMetadata.js.map +1 -1
- package/dist/dataunit/sorting/FieldComparator.d.ts +2 -2
- package/dist/dataunit/sorting/FieldComparator.js +4 -9
- package/dist/dataunit/sorting/FieldComparator.js.map +1 -1
- package/dist/dataunit/state/action/DataUnitAction.d.ts +2 -0
- package/dist/dataunit/state/action/DataUnitAction.js +2 -0
- package/dist/dataunit/state/action/DataUnitAction.js.map +1 -1
- package/dist/dataunit/state/slice/LoadingControlSlice.js +16 -0
- package/dist/dataunit/state/slice/LoadingControlSlice.js.map +1 -1
- package/dist/dataunit/state/slice/RecordsSlice.js +1 -1
- package/dist/dataunit/state/slice/RecordsSlice.js.map +1 -1
- package/dist/dataunit/state/slice/SelectionSlice.js +4 -4
- package/dist/dataunit/state/slice/SelectionSlice.js.map +1 -1
- package/dist/exceptions/ServiceCanceledException.d.ts +14 -0
- package/dist/exceptions/ServiceCanceledException.js +13 -0
- package/dist/exceptions/ServiceCanceledException.js.map +1 -0
- package/dist/exceptions/SilentException.d.ts +14 -0
- package/dist/exceptions/SilentException.js +13 -0
- package/dist/exceptions/SilentException.js.map +1 -0
- package/dist/index.d.ts +13 -2
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/repository/IRepository.d.ts +6 -0
- package/dist/repository/indexeddb/IDBRepository.d.ts +1 -0
- package/dist/repository/indexeddb/IDBRepository.js +3 -0
- package/dist/repository/indexeddb/IDBRepository.js.map +1 -1
- package/dist/utils/Base64Utils.d.ts +7 -0
- package/dist/utils/Base64Utils.js +13 -0
- package/dist/utils/Base64Utils.js.map +1 -0
- package/dist/utils/CacheManager/index.d.ts +52 -0
- package/dist/utils/CacheManager/index.js +101 -0
- package/dist/utils/CacheManager/index.js.map +1 -0
- package/dist/utils/CacheManager/interfaces/index.d.ts +5 -0
- package/dist/utils/CacheManager/interfaces/index.js +7 -0
- package/dist/utils/CacheManager/interfaces/index.js.map +1 -0
- package/dist/utils/ColumnFilterManager.d.ts +19 -0
- package/dist/utils/ColumnFilterManager.js +73 -0
- package/dist/utils/ColumnFilterManager.js.map +1 -0
- package/dist/utils/DateUtils.js +3 -0
- package/dist/utils/DateUtils.js.map +1 -1
- package/dist/utils/ElementUtils.d.ts +2 -0
- package/dist/utils/ElementUtils.js +9 -0
- package/dist/utils/ElementUtils.js.map +1 -0
- package/dist/utils/KeyboardManager/index.d.ts +9 -0
- package/dist/utils/KeyboardManager/index.js +45 -1
- package/dist/utils/KeyboardManager/index.js.map +1 -1
- package/dist/utils/KeyboardManager/interface.d.ts +1 -0
- package/dist/utils/LockManager.d.ts +58 -0
- package/dist/utils/LockManager.js +188 -0
- package/dist/utils/LockManager.js.map +1 -0
- package/dist/utils/MaskFormatter.d.ts +16 -1
- package/dist/utils/MaskFormatter.js +82 -2
- package/dist/utils/MaskFormatter.js.map +1 -1
- package/dist/utils/ObjectUtils.d.ts +52 -0
- package/dist/utils/ObjectUtils.js +71 -0
- package/dist/utils/ObjectUtils.js.map +1 -1
- package/dist/utils/OnboardingUtils.js +1 -1
- package/dist/utils/OnboardingUtils.js.map +1 -1
- package/dist/utils/OverflowWatcher/index.d.ts +59 -0
- package/dist/utils/OverflowWatcher/index.js +188 -0
- package/dist/utils/OverflowWatcher/index.js.map +1 -0
- package/dist/utils/OverflowWatcher/types/overflow-callback.d.ts +6 -0
- package/dist/utils/OverflowWatcher/types/overflow-callback.js +2 -0
- package/dist/utils/OverflowWatcher/types/overflow-callback.js.map +1 -0
- package/dist/utils/OverflowWatcher/types/overflow-direction.d.ts +7 -0
- package/dist/utils/OverflowWatcher/types/overflow-direction.js +9 -0
- package/dist/utils/OverflowWatcher/types/overflow-direction.js.map +1 -0
- package/dist/utils/ServiceUtils.d.ts +24 -0
- package/dist/utils/ServiceUtils.js +40 -0
- package/dist/utils/ServiceUtils.js.map +1 -0
- package/dist/utils/SortingUtils.d.ts +9 -0
- package/dist/utils/SortingUtils.js +24 -0
- package/dist/utils/SortingUtils.js.map +1 -0
- package/dist/utils/StringUtils.d.ts +6 -0
- package/dist/utils/StringUtils.js +23 -6
- package/dist/utils/StringUtils.js.map +1 -1
- package/dist/utils/UserAgentUtils/index.d.ts +1 -0
- package/dist/utils/UserAgentUtils/index.js +5 -0
- package/dist/utils/UserAgentUtils/index.js.map +1 -1
- package/jest.config.ts +2 -0
- package/package.json +2 -1
- package/reports/test-report.xml +760 -0
- package/setupTests.js +7 -0
- package/sonar-project.properties +6 -3
- package/src/dataunit/DataUnit.ts +278 -86
- package/src/dataunit/DataUnitHelper.ts +6 -5
- package/src/dataunit/formatting/PrettyFormatter.ts +19 -6
- package/src/dataunit/loader/DataUnitInMemoryLoaderConfig.ts +10 -0
- package/src/dataunit/loader/dataUnitInMemoryLoader.ts +176 -0
- package/src/dataunit/loader/utils/dataUnitLoaderUtils.ts +86 -0
- package/src/dataunit/loading/LoadDataRequest.ts +1 -1
- package/src/dataunit/loading/PaginationInfo.ts +10 -0
- package/src/dataunit/metadata/DataType.ts +8 -1
- package/src/dataunit/metadata/UnitMetadata.ts +1 -0
- package/src/dataunit/sorting/FieldComparator.ts +18 -32
- package/src/dataunit/state/action/DataUnitAction.ts +2 -0
- package/src/dataunit/state/slice/LoadingControlSlice.ts +42 -0
- package/src/dataunit/state/slice/RecordsSlice.ts +1 -1
- package/src/dataunit/state/slice/SelectionSlice.ts +4 -4
- package/src/dataunit/state/slice/test/RecordsSlice.spec.ts +45 -0
- package/src/dataunit/test/DataUnit.spec.ts +44 -0
- package/src/exceptions/ServiceCanceledException.ts +25 -0
- package/src/exceptions/SilentException.ts +25 -0
- package/src/index.ts +32 -1
- package/src/repository/IRepository.ts +7 -0
- package/src/repository/indexeddb/IDBRepository.ts +4 -0
- package/src/utils/Base64Utils.ts +13 -0
- package/src/utils/CacheManager/index.ts +103 -0
- package/src/utils/CacheManager/interfaces/index.ts +5 -0
- package/src/utils/ColumnFilterManager.ts +104 -0
- package/src/utils/DateUtils.ts +3 -0
- package/src/utils/ElementUtils.ts +10 -0
- package/src/utils/KeyboardManager/index.ts +57 -0
- package/src/utils/KeyboardManager/interface.ts +1 -0
- package/src/utils/LockManager.ts +207 -0
- package/src/utils/MaskFormatter.ts +93 -2
- package/src/utils/ObjectUtils.ts +77 -0
- package/src/utils/OnboardingUtils.ts +1 -1
- package/src/utils/OverflowWatcher/index.ts +243 -0
- package/src/utils/OverflowWatcher/types/overflow-callback.ts +6 -0
- package/src/utils/OverflowWatcher/types/overflow-direction.ts +7 -0
- package/src/utils/ServiceUtils.ts +36 -0
- package/src/utils/SortingUtils.ts +30 -0
- package/src/utils/StringUtils.ts +23 -6
- package/src/utils/UserAgentUtils/index.ts +6 -1
- package/test/dataunit/formatting/PrettyFormatter.spec.ts +177 -0
- package/test/dataunit/loader/dataUnitInMemoryLoader.spec.ts +221 -0
- package/test/dataunit/loader/utils/dataUnitLoaderUtils.spec.ts +158 -0
- package/test/testCases/NumberUtilsTestCases.ts +190 -0
- package/test/testCases/StringUtilsTestCases.ts +435 -0
- package/test/testCases/TimeFormatterTestUtils.ts +43 -0
- package/test/util/ColumnFilterManager.spec.ts +133 -0
- package/test/util/ElementUtils.spec.ts +34 -0
- package/test/util/NumberUtils.spec.ts +72 -150
- package/test/util/ObjectUtils.spec.ts +572 -0
- package/test/util/OverflowWatcher.spec.ts +152 -0
- package/test/util/StringUtils.spec.ts +260 -36
- package/test/util/TimeFormatter.spec.ts +65 -18
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { CacheManager } from './CacheManager/index.js';
|
|
2
|
+
import { StorageType } from './CacheManager/interfaces/index.js';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export class ServiceUtils {
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Auxilia no uso do CacheManager, gerando automaticamente uma chave de cache com base no identificador.
|
|
10
|
+
*
|
|
11
|
+
* @template T Tipo do dado a ser retornado.
|
|
12
|
+
* @param identifier Identificadores únicos usados para compor a chave de cache.
|
|
13
|
+
* @param fetchFunction Função que retorna uma `Promise` com o valor a ser armazenado no cache caso ele não exista ou tenha expirado.
|
|
14
|
+
* @param storageType Tipo de armazenamento: `'sessionStorage'` ou `'localStorage'`. O padrão é `'sessionStorage'`.
|
|
15
|
+
* @returns Uma `Promise` com o valor armazenado ou obtido via `fetchFunction`.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const actions = await useCacheWithService(
|
|
20
|
+
* `${this.entityName} - ${this.resourceID}`,
|
|
21
|
+
* async () => {
|
|
22
|
+
* return await fetchActionsFromAPI();
|
|
23
|
+
* }
|
|
24
|
+
* );
|
|
25
|
+
* console.log(actions);
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
public static async useCacheWithService<T>(
|
|
29
|
+
identifier: string,
|
|
30
|
+
fetchFunction: () => Promise<T>,
|
|
31
|
+
storageType: StorageType = StorageType.IN_MEMORY_CACHE
|
|
32
|
+
): Promise<T> {
|
|
33
|
+
const cacheKey = `${identifier}`;
|
|
34
|
+
return CacheManager.getOrSet(cacheKey, fetchFunction, storageType);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import DataUnit from "../dataunit/DataUnit.js";
|
|
2
|
+
import { FieldDescriptor, Sort, SortMode } from "../dataunit/metadata/UnitMetadata.js";
|
|
3
|
+
import { FieldComparator } from "../dataunit/sorting/FieldComparator.js";
|
|
4
|
+
import { Record } from '../dataunit/DataUnit.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `SortingUtils`: Utilizado para auxiliar na ordenacao de registros.
|
|
8
|
+
*/
|
|
9
|
+
export default class SortingUtils {
|
|
10
|
+
|
|
11
|
+
public static getSortingFunction(dataUnit: DataUnit, sorting?: Array<Sort>): ((recordA: Record, recordB: Record) => number) | undefined {
|
|
12
|
+
|
|
13
|
+
if (sorting == undefined || sorting.length == 0) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return (recordA, recordB) => {
|
|
18
|
+
for (const sort of sorting) {
|
|
19
|
+
if (sort.field){
|
|
20
|
+
const result = FieldComparator.compare(dataUnit.getField(sort.field) as FieldDescriptor, recordA, recordB, sort.mode === SortMode.ASC);
|
|
21
|
+
if (result != 0) {
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return 0;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
}
|
package/src/utils/StringUtils.ts
CHANGED
|
@@ -289,7 +289,7 @@ export class StringUtils {
|
|
|
289
289
|
* @returns String convertida em PascalCase.
|
|
290
290
|
*/
|
|
291
291
|
static toPascalCase(value: string): string{
|
|
292
|
-
return value
|
|
292
|
+
return (value || "")
|
|
293
293
|
.toLowerCase()
|
|
294
294
|
.replace(/(?:^|\s)\w/g, (match: string) => match.toUpperCase())
|
|
295
295
|
.replace(/[\s-_]/g,'');
|
|
@@ -301,7 +301,7 @@ export class StringUtils {
|
|
|
301
301
|
* @returns String convertida em snake_case.
|
|
302
302
|
*/
|
|
303
303
|
static toSnakeCase(value: string): string{
|
|
304
|
-
return value
|
|
304
|
+
return (value || "")
|
|
305
305
|
.toLowerCase()
|
|
306
306
|
.replace(/[A-Z]/g, (match: string) => `_${match.toLowerCase()}`)
|
|
307
307
|
.replace(/[\s-]/g, '_')
|
|
@@ -314,7 +314,7 @@ export class StringUtils {
|
|
|
314
314
|
* @returns String convertida em KebabCase.
|
|
315
315
|
*/
|
|
316
316
|
static toKebabCase(value: string): string{
|
|
317
|
-
return value.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/[\s_]+/g, '-').toLowerCase();
|
|
317
|
+
return (value || "").replace(/([a-z])([A-Z])/g, '$1-$2').replace(/[\s_]+/g, '-').toLowerCase();
|
|
318
318
|
}
|
|
319
319
|
|
|
320
320
|
/**
|
|
@@ -336,13 +336,13 @@ export class StringUtils {
|
|
|
336
336
|
const units = ["B", "KB", "MB", "GB"];
|
|
337
337
|
|
|
338
338
|
if (bytes < 1024) {
|
|
339
|
-
return `${bytes
|
|
339
|
+
return `${bytes?.toString()}B`;
|
|
340
340
|
}
|
|
341
341
|
|
|
342
342
|
const base = Math.log(bytes) / Math.log(1024);
|
|
343
343
|
const offSet = Math.floor(base);
|
|
344
344
|
if (offSet >= units.length) {
|
|
345
|
-
return `${bytes
|
|
345
|
+
return `${bytes?.toString()}B`;
|
|
346
346
|
}
|
|
347
347
|
|
|
348
348
|
const value = this.prettyPrecision(Math.pow(1024, base - offSet).toFixed(2).toString());
|
|
@@ -389,6 +389,8 @@ export class StringUtils {
|
|
|
389
389
|
* @return {boolean} Se a string pode ser convertida
|
|
390
390
|
*/
|
|
391
391
|
static isCaseable(original: string): boolean {
|
|
392
|
+
if(original == null) return false;
|
|
393
|
+
|
|
392
394
|
const uppercase = original.toUpperCase()
|
|
393
395
|
const lowercase = original.toLowerCase()
|
|
394
396
|
|
|
@@ -402,6 +404,7 @@ export class StringUtils {
|
|
|
402
404
|
* @return {boolean} Se a string é minúscula
|
|
403
405
|
*/
|
|
404
406
|
static isLowerCase(original: string): boolean {
|
|
407
|
+
if(original == null) return false;
|
|
405
408
|
const uppercase = original.toUpperCase()
|
|
406
409
|
|
|
407
410
|
return this.isCaseable(original) && uppercase !== original
|
|
@@ -414,6 +417,7 @@ export class StringUtils {
|
|
|
414
417
|
* @return {string} A string invertida
|
|
415
418
|
*/
|
|
416
419
|
static getOppositeCase(original: string): string {
|
|
420
|
+
if(original == null) return "";
|
|
417
421
|
return this.isLowerCase(original) ? original.toUpperCase() : original.toLowerCase()
|
|
418
422
|
}
|
|
419
423
|
|
|
@@ -487,7 +491,7 @@ export class StringUtils {
|
|
|
487
491
|
}
|
|
488
492
|
|
|
489
493
|
public static highlightValue(argument: String, matchFields: any, value: string, fieldMD: any, forceMatch: boolean) {
|
|
490
|
-
const startHighlightTag = "<span class='card-item__highlight'>";
|
|
494
|
+
const startHighlightTag = "<span class='card-item__highlight'>"; //FIXME: valor hardcoded, não podemos reaproveitar para outros cenários, dessa forma.
|
|
491
495
|
const endHighlightTag = "</span>";
|
|
492
496
|
let valueAux = JSUtils.replaceHtmlEntities(value);
|
|
493
497
|
|
|
@@ -538,4 +542,17 @@ export class StringUtils {
|
|
|
538
542
|
|
|
539
543
|
return valueAux;
|
|
540
544
|
}
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Escapa caracteres especiais em uma string, usando sequencias de escape Unicode.
|
|
549
|
+
* @param str A string que deve sofrer alteração.
|
|
550
|
+
* @returns String com valores alterados.
|
|
551
|
+
*/
|
|
552
|
+
public static escapeString(str: string): string {
|
|
553
|
+
// eslint-disable-next-line no-control-regex
|
|
554
|
+
return str.replace(/[\u0000-\u001F\u007F-\uFFFF<>=&"']/g, (char) => {
|
|
555
|
+
return "\\u" + char.charCodeAt(0).toString(16).padStart(4, "0");
|
|
556
|
+
});
|
|
557
|
+
}
|
|
541
558
|
}
|
|
@@ -15,6 +15,11 @@ export class UserAgentUtils {
|
|
|
15
15
|
return !!browser.firefox;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
public static isElectron() {
|
|
19
|
+
const browser = this.getBrowserInfo();
|
|
20
|
+
return !!browser.electron;
|
|
21
|
+
}
|
|
22
|
+
|
|
18
23
|
/**
|
|
19
24
|
* Obtém nome e versão do navegador que está sendo utilizado.
|
|
20
25
|
* @returns Objeto com o nome e versão do navegador.
|
|
@@ -40,6 +45,7 @@ export class UserAgentUtils {
|
|
|
40
45
|
}
|
|
41
46
|
} catch (e) {
|
|
42
47
|
//ignored
|
|
48
|
+
console.warn(e);
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
browser = {
|
|
@@ -49,7 +55,6 @@ export class UserAgentUtils {
|
|
|
49
55
|
simpleVersion,
|
|
50
56
|
...(Array.isArray(type) ? type.reduce((acc, val) => ({ ...acc, [val]: true}), {}) : { [type]: true })
|
|
51
57
|
}
|
|
52
|
-
|
|
53
58
|
return !hasFound;
|
|
54
59
|
}
|
|
55
60
|
})
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { getFormattedValue } from '../../../src/dataunit/formatting/PrettyFormatter';
|
|
2
|
+
import { DataType, FieldDescriptor, UserInterface } from '../../../src';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* É preciso importart o utilitário DateUtils para que seus métodos
|
|
6
|
+
* estáticos sejam executados no ambiente de testes
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { DateUtils } from '../../../src';
|
|
10
|
+
|
|
11
|
+
describe('getFormattedValue', () => {
|
|
12
|
+
it('should return empty string when value is null', () => {
|
|
13
|
+
const value = null;
|
|
14
|
+
const descriptor = getFileFieldDescriptor();
|
|
15
|
+
expect(getFormattedValue(value, descriptor)).toBe('');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should return empty string when value is not an array', () => {
|
|
19
|
+
const value = 'not an array';
|
|
20
|
+
const descriptor = getFileFieldDescriptor();
|
|
21
|
+
expect(getFormattedValue(value, descriptor)).toBe('');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should return empty string when value is an empty array', () => {
|
|
25
|
+
const value: any[] = [];
|
|
26
|
+
const descriptor = getFileFieldDescriptor();
|
|
27
|
+
expect(getFormattedValue(value, descriptor)).toBe('0 arquivos');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should return file name when value is an array with one file object', () => {
|
|
31
|
+
const value = [{ name: 'file1.txt' }];
|
|
32
|
+
const descriptor = getFileFieldDescriptor();
|
|
33
|
+
expect(getFormattedValue(value, descriptor)).toBe('file1.txt');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should return file count when value is an array with multiple file objects', () => {
|
|
37
|
+
const value = [{ name: 'file1.txt' }, { name: 'file2.txt' }];
|
|
38
|
+
const descriptor = getFileFieldDescriptor();
|
|
39
|
+
expect(getFormattedValue(value, descriptor)).toBe('2 arquivos');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should return empty string for undefined file', () => {
|
|
43
|
+
const value = undefined;
|
|
44
|
+
const descriptor = { userInterface: UserInterface.FILE } as FieldDescriptor;
|
|
45
|
+
expect(getFormattedValue(value, descriptor)).toBe('');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should handle OBJECT type with value field', () => {
|
|
49
|
+
const value = { value: 'code', label: 'label' };
|
|
50
|
+
const descriptor = { dataType: DataType.OBJECT, userInterface: UserInterface.SEARCH } as FieldDescriptor;
|
|
51
|
+
expect(getFormattedValue(value, descriptor)).toBe('code - label');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should return empty string when value is undefined', () => {
|
|
55
|
+
const value = undefined;
|
|
56
|
+
const descriptor = getObjectFieldDescriptor();
|
|
57
|
+
expect(getFormattedValue(value, descriptor)).toBe('');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should return value as string when value is not an object', () => {
|
|
61
|
+
const value = 12345;
|
|
62
|
+
const descriptor = getObjectFieldDescriptor();
|
|
63
|
+
expect(getFormattedValue(value, descriptor)).toBe('12345');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should return value.toString() when value is an object without value property', () => {
|
|
67
|
+
const value = { someProperty: 'someValue' };
|
|
68
|
+
const descriptor = getObjectFieldDescriptor();
|
|
69
|
+
expect(getFormattedValue(value, descriptor)).toBe(value.toString());
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should handle BOOLEAN type', () => {
|
|
73
|
+
expect(getFormattedValue(true, { dataType: DataType.BOOLEAN } as FieldDescriptor)).toBe('Sim');
|
|
74
|
+
expect(getFormattedValue(false, { dataType: DataType.BOOLEAN } as FieldDescriptor)).toBe('Não');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should format numbers correctly', () => {
|
|
78
|
+
const value = 1234.567;
|
|
79
|
+
const descriptor = {
|
|
80
|
+
dataType: DataType.NUMBER,
|
|
81
|
+
properties: { precision: 2, prettyPrecision: 2 },
|
|
82
|
+
} as FieldDescriptor;
|
|
83
|
+
expect(getFormattedValue(value, descriptor)).toBe('1.234,57');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should format dates correctly', () => {
|
|
87
|
+
const value = new Date('2023-07-01T14:30:00');
|
|
88
|
+
const descriptor = { userInterface: UserInterface.DATE } as FieldDescriptor;
|
|
89
|
+
expect(getFormattedValue(value, descriptor)).toBe('01/07/2023');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should format times correctly', () => {
|
|
93
|
+
const value = new Date('2023-07-01T14:30:00');
|
|
94
|
+
const descriptor = { userInterface: UserInterface.TIME } as FieldDescriptor;
|
|
95
|
+
expect(getFormattedValue(value, descriptor)).toBe('14:30');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should format datetime correctly', () => {
|
|
99
|
+
const value = new Date('2023-07-01T14:30:00');
|
|
100
|
+
const descriptor = { userInterface: UserInterface.DATETIME } as FieldDescriptor;
|
|
101
|
+
expect(getFormattedValue(value, descriptor)).toBe('01/07/2023 14:30');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should call toString with correct parameters', () => {
|
|
105
|
+
const value = 123;
|
|
106
|
+
const descriptor = { dataType: DataType.NUMBER } as FieldDescriptor;
|
|
107
|
+
expect(getFormattedValue(value, descriptor)).toBe('123,00');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should handle undefined descriptor gracefully', () => {
|
|
111
|
+
const value = 123;
|
|
112
|
+
expect(getFormattedValue(value)).toBe('123');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should format value correctly with OPTIONSELECTOR and matching option', () => {
|
|
116
|
+
const value = 'option1';
|
|
117
|
+
const descriptor = buildOptionSelectorFieldDescriptor();
|
|
118
|
+
expect(getFormattedValue(value, descriptor)).toBe('option1');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should return value with OPTIONSELECTOR and no matching option', () => {
|
|
122
|
+
const value = 'option2';
|
|
123
|
+
const descriptor = buildOptionSelectorFieldDescriptor();
|
|
124
|
+
expect(getFormattedValue(value, descriptor)).toBe('option2');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should format value correctly with OPTIONSELECTOR and value as object', () => {
|
|
128
|
+
const value = { value: 'option1' };
|
|
129
|
+
const descriptor = buildOptionSelectorFieldDescriptor();
|
|
130
|
+
expect(getFormattedValue(value, descriptor)).toBe('option1');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should return empty string with OPTIONSELECTOR and value as null', () => {
|
|
134
|
+
const value = null;
|
|
135
|
+
const descriptor = buildOptionSelectorFieldDescriptor();
|
|
136
|
+
expect(getFormattedValue(value, descriptor)).toBe('');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should format value correctly with OPTIONSELECTOR and options as JSON string', () => {
|
|
140
|
+
const value = 'option1';
|
|
141
|
+
const descriptor: FieldDescriptor = {
|
|
142
|
+
userInterface: UserInterface.OPTIONSELECTOR,
|
|
143
|
+
properties: { options: JSON.stringify([{ label: 'Option 1', value: 'option1' }]) },
|
|
144
|
+
} as FieldDescriptor;
|
|
145
|
+
expect(getFormattedValue(value, descriptor)).toBe('option1');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should return value with OPTIONSELECTOR and options as empty array', () => {
|
|
149
|
+
const value = 'option1';
|
|
150
|
+
const descriptor: FieldDescriptor = {
|
|
151
|
+
userInterface: UserInterface.OPTIONSELECTOR,
|
|
152
|
+
properties: { options: [] },
|
|
153
|
+
} as FieldDescriptor;
|
|
154
|
+
expect(getFormattedValue(value, descriptor)).toBe('option1');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should format masked values correctly', () => {
|
|
158
|
+
const value = '12345678901';
|
|
159
|
+
const descriptor = { properties: { mask: 'cpf' } } as FieldDescriptor;
|
|
160
|
+
expect(getFormattedValue(value, descriptor)).toBe('123.456.789-01');
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
function buildOptionSelectorFieldDescriptor(): FieldDescriptor {
|
|
165
|
+
return {
|
|
166
|
+
userInterface: UserInterface.OPTIONSELECTOR,
|
|
167
|
+
properties: { options: [{ label: 'Option 1', value: 'option1' }] },
|
|
168
|
+
} as FieldDescriptor;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function getFileFieldDescriptor(): FieldDescriptor{
|
|
172
|
+
return { userInterface: UserInterface.FILE } as FieldDescriptor;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function getObjectFieldDescriptor(): FieldDescriptor{
|
|
176
|
+
return { dataType: DataType.OBJECT } as FieldDescriptor;
|
|
177
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { DataUnitInMemoryLoader } from '../../../src/dataunit/loader/dataUnitInMemoryLoader.js';
|
|
2
|
+
import {
|
|
3
|
+
DataUnitInMemoryLoaderConfig,
|
|
4
|
+
RECORD_DATE_FORMAT,
|
|
5
|
+
} from '../../../src/dataunit/loader/DataUnitInMemoryLoaderConfig.js';
|
|
6
|
+
import { FieldDescriptor, UnitMetadata } from '../../../src/dataunit/metadata/UnitMetadata.js';
|
|
7
|
+
import { DataType } from '../../../src/dataunit/metadata/DataType.js';
|
|
8
|
+
import DateUtils from '../../../src/utils/DateUtils.js';
|
|
9
|
+
import DataUnit, { Record } from '../../../src/dataunit/DataUnit.js';
|
|
10
|
+
|
|
11
|
+
describe('DataUnitInMemoryLoader - loadData', () => {
|
|
12
|
+
const dataUnitMetetadata: UnitMetadata = {
|
|
13
|
+
name: 'test',
|
|
14
|
+
label: 'test',
|
|
15
|
+
fields: [],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
it('should be called load data when auto load is true', async () => {
|
|
19
|
+
const config: DataUnitInMemoryLoaderConfig = {
|
|
20
|
+
autoLoad: true,
|
|
21
|
+
};
|
|
22
|
+
const inMemoryLoader = new DataUnitInMemoryLoader(dataUnitMetetadata, [], config);
|
|
23
|
+
const loadData = jest.spyOn(inMemoryLoader.dataUnit, 'loadData');
|
|
24
|
+
await inMemoryLoader.dataUnit.loadMetadata();
|
|
25
|
+
expect(loadData).toHaveBeenCalled();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should be called load data when auto load is undefined', async () => {
|
|
29
|
+
const config: DataUnitInMemoryLoaderConfig = {
|
|
30
|
+
autoLoad: undefined,
|
|
31
|
+
};
|
|
32
|
+
const inMemoryLoader = new DataUnitInMemoryLoader(dataUnitMetetadata, [], config);
|
|
33
|
+
const loadData = jest.spyOn(inMemoryLoader.dataUnit, 'loadData');
|
|
34
|
+
await inMemoryLoader.dataUnit.loadMetadata();
|
|
35
|
+
expect(loadData).toHaveBeenCalled();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should not be called load data when auto load is false', async () => {
|
|
39
|
+
const config: DataUnitInMemoryLoaderConfig = {
|
|
40
|
+
autoLoad: false,
|
|
41
|
+
};
|
|
42
|
+
const inMemoryLoader = new DataUnitInMemoryLoader(dataUnitMetetadata, [], config);
|
|
43
|
+
const loadData = jest.spyOn(inMemoryLoader.dataUnit, 'loadData');
|
|
44
|
+
await inMemoryLoader.dataUnit.loadMetadata();
|
|
45
|
+
expect(loadData).not.toHaveBeenCalled();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should be called load data when config is undefined', async () => {
|
|
49
|
+
const inMemoryLoader = new DataUnitInMemoryLoader(dataUnitMetetadata, []);
|
|
50
|
+
const loadData = jest.spyOn(inMemoryLoader.dataUnit, 'loadData');
|
|
51
|
+
await inMemoryLoader.dataUnit.loadMetadata();
|
|
52
|
+
expect(loadData).toHaveBeenCalled();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('DataUnitInMemoryLoader - getConvertedValue', () => {
|
|
57
|
+
let descriptor: FieldDescriptor;
|
|
58
|
+
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
descriptor = { name: 'testField', dataType: DataType.TEXT, label: 'mock' }; // Valor padrão para inicialização
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should return the original string if descriptor is undefined', () => {
|
|
64
|
+
const result = DataUnitInMemoryLoader.getConvertedValue(undefined as any, 'testValue');
|
|
65
|
+
expect(result).toBe('testValue');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should return the original string if dataType is TEXT', () => {
|
|
69
|
+
descriptor.dataType = DataType.TEXT;
|
|
70
|
+
const result = DataUnitInMemoryLoader.getConvertedValue(descriptor, 'testValue');
|
|
71
|
+
expect(result).toBe('testValue');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should convert to boolean when dataType is BOOLEAN', () => {
|
|
75
|
+
descriptor.dataType = DataType.BOOLEAN;
|
|
76
|
+
expect(DataUnitInMemoryLoader.getConvertedValue(descriptor, 'S')).toBe(true);
|
|
77
|
+
expect(DataUnitInMemoryLoader.getConvertedValue(descriptor, 'N')).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should convert to number when dataType is NUMBER', () => {
|
|
81
|
+
descriptor.dataType = DataType.NUMBER;
|
|
82
|
+
expect(DataUnitInMemoryLoader.getConvertedValue(descriptor, '123')).toBe(123);
|
|
83
|
+
expect(DataUnitInMemoryLoader.getConvertedValue(descriptor, '45.67')).toBe(45.67);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should parse JSON when dataType is OBJECT', () => {
|
|
87
|
+
descriptor.dataType = DataType.OBJECT;
|
|
88
|
+
const jsonString = '{"key": "value"}';
|
|
89
|
+
const expectedObject = { key: 'value' };
|
|
90
|
+
expect(DataUnitInMemoryLoader.getConvertedValue(descriptor, jsonString)).toEqual(expectedObject);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should convert to date when dataType is DATE and format is ISO', () => {
|
|
94
|
+
descriptor.dataType = DataType.DATE;
|
|
95
|
+
|
|
96
|
+
const isoDate = '2023-01-01T00:00:00Z';
|
|
97
|
+
jest.spyOn(DateUtils, 'validateDate').mockImplementation((date) => date);
|
|
98
|
+
|
|
99
|
+
const result = DataUnitInMemoryLoader.getConvertedValue(descriptor, isoDate, RECORD_DATE_FORMAT.ISO);
|
|
100
|
+
expect(DateUtils.validateDate).toHaveBeenCalledWith(new Date(isoDate), true);
|
|
101
|
+
expect(result).toEqual(new Date(isoDate));
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should convert to date when dataType is DATE and format is not ISO', () => {
|
|
105
|
+
descriptor.dataType = DataType.DATE;
|
|
106
|
+
|
|
107
|
+
const dateString = '01/01/2023';
|
|
108
|
+
jest.spyOn(DateUtils, 'strToDate').mockImplementation(() => new Date(2023, 0, 1));
|
|
109
|
+
|
|
110
|
+
const result = DataUnitInMemoryLoader.getConvertedValue(descriptor, dateString, RECORD_DATE_FORMAT.DD_MM_YYYY);
|
|
111
|
+
expect(DateUtils.strToDate).toHaveBeenCalledWith(dateString, true);
|
|
112
|
+
expect(result).toEqual(new Date(2023, 0, 1));
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should return the original string if no dataType matches', () => {
|
|
116
|
+
descriptor.dataType = undefined as any; // Simula um caso inválido
|
|
117
|
+
const result = DataUnitInMemoryLoader.getConvertedValue(descriptor, 'testValue');
|
|
118
|
+
expect(result).toBe('testValue');
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('DataUnitInMemoryLoader - removeLoader', () => {
|
|
123
|
+
let loader: DataUnitInMemoryLoader;
|
|
124
|
+
const mockedDataUnit = {} as DataUnit;
|
|
125
|
+
|
|
126
|
+
beforeEach(() => {
|
|
127
|
+
loader = new DataUnitInMemoryLoader();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should resolve with the provided record IDs', async () => {
|
|
131
|
+
const mockRecordIds = ['record1', 'record2', 'record3'];
|
|
132
|
+
|
|
133
|
+
const result = await loader.removeLoader(mockedDataUnit, mockRecordIds);
|
|
134
|
+
|
|
135
|
+
expect(result).toEqual(mockRecordIds);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should handle empty record ID array', async () => {
|
|
139
|
+
const mockRecordIds: string[] = [];
|
|
140
|
+
|
|
141
|
+
const result = await loader.removeLoader(mockedDataUnit, mockRecordIds);
|
|
142
|
+
|
|
143
|
+
expect(result).toEqual([]);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should resolve the promise', async () => {
|
|
147
|
+
const mockRecordIds = ['record1'];
|
|
148
|
+
|
|
149
|
+
await expect(loader.removeLoader(mockedDataUnit, mockRecordIds)).resolves.toEqual(mockRecordIds);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('DataUnitInMemoryLoader - buildInitialRecords (indiretamente via set records)', () => {
|
|
154
|
+
let loader: DataUnitInMemoryLoader;
|
|
155
|
+
let mockRecords: Array<Record> = [
|
|
156
|
+
{ id: '1', amount: '100', isActive: 'S', createdAt: '01/01/2023' },
|
|
157
|
+
{ id: '2', amount: '200.50', isActive: 'N', createdAt: '02/01/2023' },
|
|
158
|
+
] as unknown as Record[];
|
|
159
|
+
|
|
160
|
+
let mockMetadata = {
|
|
161
|
+
fields: [
|
|
162
|
+
{ name: 'id', label: 'id', dataType: DataType.TEXT },
|
|
163
|
+
{ name: 'amount', label: 'amount', dataType: DataType.NUMBER },
|
|
164
|
+
{ name: 'isActive', label: 'isActive', dataType: DataType.BOOLEAN },
|
|
165
|
+
{ name: 'createdAt', label: 'createdAt', dataType: DataType.DATE },
|
|
166
|
+
],
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
beforeEach(() => {
|
|
170
|
+
loader = new DataUnitInMemoryLoader(mockMetadata as any, [], { recordDateFormat: RECORD_DATE_FORMAT.DD_MM_YYYY });
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should build initial records and convert field values based on metadata', () => {
|
|
174
|
+
loader.records = mockRecords;
|
|
175
|
+
const initialRecords = loader['_initialRecords'];
|
|
176
|
+
|
|
177
|
+
expect(initialRecords).toHaveLength(2);
|
|
178
|
+
|
|
179
|
+
expect(initialRecords[0]).toEqual({
|
|
180
|
+
id: '1',
|
|
181
|
+
amount: 100,
|
|
182
|
+
isActive: true,
|
|
183
|
+
createdAt: expect.any(Date),
|
|
184
|
+
__record__id__: expect.any(String),
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
expect(initialRecords[1]).toEqual({
|
|
188
|
+
id: '2',
|
|
189
|
+
amount: 200.5,
|
|
190
|
+
isActive: false,
|
|
191
|
+
createdAt: expect.any(Date),
|
|
192
|
+
__record__id__: expect.any(String),
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should generate unique IDs for records without "__record__id__"', () => {
|
|
197
|
+
loader.records = [{ id: '3', amount: '300' }] as unknown as Record[];
|
|
198
|
+
|
|
199
|
+
const builtRecords = loader['_initialRecords'];
|
|
200
|
+
expect(builtRecords).toHaveLength(1);
|
|
201
|
+
|
|
202
|
+
expect(builtRecords[0]).toHaveProperty('__record__id__');
|
|
203
|
+
expect(typeof builtRecords[0]['__record__id__']).toBe('string');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should leave fields untouched if no matching metadata exists', () => {
|
|
207
|
+
mockRecords[0]['extraField'] = 'unchanged';
|
|
208
|
+
loader.records = mockRecords;
|
|
209
|
+
|
|
210
|
+
const builtRecords = loader['_initialRecords'];
|
|
211
|
+
expect(builtRecords[0]).toHaveProperty('extraField', 'unchanged');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should skip conversion for non-string field values', () => {
|
|
215
|
+
mockRecords[0].amount = 500;
|
|
216
|
+
loader.records = mockRecords;
|
|
217
|
+
|
|
218
|
+
const builtRecords = loader['_initialRecords'];
|
|
219
|
+
expect(builtRecords[0].amount).toBe(500);
|
|
220
|
+
});
|
|
221
|
+
});
|