vest-utils 1.5.0 → 2.0.1

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 (157) hide show
  1. package/README.md +7 -2
  2. package/dist/chunk-CLMFDpHK.mjs +18 -0
  3. package/dist/exports/minifyObject.cjs +114 -0
  4. package/dist/exports/minifyObject.cjs.map +1 -0
  5. package/dist/exports/minifyObject.mjs +113 -0
  6. package/dist/exports/minifyObject.mjs.map +1 -0
  7. package/dist/exports/standardSchemaSpec.cjs +0 -0
  8. package/dist/exports/standardSchemaSpec.mjs +1 -0
  9. package/dist/isEmpty-BBxAFjjm.mjs +103 -0
  10. package/dist/isEmpty-BBxAFjjm.mjs.map +1 -0
  11. package/dist/isEmpty-BuEa-96Q.cjs +235 -0
  12. package/dist/isEmpty-BuEa-96Q.cjs.map +1 -0
  13. package/dist/vest-utils.cjs +510 -0
  14. package/dist/vest-utils.cjs.map +1 -0
  15. package/dist/vest-utils.mjs +421 -0
  16. package/dist/vest-utils.mjs.map +1 -0
  17. package/minifyObject/package.json +12 -8
  18. package/package.json +43 -58
  19. package/src/Brand.ts +9 -0
  20. package/src/IO.ts +2 -0
  21. package/src/Predicates.ts +13 -0
  22. package/src/Result.ts +121 -0
  23. package/src/SimpleStateMachine.ts +157 -0
  24. package/src/StringObject.ts +6 -0
  25. package/src/__tests__/Architecture.test.ts +69 -0
  26. package/src/__tests__/Predicates.test.ts +118 -0
  27. package/src/__tests__/Result.test.ts +284 -0
  28. package/src/__tests__/SimpleStateMachine.test.ts +425 -0
  29. package/src/__tests__/StringObject.test.ts +18 -0
  30. package/src/__tests__/asArray.test.ts +14 -0
  31. package/src/__tests__/bindNot.test.ts +39 -0
  32. package/src/__tests__/bus.test.ts +135 -0
  33. package/src/__tests__/cache.test.ts +139 -0
  34. package/src/__tests__/callEach.test.ts +20 -0
  35. package/src/__tests__/defaultTo.test.ts +52 -0
  36. package/src/__tests__/deferThrow.test.ts +26 -0
  37. package/src/__tests__/either.test.ts +17 -0
  38. package/src/__tests__/freezeAssign.test.ts +24 -0
  39. package/src/__tests__/greaterThan.test.ts +68 -0
  40. package/src/__tests__/invariant.test.ts +47 -0
  41. package/src/__tests__/isArray.test.ts +16 -0
  42. package/src/__tests__/isBoolean.test.ts +16 -0
  43. package/src/__tests__/isEmpty.test.ts +55 -0
  44. package/src/__tests__/isEmptySet.test.ts +22 -0
  45. package/src/__tests__/isNull.test.ts +26 -0
  46. package/src/__tests__/isNumeric.test.ts +27 -0
  47. package/src/__tests__/isPositive.test.ts +38 -0
  48. package/src/__tests__/isPromise.test.ts +17 -0
  49. package/src/__tests__/isString.test.ts +13 -0
  50. package/src/__tests__/isUndefined.test.ts +27 -0
  51. package/src/__tests__/isUnsafeKey.test.ts +22 -0
  52. package/src/__tests__/lengthEquals.test.ts +58 -0
  53. package/src/__tests__/longerThan.test.ts +58 -0
  54. package/src/__tests__/mapFirst.test.ts +31 -0
  55. package/src/__tests__/nonnullish.test.ts +25 -0
  56. package/src/__tests__/noop.test.ts +12 -0
  57. package/src/__tests__/numberEquals.test.ts +67 -0
  58. package/src/__tests__/optionalFunctionValue.test.ts +29 -0
  59. package/src/__tests__/seq.test.ts +29 -0
  60. package/src/__tests__/text.test.ts +41 -0
  61. package/src/__tests__/tinyState.test.ts +68 -0
  62. package/src/__tests__/toNumber.test.ts +39 -0
  63. package/src/__tests__/vest-utils.test.ts +13 -0
  64. package/src/__tests__/withCatch.test.ts +17 -0
  65. package/src/__tests__/withResolvers.test.ts +45 -0
  66. package/src/asArray.ts +3 -0
  67. package/src/assign.ts +1 -0
  68. package/src/bindNot.ts +3 -0
  69. package/src/bus.ts +52 -0
  70. package/src/cache.ts +68 -0
  71. package/src/callEach.ts +5 -0
  72. package/src/defaultTo.ts +9 -0
  73. package/src/deferThrow.ts +7 -0
  74. package/src/dynamicValue.ts +9 -0
  75. package/src/either.ts +3 -0
  76. package/src/exports/__tests__/minifyObject.security.test.ts +65 -0
  77. package/src/exports/__tests__/minifyObject.test.ts +281 -0
  78. package/src/exports/minifyObject.ts +198 -0
  79. package/src/exports/standardSchemaSpec.ts +70 -0
  80. package/src/freezeAssign.ts +5 -0
  81. package/src/globals.d.ts +3 -0
  82. package/src/greaterThan.ts +8 -0
  83. package/src/hasOwnProperty.ts +9 -0
  84. package/src/invariant.ts +19 -0
  85. package/src/isArrayValue.ts +11 -0
  86. package/src/isBooleanValue.ts +3 -0
  87. package/src/isEmpty.ts +18 -0
  88. package/src/isEmptySet.ts +15 -0
  89. package/src/isFunction.ts +5 -0
  90. package/src/isNull.ts +7 -0
  91. package/src/isNullish.ts +10 -0
  92. package/src/isNumeric.ts +11 -0
  93. package/src/isPositive.ts +5 -0
  94. package/src/isPromise.ts +5 -0
  95. package/src/isStringValue.ts +3 -0
  96. package/src/isUndefined.ts +7 -0
  97. package/src/isUnsafeKey.ts +3 -0
  98. package/src/lengthEquals.ts +11 -0
  99. package/src/longerThan.ts +8 -0
  100. package/src/mapFirst.ts +25 -0
  101. package/src/nonnullish.ts +9 -0
  102. package/src/noop.ts +1 -0
  103. package/src/numberEquals.ts +11 -0
  104. package/src/seq.ts +16 -0
  105. package/src/text.ts +20 -0
  106. package/src/tinyState.ts +28 -0
  107. package/src/toNumber.ts +11 -0
  108. package/src/utilityTypes.ts +25 -0
  109. package/src/valueIsObject.ts +5 -0
  110. package/src/vest-utils.ts +73 -0
  111. package/src/withCatch.ts +11 -0
  112. package/src/withResolvers.ts +33 -0
  113. package/standardSchemaSpec/package.json +14 -0
  114. package/types/{minifyObject.d.ts → exports/minifyObject.d.cts} +4 -2
  115. package/types/exports/minifyObject.d.cts.map +1 -0
  116. package/types/exports/minifyObject.d.mts +7 -0
  117. package/types/exports/minifyObject.d.mts.map +1 -0
  118. package/types/exports/standardSchemaSpec.d.cts +59 -0
  119. package/types/exports/standardSchemaSpec.d.cts.map +1 -0
  120. package/types/exports/standardSchemaSpec.d.mts +59 -0
  121. package/types/exports/standardSchemaSpec.d.mts.map +1 -0
  122. package/types/vest-utils.d.cts +296 -0
  123. package/types/vest-utils.d.cts.map +1 -0
  124. package/types/vest-utils.d.mts +295 -0
  125. package/types/vest-utils.d.mts.map +1 -0
  126. package/types/vest-utils.d.ts +245 -143
  127. package/vitest.config.ts +9 -45
  128. package/dist/cjs/minifyObject.development.js +0 -217
  129. package/dist/cjs/minifyObject.development.js.map +0 -1
  130. package/dist/cjs/minifyObject.js +0 -6
  131. package/dist/cjs/minifyObject.production.js +0 -2
  132. package/dist/cjs/minifyObject.production.js.map +0 -1
  133. package/dist/cjs/package.json +0 -1
  134. package/dist/cjs/vest-utils.development.js +0 -378
  135. package/dist/cjs/vest-utils.development.js.map +0 -1
  136. package/dist/cjs/vest-utils.js +0 -6
  137. package/dist/cjs/vest-utils.production.js +0 -2
  138. package/dist/cjs/vest-utils.production.js.map +0 -1
  139. package/dist/es/minifyObject.development.js +0 -214
  140. package/dist/es/minifyObject.development.js.map +0 -1
  141. package/dist/es/minifyObject.production.js +0 -2
  142. package/dist/es/minifyObject.production.js.map +0 -1
  143. package/dist/es/package.json +0 -1
  144. package/dist/es/vest-utils.development.js +0 -330
  145. package/dist/es/vest-utils.development.js.map +0 -1
  146. package/dist/es/vest-utils.production.js +0 -2
  147. package/dist/es/vest-utils.production.js.map +0 -1
  148. package/dist/umd/minifyObject.development.js +0 -223
  149. package/dist/umd/minifyObject.development.js.map +0 -1
  150. package/dist/umd/minifyObject.production.js +0 -2
  151. package/dist/umd/minifyObject.production.js.map +0 -1
  152. package/dist/umd/vest-utils.development.js +0 -384
  153. package/dist/umd/vest-utils.development.js.map +0 -1
  154. package/dist/umd/vest-utils.production.js +0 -2
  155. package/dist/umd/vest-utils.production.js.map +0 -1
  156. package/types/minifyObject.d.ts.map +0 -1
  157. package/types/vest-utils.d.ts.map +0 -1
