diff --git a/src/utils/__tests__/shake.spec-d.ts b/src/utils/__tests__/shake.spec-d.ts new file mode 100644 index 00000000..fd1866cb --- /dev/null +++ b/src/utils/__tests__/shake.spec-d.ts @@ -0,0 +1,19 @@ +/** + * @file Type Tests - shake + * @module tutils/utils/tests/unit-d/shake + */ + +import type { Shake } from '#src/types' +import type testSubject from '../shake' + +describe('unit-d:utils/shake', () => { + it('should return Shake', () => { + // Arrange + type T = { x: number; y: null } + type F = null + type Expect = Shake + + // Expect + expectTypeOf>().returns.toEqualTypeOf() + }) +}) diff --git a/src/utils/__tests__/shake.spec.ts b/src/utils/__tests__/shake.spec.ts new file mode 100644 index 00000000..87dded5f --- /dev/null +++ b/src/utils/__tests__/shake.spec.ts @@ -0,0 +1,23 @@ +/** + * @file Unit Tests - shake + * @module tutils/utils/tests/unit/shake + */ + +import testSubject from '../shake' + +describe('unit:utils/shake', () => { + let obj: { x: number; y: undefined } + + beforeAll(() => { + obj = { x: faker.number.int(), y: undefined } + }) + + it('should return filtered object', () => { + // Act + const result = testSubject(obj) + + // Expect + expect(result).to.equal(obj).and.have.property('x', obj.x) + expect(result).to.not.have.property('y') + }) +}) diff --git a/src/utils/index.ts b/src/utils/index.ts index f6aa9de0..911055c6 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -116,6 +116,7 @@ export { default as regexp } from './regexp' export { default as reverse } from './reverse' export { default as segment } from './segment' export { default as select } from './select' +export { default as shake } from './shake' export { default as sift } from './sift' export { default as sort } from './sort' export { default as split } from './split' diff --git a/src/utils/shake.ts b/src/utils/shake.ts new file mode 100644 index 00000000..b9266551 --- /dev/null +++ b/src/utils/shake.ts @@ -0,0 +1,45 @@ +/** + * @file Utilities - shake + * @module tutils/utils/shake + */ + +import type { Fn, ObjectCurly, Shake, Values } from '#src/types' +import cast from './cast' +import isUndefined from './is-undefined' +import properties from './properties' + +/** + * Remove properties from an object where the key-value meets a given `filter` + * condition. The initial target object **will** be modified. + * + * All `undefined` properties will be removed if a `filter` is not provided. + * + * Inherited properties will not be removed. + * + * **Note**: TypeScript does not track inheritance. The return type may differ + * from the actual return value when target objects contain inherited properties + * (e.g. `Map`, `Set`) that meet the `filter` condition. In such cases, the + * return type will have less properties than present on the return value. + * + * @see {@linkcode Shake} + * + * @todo examples + * + * @template T - Object to filter + * @template F - Key value filter + * + * @param {T} obj - Object to filter + * @param {Fn<[Values[number]], boolean>} [filter=isUndefined] - Value filter + * @return {Shake} Filtered object + */ +const shake = ( + obj: T, + filter: Fn<[Values[number]], boolean> = isUndefined +): Shake => { + return properties(obj).reduce>((acc, key) => { + filter(obj[cast(key)]) && Reflect.deleteProperty(acc, key) + return acc + }, cast(obj)) +} + +export default shake