@@ -0,0 +1,39 @@
1
+ import { describe, it, expect } from 'vitest';
2
+
3
+ import { isFailure, isSuccess, unwrap } from '../Result';
4
+ import { toNumber } from '../toNumber';
5
+
6
+ describe('toNumber', () => {
7
+ describe('Successful conversions', () => {
8
+ it.each([
9
+ [1, 1],
10
+ [0, 0],
11
+ [-1, -1],
12
+ [1.5, 1.5],
13
+ ['1', 1],
14
+ ['0', 0],
15
+ ['-1', -1],
16
+ ['1.5', 1.5],
17
+ [true, 1],
18
+ [false, 0],
19
+ [null, 0],
20
+ ['', 0],
21
+ [' ', 0],
22
+ [[], 0],
23
+ [[1], 1],
24
+ ])('should convert %s to %s', (input, expected) => {
25
+ const result = toNumber(input);
26
+ expect(isSuccess(result)).toBe(true);
27
+ expect(unwrap(result)).toBe(expected);
28
+ });
29
+ });
30
+
31
+ describe('Failed conversions', () => {
32
+ it.each([undefined, 'abc', '1a', {}, [1, 2], NaN])(
33
+ 'should fail to convert %s',
34
+ input => {
35
+ expect(isFailure(toNumber(input))).toBe(true);
36
+ },
37
+ );
38
+ });
39
+ });
@@ -0,0 +1,13 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import * as vestUtils from '../vest-utils';
3
+
4
+ describe('vest-utils exports', () => {
5
+ it('should export expected utilities', () => {
6
+ expect(vestUtils.noop).toBeDefined();
7
+ expect(vestUtils.isFunction).toBeDefined();
8
+ expect(vestUtils.withResolvers).toBeDefined();
9
+ // Verify a few others to ensure the file is processed
10
+ expect(vestUtils.defaultTo).toBeDefined();
11
+ expect(vestUtils.assign).toBeDefined();
12
+ });
13
+ });
@@ -0,0 +1,17 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { withCatch } from '../withCatch';
3
+
4
+ describe('withCatch', () => {
5
+ it('should return the result of the callback if it succeeds', () => {
6
+ const fn = withCatch(() => 'success');
7
+ expect(fn()).toBe('success');
8
+ });
9
+
10
+ it('should return the error if the callback throws', () => {
11
+ const error = new Error('failure');
12
+ const fn = withCatch(() => {
13
+ throw error;
14
+ });
15
+ expect(fn()).toBe(error);
16
+ });
17
+ });
@@ -0,0 +1,45 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { withResolvers } from '../withResolvers';
3
+
4
+ describe('withResolvers', () => {
5
+ const originalWithResolvers = Promise.withResolvers;
6
+
7
+ afterEach(() => {
8
+ // Restore the original implementation
9
+ if (originalWithResolvers) {
10
+ Promise.withResolvers = originalWithResolvers;
11
+ } else {
12
+ // @ts-ignore
13
+ delete Promise.withResolvers;
14
+ }
15
+ });
16
+
17
+ it('should return a promise, resolve, and reject', () => {
18
+ const { promise, resolve, reject } = withResolvers();
19
+ expect(promise).toBeInstanceOf(Promise);
20
+ expect(typeof resolve).toBe('function');
21
+ expect(typeof reject).toBe('function');
22
+ });
23
+
24
+ it('should resolve the promise', async () => {
25
+ const { promise, resolve } = withResolvers<string>();
26
+ resolve('success');
27
+ await expect(promise).resolves.toBe('success');
28
+ });
29
+
30
+ it('should reject the promise', async () => {
31
+ const { promise, reject } = withResolvers<string>();
32
+ const error = new Error('fail');
33
+ reject(error);
34
+ await expect(promise).rejects.toBe(error);
35
+ });
36
+
37
+ it('should use the polyfill if Promise.withResolvers is not defined', async () => {
38
+ // @ts-ignore
39
+ Promise.withResolvers = undefined;
40
+
41
+ const { promise, resolve } = withResolvers<string>();
42
+ resolve('polyfill');
43
+ await expect(promise).resolves.toBe('polyfill');
44
+ });
45
+ });
package/src/asArray.ts ADDED
@@ -0,0 +1,3 @@
1
+ export default function asArray<T>(possibleArg: T | T[]): T[] {
2
+ return ([] as T[]).concat(possibleArg);
3
+ }
package/src/assign.ts ADDED
@@ -0,0 +1 @@
1
+ export default Object.assign;
package/src/bindNot.ts ADDED
@@ -0,0 +1,3 @@
1
+ export default function bindNot<T extends (...args: any[]) => unknown>(fn: T) {
2
+ return (...args: Parameters<T>): boolean => !fn(...args);
3
+ }
package/src/bus.ts ADDED
@@ -0,0 +1,52 @@
1
+ import type { CB } from './utilityTypes';
2
+
3
+ const EVENT_WILDCARD = '*';
4
+ type TEventsWildcard = typeof EVENT_WILDCARD;
5
+
6
+ export function createBus<
7
+ TEvents extends Record<string, any> = Record<string, any>,
8
+ >(): BusType<TEvents> {
9
+ const listeners = {} as Record<keyof TEvents | TEventsWildcard, CB[]>;
10
+
11
+ return {
12
+ emit(event: keyof TEvents | TEventsWildcard, payload?: any) {
13
+ getListeners(event)
14
+ .concat(getListeners(EVENT_WILDCARD))
15
+ .forEach(handler => {
16
+ handler(payload);
17
+ });
18
+ },
19
+
20
+ on(event: keyof TEvents | TEventsWildcard, handler: CB): OnReturn {
21
+ listeners[event] = getListeners(event).concat(handler);
22
+
23
+ return {
24
+ off() {
25
+ listeners[event] = getListeners(event).filter(h => h !== handler);
26
+ },
27
+ };
28
+ },
29
+ };
30
+
31
+ function getListeners(event: keyof TEvents | TEventsWildcard): CB[] {
32
+ return listeners[event] || [];
33
+ }
34
+ }
35
+
36
+ type OnReturn = { off: CB<void> };
37
+
38
+ export type BusType<TEvents extends Record<string, any>> = {
39
+ on<T extends keyof TEvents>(
40
+ event: T,
41
+ handler: (payload: TEvents[T]) => void,
42
+ ): OnReturn;
43
+ on(event: TEventsWildcard, handler: (payload: any) => void): OnReturn;
44
+
45
+ emit<T extends keyof TEvents>(
46
+ event: T,
47
+ ...args: TEvents[T] extends void
48
+ ? [payload?: TEvents[T]]
49
+ : [payload: TEvents[T]]
50
+ ): void;
51
+ emit(event: TEventsWildcard, payload?: any): void;
52
+ };
package/src/cache.ts ADDED
@@ -0,0 +1,68 @@
1
+ import { lengthEquals } from './lengthEquals';
2
+ import { longerThan } from './longerThan';
3
+ import { DynamicValue, Nullable } from './utilityTypes';
4
+ import { dynamicValue } from './vest-utils';
5
+
6
+ /**
7
+ * Creates a cache function
8
+ */
9
+ export default function createCache<T = unknown>(maxSize = 1): CacheApi<T> {
10
+ const cacheStorage: Array<[unknown[], T]> = [];
11
+
12
+ const cache = (deps: unknown[], cacheAction: DynamicValue<T>): T => {
13
+ const cacheHit = cache.get(deps);
14
+ // cache hit is not null
15
+ if (cacheHit) return cacheHit[1];
16
+
17
+ const result = dynamicValue(cacheAction);
18
+ cacheStorage.unshift([deps.concat(), result]);
19
+
20
+ trimToSize();
21
+
22
+ return result;
23
+ };
24
+
25
+ // invalidate an item in the cache by its dependencies
26
+ cache.invalidate = (deps: any[]): void => {
27
+ const index = findIndex(deps);
28
+ if (index > -1) cacheStorage.splice(index, 1);
29
+ };
30
+
31
+ // Retrieves an item from the cache.
32
+ cache.get = (deps: unknown[]): Nullable<[unknown[], T]> =>
33
+ cacheStorage[findIndex(deps)] || null;
34
+
35
+ // sets a value to the cache by its dependencies, updating if a hit is found.
36
+ cache.set = (deps: unknown[], value: T): void => {
37
+ const index = findIndex(deps);
38
+ if (index > -1) {
39
+ cacheStorage[index] = [deps, value];
40
+ } else {
41
+ cacheStorage.unshift([deps, value]);
42
+ }
43
+ trimToSize();
44
+ };
45
+
46
+ return cache;
47
+
48
+ function trimToSize() {
49
+ if (longerThan(cacheStorage, maxSize)) {
50
+ cacheStorage.length = maxSize;
51
+ }
52
+ }
53
+
54
+ function findIndex(deps: unknown[]): number {
55
+ return cacheStorage.findIndex(
56
+ ([cachedDeps]) =>
57
+ lengthEquals(deps, cachedDeps.length) &&
58
+ deps.every((dep, i) => dep === cachedDeps[i]),
59
+ );
60
+ }
61
+ }
62
+
63
+ export type CacheApi<T = unknown> = {
64
+ (deps: unknown[], cacheAction: DynamicValue<T>): T;
65
+ get(deps: unknown[]): Nullable<[unknown[], T]>;
66
+ set(deps: unknown[], value: T): void;
67
+ invalidate(item: any): void;
68
+ };
@@ -0,0 +1,5 @@
1
+ import type { CB } from './utilityTypes';
2
+
3
+ export default function callEach(arr: CB[]): void {
4
+ return arr.forEach(fn => fn());
5
+ }
@@ -0,0 +1,9 @@
1
+ import dynamicValue from './dynamicValue';
2
+ import { DynamicValue, Nullish } from './utilityTypes';
3
+
4
+ export default function defaultTo<T>(
5
+ value: DynamicValue<Nullish<T>>,
6
+ defaultValue: DynamicValue<T>,
7
+ ): T {
8
+ return dynamicValue(value) ?? dynamicValue(defaultValue);
9
+ }
@@ -0,0 +1,7 @@
1
+ export default function deferThrow(message?: string): void {
2
+ setTimeout(() => {
3
+ throw new Error(message);
4
+ }, 0);
5
+ }
6
+
7
+ export type TDeferThrow = typeof deferThrow;
@@ -0,0 +1,9 @@
1
+ import isFunction from './isFunction';
2
+ import { DynamicValue } from './utilityTypes';
3
+
4
+ export default function dynamicValue<T>(
5
+ value: DynamicValue<T>,
6
+ ...args: unknown[]
7
+ ): T {
8
+ return isFunction(value) ? value(...args) : value;
9
+ }
package/src/either.ts ADDED
@@ -0,0 +1,3 @@
1
+ export default function either(a: unknown, b: unknown): boolean {
2
+ return !!a !== !!b;
3
+ }
@@ -0,0 +1,65 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { expandObject } from '../minifyObject';
3
+
4
+ describe('minifyObject Security', () => {
5
+ describe('Prototype Pollution', () => {
6
+ it('Should not allow pollution via "__proto__" key', () => {
7
+ const maliciousPayload = '{"__proto__":{"polluted":true}}';
8
+ const parsed = JSON.parse(maliciousPayload);
9
+
10
+ // Simulate expansion (minifyObject's expandObject handles the reconstruction)
11
+ // We pass an empty map as we are testing raw key assignment
12
+ const expanded = expandObject(parsed, {});
13
+
14
+ // @ts-ignore
15
+ expect({}.polluted).toBeUndefined();
16
+ expect(expanded.polluted).toBeUndefined();
17
+ // @ts-ignore
18
+ expect(expanded.__proto__).not.toEqual({ polluted: true });
19
+ });
20
+
21
+ it('Should not allow pollution via "constructor" key', () => {
22
+ const maliciousPayload =
23
+ '{"constructor":{"prototype":{"polluted":true}}}';
24
+ const parsed = JSON.parse(maliciousPayload);
25
+
26
+ const expanded = expandObject(parsed, {});
27
+
28
+ // @ts-ignore
29
+ expect({}.polluted).toBeUndefined();
30
+ expect(expanded.constructor?.prototype?.polluted).toBeUndefined();
31
+ });
32
+
33
+ it('Should not allow pollution via "prototype" key', () => {
34
+ const maliciousPayload = '{"prototype":{"polluted":true}}';
35
+ const parsed = JSON.parse(maliciousPayload);
36
+
37
+ expandObject(parsed, {});
38
+
39
+ // @ts-ignore
40
+ expect({}.polluted).toBeUndefined();
41
+ });
42
+ });
43
+
44
+ describe('Sanity: Unprotected JSON.parse behavior', () => {
45
+ it('Should preserve "__proto__" as a plain key when using standard JSON.parse', () => {
46
+ const json = '{"__proto__":{"polluted":true}}';
47
+ const parsed = JSON.parse(json);
48
+
49
+ // This confirms that the environment allows parsing this key
50
+ // which justifies the need for our protections.
51
+ expect(Object.prototype.hasOwnProperty.call(parsed, '__proto__')).toBe(
52
+ true,
53
+ );
54
+ expect(parsed.__proto__).toEqual({ polluted: true });
55
+ });
56
+
57
+ it('Should preserve "constructor" as a plain key when using standard JSON.parse', () => {
58
+ const json = '{"constructor":{"prototype":{"polluted":true}}}';
59
+ const parsed = JSON.parse(json);
60
+
61
+ expect(parsed).toHaveProperty('constructor');
62
+ expect(parsed.constructor).toEqual({ prototype: { polluted: true } });
63
+ });
64
+ });
65
+ });
@@ -0,0 +1,281 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+
3
+ import { expandObject, minifyObject } from '../minifyObject';
4
+
5
+ describe('minifyObject', () => {
6
+ it('should be a function', () => {
7
+ expect(typeof minifyObject).toBe('function');
8
+ });
9
+
10
+ it('should return an array', () => {
11
+ expect(Array.isArray(minifyObject({}))).toBe(true);
12
+ });
13
+
14
+ it('should return an array with two items', () => {
15
+ expect(minifyObject({})).toHaveLength(2);
16
+ });
17
+
18
+ it('should return an array with two items, the first being an object', () => {
19
+ expect(typeof minifyObject({})[0]).toBe('object');
20
+ });
21
+
22
+ it('should return an array with two items, the second being an object', () => {
23
+ expect(typeof minifyObject({})[1]).toBe('object');
24
+ });
25
+
26
+ it('Should return a minified object', () => {
27
+ // we can't really test this because the minified object is random
28
+ // but we can test that the object is minified
29
+ // by checking that it is smaller than the original
30
+
31
+ const obj = {
32
+ lorem: 'ipsum',
33
+ dolor: 'sit',
34
+ amet: 'consectetur',
35
+ adipiscing: 'elit',
36
+ sed: [
37
+ 'lorem',
38
+ 'ipsum',
39
+ 'dolor',
40
+ 'sit',
41
+ 'amet',
42
+ 'consectetur',
43
+ 'adipiscing',
44
+ 'elit',
45
+ ],
46
+ sed2: [
47
+ 'lorem',
48
+ 'ipsum',
49
+ 'dolor',
50
+ 'sit',
51
+ 'amet',
52
+ 'consectetur',
53
+ 'adipiscing',
54
+ 'elit',
55
+ ],
56
+ };
57
+
58
+ const [minified] = minifyObject(obj);
59
+ expect(JSON.stringify(minified).length).toBeLessThan(
60
+ JSON.stringify(obj).length,
61
+ );
62
+ });
63
+
64
+ it('Should return a minified object with a reverse map', () => {
65
+ // check that all the keys in the reverse map are in the minified object
66
+ // either as a key or a value
67
+ const obj = {
68
+ lorem: 'ipsum',
69
+ dolor: 'sit',
70
+ amet: 'consectetur',
71
+ adipiscing: 'elit',
72
+ };
73
+
74
+ const [minified, reverseMap] = minifyObject(obj);
75
+
76
+ for (const key in reverseMap) {
77
+ expect(minified[key] || Object.values(minified).includes(key)).toBe(true);
78
+ }
79
+ });
80
+
81
+ describe('When a key only appears once', () => {
82
+ it('Should remain in place', () => {
83
+ const obj = {
84
+ lorem: 'ipsum',
85
+ dolor: false,
86
+ amet: true,
87
+ adipiscing: 1,
88
+ };
89
+
90
+ const [minified] = minifyObject(obj);
91
+ expect(minified).toEqual(obj);
92
+ });
93
+
94
+ it('Should not appear in the mapping', () => {
95
+ const obj = {
96
+ lorem: 'ipsum',
97
+ dolor: false,
98
+ amet: true,
99
+ adipiscing: 1,
100
+ };
101
+
102
+ const [, reverseMap] = minifyObject(obj);
103
+ expect(reverseMap).toEqual({});
104
+ });
105
+ });
106
+
107
+ it('Should omit nullish values', () => {
108
+ const obj = {
109
+ lorem: 'ipsum',
110
+ dolor: null,
111
+ amet: undefined,
112
+ adipiscing: '',
113
+ };
114
+
115
+ const [minified] = minifyObject(obj);
116
+ expect(minified).toEqual({ lorem: 'ipsum', adipiscing: '' });
117
+ });
118
+
119
+ it('Should omit functions', () => {
120
+ const obj = {
121
+ lorem: 'ipsum',
122
+ dolor: () => {},
123
+ amet() {},
124
+ adipiscing: '',
125
+ };
126
+
127
+ const [minified] = minifyObject(obj);
128
+ expect(minified).toEqual({ lorem: 'ipsum', adipiscing: '' });
129
+ });
130
+
131
+ it('Should omit symbols', () => {
132
+ const obj = {
133
+ lorem: 'ipsum',
134
+ dolor: Symbol('dolor'),
135
+ amet: Symbol('amet'),
136
+ adipiscing: '',
137
+ };
138
+
139
+ const [minified] = minifyObject(obj);
140
+ expect(minified).toEqual({ lorem: 'ipsum', adipiscing: '' });
141
+ });
142
+
143
+ it('Should give the most occuring values or keys the first keys', () => {
144
+ const obj = {
145
+ consectetur: ['consectetur'],
146
+ amet: ['amet', 'amet'],
147
+ sit: ['sit', 'sit', 'sit'],
148
+ dolor: ['dolor', 'dolor', 'dolor', 'dolor'],
149
+ ipsum: ['ipsum', 'ipsum', 'ipsum', 'ipsum', 'ipsum'],
150
+ lorem: ['lorem', 'lorem', 'lorem', 'lorem', 'lorem', 'lorem'],
151
+ };
152
+
153
+ const [_m, map] = minifyObject(obj);
154
+
155
+ expect(map).toEqual({
156
+ 0: 'lorem',
157
+ 1: 'ipsum',
158
+ 2: 'dolor',
159
+ 3: 'sit',
160
+ 4: 'amet',
161
+ 5: 'consectetur',
162
+ });
163
+ });
164
+
165
+ it('Should only minify values that are longer than the next key', () => {
166
+ const obj = {
167
+ a: 1,
168
+ b: 2,
169
+ c: 3,
170
+ lorem: 'lorem',
171
+ };
172
+ expect(minifyObject(obj)).toEqual([
173
+ {
174
+ a: 1,
175
+ b: 2,
176
+ c: 3,
177
+ 0: '0',
178
+ },
179
+ { 0: 'lorem' },
180
+ ]);
181
+ });
182
+
183
+ describe('replacer function', () => {
184
+ it('Should be a function receiving the value and key being iterated', () => {
185
+ const replacer = vi.fn((value: any, _key: string) => value);
186
+ const obj = {
187
+ lorem: 'hi!',
188
+ a: 1,
189
+ };
190
+ minifyObject(obj, replacer);
191
+
192
+ expect(replacer.mock.calls[0]).toEqual(['hi!', 'lorem']);
193
+ expect(replacer.mock.calls[1]).toEqual([1, 'a']);
194
+ });
195
+
196
+ it('Should replace the object value when a different value is being returned', () => {
197
+ const obj = {
198
+ lorem: 'hi!',
199
+ a: 1,
200
+ };
201
+
202
+ expect(minifyObject(obj, v => v + 1)).toEqual([
203
+ {
204
+ a: 2,
205
+ lorem: 'hi!1',
206
+ },
207
+ {},
208
+ ]);
209
+ });
210
+
211
+ it('Should omit values when returning undefined', () => {
212
+ const obj = {
213
+ lorem: 'hi!',
214
+ a: 1,
215
+ };
216
+
217
+ expect(
218
+ minifyObject(obj, (v, k) => (k === 'lorem' ? undefined : v)),
219
+ ).toEqual([
220
+ {
221
+ a: 1,
222
+ },
223
+ {},
224
+ ]);
225
+ });
226
+ });
227
+
228
+ describe('Auto removal of empty objects', () => {
229
+ it('Should remove empty objects', () => {
230
+ expect(
231
+ minifyObject({
232
+ a: 1,
233
+ data: {},
234
+ }),
235
+ ).toEqual([{ a: 1 }, {}]);
236
+ });
237
+
238
+ it('Should remove empty arrays', () => {
239
+ expect(
240
+ minifyObject({
241
+ a: 1,
242
+ data: [],
243
+ }),
244
+ ).toEqual([{ a: 1 }, {}]);
245
+ });
246
+ });
247
+ });
248
+
249
+ describe('expandObject', () => {
250
+ it('should be a function', () => {
251
+ expect(typeof expandObject).toBe('function');
252
+ });
253
+ it('should take the output of minifyObject and return a clone original object', () => {
254
+ const obj = {
255
+ lorem: 'ipsum',
256
+ dolor: ['lorem', 'ipsum'],
257
+ amet: {
258
+ lorem: 'ipsum',
259
+ dolor: ['sit', 'amet'],
260
+ },
261
+ adipiscing: 'elit',
262
+ number: 1,
263
+ boolean: true,
264
+ someValues: [
265
+ 'lorem',
266
+ 'ipsum',
267
+ 'dolor',
268
+ 'sit',
269
+ 'amet',
270
+ {
271
+ lorem: 'ipsum',
272
+ dolor: ['sit', 'amet'],
273
+ },
274
+ ],
275
+ };
276
+
277
+ const [minified, reverseMap] = minifyObject(obj);
278
+
279
+ expect(expandObject(minified, reverseMap)).toEqual(obj);
280
+ });
281
+ });