From ec8c2dd005a018518d1c544fd8a4fcd6f77907b2 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 2 Jul 2024 22:17:46 -0400 Subject: [PATCH 01/12] refactor!: remove implicit nullish handling --- src/array/alphabetical.ts | 3 --- src/array/counting.ts | 3 --- src/array/diff.ts | 9 --------- src/array/merge.ts | 12 ------------ src/array/replace.ts | 3 --- src/array/replaceOrAppend.ts | 8 +------- src/array/select.ts | 3 --- src/array/selectFirst.ts | 3 --- src/array/sort.ts | 3 --- src/array/toggle.ts | 3 --- src/async/map.ts | 3 --- src/number/inRange.ts | 15 +-------------- src/object/assign.ts | 3 --- src/object/construct.ts | 3 --- src/object/crush.ts | 3 --- src/object/invert.ts | 3 --- src/object/keys.ts | 3 --- src/object/listify.ts | 9 +-------- src/object/mapEntries.ts | 3 --- src/object/omit.ts | 5 +---- src/object/pick.ts | 3 --- src/object/set.ts | 23 ++++++++--------------- src/object/shake.ts | 3 --- src/string/camel.ts | 16 +++++----------- src/string/capitalize.ts | 3 --- src/string/dash.ts | 16 +++++----------- src/string/pascal.ts | 4 ---- src/string/snake.ts | 19 +++++++------------ src/string/title.ts | 5 +---- src/string/trim.ts | 8 +------- 30 files changed, 31 insertions(+), 169 deletions(-) diff --git a/src/array/alphabetical.ts b/src/array/alphabetical.ts index 22920fe6..19b661f2 100644 --- a/src/array/alphabetical.ts +++ b/src/array/alphabetical.ts @@ -10,9 +10,6 @@ export function alphabetical( getter: (item: T) => string, direction: 'asc' | 'desc' = 'asc', ): T[] { - if (!array) { - return [] - } const asc = (a: T, b: T) => `${getter(a)}`.localeCompare(getter(b)) const dsc = (a: T, b: T) => `${getter(b)}`.localeCompare(getter(a)) return array.slice().sort(direction === 'desc' ? dsc : asc) diff --git a/src/array/counting.ts b/src/array/counting.ts index 40209302..5b781327 100644 --- a/src/array/counting.ts +++ b/src/array/counting.ts @@ -14,9 +14,6 @@ export function counting( array: readonly T[], identity: (item: T) => TId, ): Record { - if (!array) { - return {} as Record - } return array.reduce( (acc, item) => { const id = identity(item) diff --git a/src/array/diff.ts b/src/array/diff.ts index 9559d64b..fc733ea4 100644 --- a/src/array/diff.ts +++ b/src/array/diff.ts @@ -19,15 +19,6 @@ export function diff( identity: (item: T) => string | number | symbol = (t: T) => t as unknown as string | number | symbol, ): T[] { - if (!root?.length && !other?.length) { - return [] - } - if (root?.length === undefined) { - return [...other] - } - if (!other?.length) { - return [...root] - } const bKeys = other.reduce( (acc, item) => { acc[identity(item)] = true diff --git a/src/array/merge.ts b/src/array/merge.ts index 538a265e..a4e54bc0 100644 --- a/src/array/merge.ts +++ b/src/array/merge.ts @@ -20,18 +20,6 @@ export function merge( array: readonly T[], toKey: (item: T) => any, ): T[] { - if (!array && !prev) { - return [] - } - if (!array) { - return [...prev] - } - if (!prev) { - return [] - } - if (!toKey) { - return [...prev] - } const keys = new Map() for (const item of array) { keys.set(toKey(item), item) diff --git a/src/array/replace.ts b/src/array/replace.ts index c60f7417..f8e1c4d9 100644 --- a/src/array/replace.ts +++ b/src/array/replace.ts @@ -15,9 +15,6 @@ export function replace( newItem: T, match: (item: T, idx: number) => boolean, ): T[] { - if (!array) { - return [] - } if (newItem === undefined) { return [...array] } diff --git a/src/array/replaceOrAppend.ts b/src/array/replaceOrAppend.ts index 9aac4b04..f8d9b5f5 100644 --- a/src/array/replaceOrAppend.ts +++ b/src/array/replaceOrAppend.ts @@ -19,15 +19,9 @@ export function replaceOrAppend( newItem: T, match: (a: T, idx: number) => boolean, ): T[] { - if (!array && !newItem) { - return [] - } - if (!newItem) { + if (newItem === undefined) { return [...array] } - if (!array) { - return [newItem] - } const out = array.slice() for (let index = 0; index < array.length; index++) { if (match(array[index], index)) { diff --git a/src/array/select.ts b/src/array/select.ts index cc376aad..e889ea23 100644 --- a/src/array/select.ts +++ b/src/array/select.ts @@ -31,9 +31,6 @@ export function select( mapper: (item: T, index: number) => U, condition?: ((item: T, index: number) => boolean) | null, ): U[] { - if (!array) { - return [] - } let mapped: U return array.reduce((acc, item, index) => { if (condition) { diff --git a/src/array/selectFirst.ts b/src/array/selectFirst.ts index 9f9c16f1..2cd2c6db 100644 --- a/src/array/selectFirst.ts +++ b/src/array/selectFirst.ts @@ -20,9 +20,6 @@ export function selectFirst( mapper: (item: T, index: number) => U, condition?: (item: T, index: number) => boolean, ): U | undefined { - if (!array) { - return undefined - } let foundIndex = -1 const found = array.find((item, index) => { foundIndex = index diff --git a/src/array/sort.ts b/src/array/sort.ts index 48b0329d..d8b8ea1f 100644 --- a/src/array/sort.ts +++ b/src/array/sort.ts @@ -21,9 +21,6 @@ export function sort( getter: (item: T) => number, desc = false, ): T[] { - if (!array) { - return [] - } const asc = (a: T, b: T) => getter(a) - getter(b) const dsc = (a: T, b: T) => getter(b) - getter(a) return array.slice().sort(desc === true ? dsc : asc) diff --git a/src/array/toggle.ts b/src/array/toggle.ts index d013e043..c4c35bd1 100644 --- a/src/array/toggle.ts +++ b/src/array/toggle.ts @@ -42,9 +42,6 @@ export function toggle( strategy?: 'prepend' | 'append' }, ): T[] { - if (!array) { - return item !== undefined ? [item] : [] - } if (item === undefined) { return [...array] } diff --git a/src/async/map.ts b/src/async/map.ts index f18e694d..a80ffdfa 100644 --- a/src/async/map.ts +++ b/src/async/map.ts @@ -17,9 +17,6 @@ export async function map( array: readonly T[], asyncMapFunc: (item: T, index: number) => PromiseLike, ): Promise { - if (!array) { - return [] - } const result = [] let index = 0 for (const value of array) { diff --git a/src/number/inRange.ts b/src/number/inRange.ts index b1072bb5..687e224d 100644 --- a/src/number/inRange.ts +++ b/src/number/inRange.ts @@ -34,20 +34,7 @@ export function inRange(number: number, end: number): boolean * ``` */ export function inRange(number: number, start: number, end: number): boolean -export function inRange(number: number, start: number, end?: number): boolean { - const isTypeSafe = - typeof number === 'number' && - typeof start === 'number' && - (typeof end === 'undefined' || typeof end === 'number') - - if (!isTypeSafe) { - return false - } - - if (typeof end === 'undefined') { - end = start - start = 0 - } +export function inRange(number: number, start: number, end = 0): boolean { return number >= Math.min(start, end) && number < Math.max(start, end) } diff --git a/src/object/assign.ts b/src/object/assign.ts index d429db33..fc878e8e 100644 --- a/src/object/assign.ts +++ b/src/object/assign.ts @@ -27,9 +27,6 @@ export function assign< TInitial extends Record, TOverride extends Record, >(initial: TInitial, override: TOverride): Assign { - if (!initial || !override) { - return (initial ?? override ?? {}) as any - } const proto = Object.getPrototypeOf(initial) const merged = proto ? { ...initial } diff --git a/src/object/construct.ts b/src/object/construct.ts index 450546b5..47233ad6 100644 --- a/src/object/construct.ts +++ b/src/object/construct.ts @@ -13,9 +13,6 @@ import { set } from 'radashi' * @version 12.1.0 */ export function construct(obj: TObject): object { - if (!obj) { - return {} - } return Object.keys(obj).reduce((acc, path) => { return set(acc, path, (obj as any)[path]) }, {}) diff --git a/src/object/crush.ts b/src/object/crush.ts index 5f0c6840..d39d6dbc 100644 --- a/src/object/crush.ts +++ b/src/object/crush.ts @@ -13,9 +13,6 @@ import { type Intersect, isArray, isObject, type Simplify } from 'radashi' * @version 12.1.0 */ export function crush(value: T): Crush { - if (!value) { - return {} as Crush - } return (function crushReducer( crushed: Crush, value: unknown, diff --git a/src/object/invert.ts b/src/object/invert.ts index 671cae01..cae82bc6 100644 --- a/src/object/invert.ts +++ b/src/object/invert.ts @@ -15,9 +15,6 @@ export function invert< TKey extends string | number | symbol, TValue extends string | number | symbol, >(obj: Record): Record { - if (!obj) { - return {} as Record - } const keys = Object.keys(obj) as TKey[] return keys.reduce( (acc, key) => { diff --git a/src/object/keys.ts b/src/object/keys.ts index a5fbb153..b51efce4 100644 --- a/src/object/keys.ts +++ b/src/object/keys.ts @@ -14,9 +14,6 @@ import { isArray, isPlainObject } from 'radashi' * @version 12.1.0 */ export function keys(value: object): string[] { - if (!value) { - return [] - } const keys: string[] = [] const keyPath: (string | number)[] = [] const recurse = (value: any) => { diff --git a/src/object/listify.ts b/src/object/listify.ts index 54931ce9..59203c0e 100644 --- a/src/object/listify.ts +++ b/src/object/listify.ts @@ -18,14 +18,7 @@ export function listify( obj: Record, toItem: (key: Key, value: Value) => Item, ): Item[] { - if (!obj) { - return [] - } - const entries = Object.entries(obj) - if (entries.length === 0) { - return [] - } - return entries.reduce((acc, entry) => { + return Object.entries(obj).reduce((acc, entry) => { acc.push(toItem(entry[0] as Key, entry[1] as Value)) return acc }, [] as Item[]) diff --git a/src/object/mapEntries.ts b/src/object/mapEntries.ts index 74bd43f5..7f220394 100644 --- a/src/object/mapEntries.ts +++ b/src/object/mapEntries.ts @@ -19,9 +19,6 @@ export function mapEntries< obj: Record, toEntry: (key: TKey, value: TValue) => [TNewKey, TNewValue], ): Record { - if (!obj) { - return {} as Record - } return Object.entries(obj).reduce( (acc, [key, value]) => { const [newKey, newValue] = toEntry(key as TKey, value as TValue) diff --git a/src/object/omit.ts b/src/object/omit.ts index ed8de03c..5f3f57b7 100644 --- a/src/object/omit.ts +++ b/src/object/omit.ts @@ -15,10 +15,7 @@ export function omit( obj: T, keys: readonly TKeys[], ): Omit { - if (!obj) { - return {} as Omit - } - if (!keys || keys.length === 0) { + if (keys.length === 0) { return obj as Omit } return keys.reduce( diff --git a/src/object/pick.ts b/src/object/pick.ts index 2b3e4bc7..a9e00226 100644 --- a/src/object/pick.ts +++ b/src/object/pick.ts @@ -31,9 +31,6 @@ export function pick( obj: T, filter: KeyFilter | null, ) { - if (!obj) { - return {} - } let keys: (keyof T)[] = filter as any if (isArray(filter)) { filter = null diff --git a/src/object/set.ts b/src/object/set.ts index c580f15c..cff9f6dd 100644 --- a/src/object/set.ts +++ b/src/object/set.ts @@ -17,24 +17,17 @@ export function set( path: string, value: K, ): T { - if (!initial) { - return {} as T - } - if (!path || value === undefined) { + if (value === undefined) { return initial } - const root: any = clone(initial) const keys = path.match(/[^.[\]]+/g) - if (keys) { - keys.reduce( - (object, key, i) => - i < keys.length - 1 - ? (object[key] ??= isIntString(keys[i + 1]) ? [] : {}) - : (object[key] = value), - root, - ) - } - + keys?.reduce( + (object, key, i) => + i < keys.length - 1 + ? (object[key] ??= isIntString(keys[i + 1]) ? [] : {}) + : (object[key] = value), + root, + ) return root } diff --git a/src/object/shake.ts b/src/object/shake.ts index 66dcde26..0ee58397 100644 --- a/src/object/shake.ts +++ b/src/object/shake.ts @@ -28,9 +28,6 @@ export function shake( obj: T, filter: (value: unknown) => boolean = value => value === undefined, ): T { - if (!obj) { - return {} as T - } return (Object.keys(obj) as (keyof T)[]).reduce((acc, key) => { if (!filter(obj[key])) { acc[key] = obj[key] diff --git a/src/string/camel.ts b/src/string/camel.ts index 9da12a74..3b00ca9e 100644 --- a/src/string/camel.ts +++ b/src/string/camel.ts @@ -13,17 +13,11 @@ import { capitalize } from 'radashi' * @version 12.1.0 */ export function camel(str: string): string { - const parts = - str - ?.replace(/([A-Z])+/g, capitalize) - ?.split(/(?=[A-Z])|[\.\-\s_]/) - .map(x => x.toLowerCase()) ?? [] - if (parts.length === 0) { - return '' - } - if (parts.length === 1) { - return parts[0] - } + const parts = str + .replace(/([A-Z])+/g, capitalize) + .split(/(?=[A-Z])|[\.\-\s_]/) + .map(x => x.toLowerCase()) + return parts.reduce((acc, part) => { return `${acc}${part.charAt(0).toUpperCase()}${part.slice(1)}` }) diff --git a/src/string/capitalize.ts b/src/string/capitalize.ts index 1e91907b..19b676b1 100644 --- a/src/string/capitalize.ts +++ b/src/string/capitalize.ts @@ -10,9 +10,6 @@ * @version 12.1.0 */ export function capitalize(str: string): string { - if (!str || str.length === 0) { - return '' - } const lower = str.toLowerCase() return lower.substring(0, 1).toUpperCase() + lower.substring(1, lower.length) } diff --git a/src/string/dash.ts b/src/string/dash.ts index 51fca152..e32af18a 100644 --- a/src/string/dash.ts +++ b/src/string/dash.ts @@ -13,17 +13,11 @@ import { capitalize } from 'radashi' * @version 12.1.0 */ export function dash(str: string): string { - const parts = - str - ?.replace(/([A-Z])+/g, capitalize) - ?.split(/(?=[A-Z])|[\.\-\s_]/) - .map(x => x.toLowerCase()) ?? [] - if (parts.length === 0) { - return '' - } - if (parts.length === 1) { - return parts[0] - } + const parts = str + .replace(/([A-Z])+/g, capitalize) + .split(/(?=[A-Z])|[\.\-\s_]/) + .map(x => x.toLowerCase()) + return parts.reduce((acc, part) => { return `${acc}-${part.toLowerCase()}` }) diff --git a/src/string/pascal.ts b/src/string/pascal.ts index a734ea76..6781edc8 100644 --- a/src/string/pascal.ts +++ b/src/string/pascal.ts @@ -11,10 +11,6 @@ * @version 12.1.0 */ export function pascal(str: string): string { - if (!str) { - return '' - } - const result = str.replace( /(?:[^\w\d]|_|\s)+(\w)([A-Z]+)?/g, (_, firstCharacter, capitalizedLetters) => { diff --git a/src/string/snake.ts b/src/string/snake.ts index 46a733c6..38653f59 100644 --- a/src/string/snake.ts +++ b/src/string/snake.ts @@ -18,21 +18,16 @@ export function snake( splitOnNumber?: boolean }, ): string { - const parts = - str - ?.replace(/([A-Z])+/g, capitalize) - .split(/(?=[A-Z])|[\.\-\s_]/) - .map(x => x.toLowerCase()) ?? [] - if (parts.length === 0) { - return '' - } - if (parts.length === 1) { - return parts[0] - } + const parts = str + .replace(/([A-Z])+/g, capitalize) + .split(/(?=[A-Z])|[\.\-\s_]/) + .map(x => x.toLowerCase()) + const result = parts.reduce((acc, part) => { return `${acc}_${part.toLowerCase()}` }) + return options?.splitOnNumber === false ? result - : result.replace(/([A-Za-z]{1}[0-9]{1})/, val => `${val[0]!}_${val[1]!}`) + : result.replace(/([A-Za-z]{1}[0-9]{1})/, val => `${val[0]}_${val[1]}`) } diff --git a/src/string/title.ts b/src/string/title.ts index be7aef1f..1aca4460 100644 --- a/src/string/title.ts +++ b/src/string/title.ts @@ -13,10 +13,7 @@ import { capitalize } from 'radashi' * ``` * @version 12.1.0 */ -export function title(str: string | null | undefined): string { - if (!str) { - return '' - } +export function title(str: string): string { return str .split(/(?=[A-Z])|[\.\-\s_]/) .map(s => s.trim()) diff --git a/src/string/trim.ts b/src/string/trim.ts index ecc8f0c0..d963481b 100644 --- a/src/string/trim.ts +++ b/src/string/trim.ts @@ -13,13 +13,7 @@ * ``` * @version 12.1.0 */ -export function trim( - str: string | null | undefined, - charsToTrim = ' ', -): string { - if (!str) { - return '' - } +export function trim(str: string, charsToTrim = ' '): string { const toTrim = charsToTrim.replace(/[\W]{1}/g, '\\$&') const regex = new RegExp(`^[${toTrim}]+|[${toTrim}]+$`, 'g') return str.replace(regex, '') From 56979c592d1b2d08b83161f7e86776a1eee33e2f Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:19:35 -0400 Subject: [PATCH 02/12] chore: update tests --- tests/array/alphabetical.test.ts | 6 ------ tests/array/counting.test.ts | 6 ------ tests/array/diff.test.ts | 14 -------------- tests/array/first.test.ts | 6 ------ tests/array/fork.test.ts | 7 ------- tests/array/last.test.ts | 6 ------ tests/array/merge.test.ts | 18 ------------------ tests/array/replace.test.ts | 6 ------ tests/array/replaceOrAppend.test.ts | 17 ++++++----------- tests/array/select.test.ts | 18 ------------------ tests/array/selectFirst.test.ts | 18 ------------------ tests/array/sort.test.ts | 4 ---- tests/array/toggle.test.ts | 8 -------- tests/async/map.test.ts | 7 ------- tests/number/inRange.test.ts | 13 ------------- tests/object/assign.test.ts | 14 -------------- tests/object/construct.test.ts | 5 ----- tests/object/invert.test.ts | 16 +++++----------- tests/object/keys.test.ts | 5 ----- tests/object/listify.test.ts | 8 -------- tests/object/mapEntries.test.ts | 21 +++++---------------- tests/object/omit.test.ts | 10 ---------- tests/object/set.test.ts | 8 -------- tests/object/shake.test.ts | 4 ---- tests/string/camel.test.ts | 4 ---- tests/string/capitalize.test.ts | 4 ---- tests/string/dash.test.ts | 4 ---- tests/string/pascal.test.ts | 4 ---- tests/string/snake.test.ts | 4 ---- tests/string/title.test.ts | 4 ---- tests/string/trim.test.ts | 5 ----- 31 files changed, 16 insertions(+), 258 deletions(-) diff --git a/tests/array/alphabetical.test.ts b/tests/array/alphabetical.test.ts index 69f39bc6..af16b07e 100644 --- a/tests/array/alphabetical.test.ts +++ b/tests/array/alphabetical.test.ts @@ -1,7 +1,5 @@ import * as _ from 'radashi' -const cast = (value: any) => value as string[] - describe('alphabetical', () => { test('uses getter', () => { const list = [{ name: 'Leo' }, { name: 'AJ' }, { name: 'Cynthia' }] @@ -17,8 +15,4 @@ describe('alphabetical', () => { expect(result[1].name).toBe('Cynthia') expect(result[2].name).toBe('AJ') }) - test('gracefully handles null input list', () => { - const result = _.alphabetical(cast(null), x => x) - expect(result).toEqual([]) - }) }) diff --git a/tests/array/counting.test.ts b/tests/array/counting.test.ts index 0b458a6a..b7a874fa 100644 --- a/tests/array/counting.test.ts +++ b/tests/array/counting.test.ts @@ -1,7 +1,5 @@ import * as _ from 'radashi' -const cast = (value: any): T => value - describe('counting', () => { const people = [ { name: 'ray', group: 'X' }, @@ -16,8 +14,4 @@ describe('counting', () => { Y: 2, }) }) - test('does not error on bad input', () => { - expect(() => _.counting(cast(null), x => x)).not.toThrow() - expect(() => _.counting(cast(undefined), x => x)).not.toThrow() - }) }) diff --git a/tests/array/diff.test.ts b/tests/array/diff.test.ts index 34fe5fd1..e085dd21 100644 --- a/tests/array/diff.test.ts +++ b/tests/array/diff.test.ts @@ -1,20 +1,6 @@ import * as _ from 'radashi' -const cast = (value: any): T => value - describe('diff', () => { - test('handles null root', () => { - const result = _.diff(cast(null), ['a']) - expect(result).toEqual(['a']) - }) - test('handles null other', () => { - const result = _.diff(['a'], cast(null)) - expect(result).toEqual(['a']) - }) - test('handles null inputs', () => { - const result = _.diff(cast(null), cast(null)) - expect(result).toEqual([]) - }) test('handles empty array root', () => { const result = _.diff([], ['a']) expect(result).toEqual([]) diff --git a/tests/array/first.test.ts b/tests/array/first.test.ts index 62c71fbd..8fde006f 100644 --- a/tests/array/first.test.ts +++ b/tests/array/first.test.ts @@ -1,7 +1,5 @@ import * as _ from 'radashi' -const cast = (value: any) => value as unknown[] - describe('first', () => { test('returns first item in list', () => { const list = [ @@ -17,8 +15,4 @@ describe('first', () => { const result = _.first(list, 'yolo') expect(result).toBe('yolo') }) - test('gracefully handles null input list', () => { - const result = _.first(cast(null)) - expect(result).toBeUndefined() - }) }) diff --git a/tests/array/fork.test.ts b/tests/array/fork.test.ts index 79c4b2f5..a7d34373 100644 --- a/tests/array/fork.test.ts +++ b/tests/array/fork.test.ts @@ -1,13 +1,6 @@ import * as _ from 'radashi' -const cast = (value: any) => value as unknown[] - describe('fork', () => { - test('returns two empty arrays for null input', () => { - const [a, b] = _.fork(cast(null), x => !!x) - expect(a).toEqual([]) - expect(b).toEqual([]) - }) test('returns two empty arrays for one empty array input', () => { const [a, b] = _.fork([], x => !!x) expect(a).toEqual([]) diff --git a/tests/array/last.test.ts b/tests/array/last.test.ts index 58dc0895..1d76a7aa 100644 --- a/tests/array/last.test.ts +++ b/tests/array/last.test.ts @@ -1,7 +1,5 @@ import * as _ from 'radashi' -const cast = (value: any) => value as unknown[] - describe('last', () => { test('returns last item in list', () => { const list = [ @@ -17,8 +15,4 @@ describe('last', () => { const result = _.last(list, 'yolo') expect(result).toBe('yolo') }) - test('gracefully handles null input list', () => { - const result = _.last(cast(null)) - expect(result).toBeUndefined() - }) }) diff --git a/tests/array/merge.test.ts b/tests/array/merge.test.ts index d711b696..155aff53 100644 --- a/tests/array/merge.test.ts +++ b/tests/array/merge.test.ts @@ -1,28 +1,10 @@ import * as _ from 'radashi' -const cast = (value: any) => value as T - describe('merge', () => { - test('returns empty array for two null inputs', () => { - const result = _.merge(cast(null), cast(null), _ => '') - expect(result).toEqual([]) - }) test('returns an empty array for two empty array inputs', () => { const result = _.merge([], [], _ => '') expect(result).toEqual([]) }) - test('returns root for a null other input', () => { - const result = _.merge([], cast(null), _ => '') - expect(result).toEqual([]) - }) - test('returns empty array for a null root input', () => { - const result = _.merge(cast(null), [], _ => '') - expect(result).toEqual([]) - }) - test('returns root for a null matcher input', () => { - const result = _.merge(['a'], [], cast(null)) - expect(result).toEqual(['a']) - }) test('returns correctly merged lists', () => { const inputA = [ { name: 'ray', group: 'X' }, diff --git a/tests/array/replace.test.ts b/tests/array/replace.test.ts index 758d133c..fdaf6970 100644 --- a/tests/array/replace.test.ts +++ b/tests/array/replace.test.ts @@ -1,12 +1,6 @@ import * as _ from 'radashi' -const cast = (value: any): T => value - describe('replace', () => { - test('returns empty list for null input list', () => { - const result = _.replace(cast(null), 'x', () => false) - expect(result).toEqual([]) - }) test('returns the list for an undefined new item', () => { const result = _.replace(['a'], undefined, () => true) expect(result).toEqual(['a']) diff --git a/tests/array/replaceOrAppend.test.ts b/tests/array/replaceOrAppend.test.ts index a71f13d6..cbb3fbf5 100644 --- a/tests/array/replaceOrAppend.test.ts +++ b/tests/array/replaceOrAppend.test.ts @@ -1,24 +1,19 @@ import * as _ from 'radashi' -const cast = (value: any) => value as T - describe('replaceOrAppend', () => { const letters = ['a', 'b', 'c', 'd', 'e'] const lettersXA = ['XA', 'b', 'c', 'd', 'e'] const lettersXC = ['a', 'b', 'XC', 'd', 'e'] const lettersXE = ['a', 'b', 'c', 'd', 'XE'] const lettersXX = ['a', 'b', 'c', 'd', 'e', 'XX'] - test('returns empty array for two null inputs', () => { - const result = _.replaceOrAppend(cast(null), null, _ => false) - expect(result).toEqual([]) - }) - test('returns array with new item for null list input', () => { - const result = _.replaceOrAppend(cast(null), 'a', _ => false) + + test('returns list for undefined new item input', () => { + const result = _.replaceOrAppend(['a'], undefined, _ => false) expect(result).toEqual(['a']) }) - test('returns list for null new item input', () => { - const result = _.replaceOrAppend(['a'], null, _ => false) - expect(result).toEqual(['a']) + test('returns replaced item when value is null', () => { + const result = _.replaceOrAppend(['a'], null, i => i === 'a') + expect(result).toEqual([null]) }) test('returns list with item replacing match by index', () => { const result = _.replaceOrAppend( diff --git a/tests/array/select.test.ts b/tests/array/select.test.ts index 8ef8105c..9f409edc 100644 --- a/tests/array/select.test.ts +++ b/tests/array/select.test.ts @@ -1,24 +1,6 @@ import * as _ from 'radashi' -const cast = (value: any): T => value - describe('select', () => { - test('does not fail on bad input', () => { - expect( - _.select( - cast(null), - x => x, - x => x, - ), - ).toEqual([]) - expect( - _.select( - cast(undefined), - x => x, - x => x, - ), - ).toEqual([]) - }) test('returns mapped and filtered values', () => { const list = [ { group: 'a', word: 'hello' }, diff --git a/tests/array/selectFirst.test.ts b/tests/array/selectFirst.test.ts index a6c6fefc..86c27dd6 100644 --- a/tests/array/selectFirst.test.ts +++ b/tests/array/selectFirst.test.ts @@ -1,24 +1,6 @@ import * as _ from 'radashi' -const cast = (value: any): T => value - describe('selectFirst', () => { - test('does not fail on bad input', () => { - expect( - _.selectFirst( - cast(null), - x => x, - x => x, - ), - ).toBeUndefined() - expect( - _.selectFirst( - cast(undefined), - x => x, - x => x, - ), - ).toBeUndefined() - }) test('returns mapped result of first value that meets the condition', () => { const list = [ { group: 'a', word: 'hello' }, diff --git a/tests/array/sort.test.ts b/tests/array/sort.test.ts index 27e23feb..2e385acf 100644 --- a/tests/array/sort.test.ts +++ b/tests/array/sort.test.ts @@ -15,8 +15,4 @@ describe('sort', () => { expect(result[1].index).toBe(1) expect(result[2].index).toBe(0) }) - test('gracefully handles null input list', () => { - const result = _.sort(null as any as number[], x => x) - expect(result).toEqual([]) - }) }) diff --git a/tests/array/toggle.test.ts b/tests/array/toggle.test.ts index 8b044e22..09beeeb8 100644 --- a/tests/array/toggle.test.ts +++ b/tests/array/toggle.test.ts @@ -1,14 +1,6 @@ import * as _ from 'radashi' -const cast = (value: any): T => value - describe('toggle', () => { - test('should handle null input list', () => { - let result = _.toggle(cast(null), 'a') - expect(result).toEqual(['a']) - result = _.toggle(cast(null), undefined) - expect(result).toEqual([]) - }) test('should skip undefined item', () => { const result = _.toggle(['a'], undefined) expect(result).toEqual(['a']) diff --git a/tests/async/map.test.ts b/tests/async/map.test.ts index a8f359b4..9c1da954 100644 --- a/tests/async/map.test.ts +++ b/tests/async/map.test.ts @@ -1,7 +1,5 @@ import * as _ from 'radashi' -const cast = (value: any): T => value - describe('map', () => { beforeEach(() => { vi.useFakeTimers({ shouldAdvanceTime: true }) @@ -16,11 +14,6 @@ describe('map', () => { expect(result).toEqual([1, 4, 9, 16]) }) - test('handles null input', async () => { - const result = await _.map(cast(null), async () => '') - expect(result).toEqual([]) - }) - test('passes correct indexes', async () => { const array = ['a', 'b', 'c', 'd'] const mapper = async (l: string, index: number) => `${l}${index}` diff --git a/tests/number/inRange.test.ts b/tests/number/inRange.test.ts index d0543452..2c457842 100644 --- a/tests/number/inRange.test.ts +++ b/tests/number/inRange.test.ts @@ -1,19 +1,6 @@ import * as _ from 'radashi' describe('inRange', () => { - test('handles nullish values', () => { - expect(_.inRange(0, 1, null as any)).toBe(false) - expect(_.inRange(0, null as any, 1)).toBe(false) - expect(_.inRange(null as any, 0, 1)).toBe(false) - expect(_.inRange(0, undefined as any, 1)).toBe(false) - expect(_.inRange(undefined as any, 0, 1)).toBe(false) - - expect(_.inRange(0, 1, undefined as any)).toBe(true) - }) - test('handles bad input', () => { - const result = _.inRange(0, 1, {} as any) - expect(result).toBe(false) - }) test('computes correctly', () => { expect(_.inRange(10, 0, 5)).toBe(false) expect(_.inRange(10, 0, 20)).toBe(true) diff --git a/tests/object/assign.test.ts b/tests/object/assign.test.ts index 2c1d703e..b3af6261 100644 --- a/tests/object/assign.test.ts +++ b/tests/object/assign.test.ts @@ -1,7 +1,5 @@ import * as _ from 'radashi' -const cast = (value: any): T => value - describe('assign', () => { const initial = { name: 'jay', @@ -25,18 +23,6 @@ describe('assign', () => { }, }, } - test('handles both null input', () => { - const result = _.assign(cast(null), cast(null)) - expect(result).toEqual({}) - }) - test('handles null first input', () => { - const result = _.assign({ a: 'y' }, cast(null)) - expect(result).toEqual({ a: 'y' }) - }) - test('handles null last input', () => { - const result = _.assign(cast(null), { a: 'y' }) - expect(result).toEqual({ a: 'y' }) - }) test('correctly assign a with values from b', () => { const result = _.assign(initial, override) expect(result).toEqual(override) diff --git a/tests/object/construct.test.ts b/tests/object/construct.test.ts index 8c58d3d4..2a77610d 100644 --- a/tests/object/construct.test.ts +++ b/tests/object/construct.test.ts @@ -1,11 +1,6 @@ import * as _ from 'radashi' describe('construct', () => { - test('handles bad input', () => { - expect(_.construct({})).toEqual({}) - expect(_.construct(null as any)).toEqual({}) - expect(_.construct(undefined as any)).toEqual({}) - }) test('returns correctly constructed object', () => { const now = new Date() const ra = { diff --git a/tests/object/invert.test.ts b/tests/object/invert.test.ts index da7f9362..cc5de469 100644 --- a/tests/object/invert.test.ts +++ b/tests/object/invert.test.ts @@ -1,18 +1,12 @@ import * as _ from 'radashi' -const cast = (value: any): T => value - describe('invert', () => { - const peopleByRole = { - admin: 'jay', - user: 'fey', - guest: 'bray', - } - test('handles null input', () => { - const result = _.invert(cast(null)) - expect(result).toEqual({}) - }) test('correctly maps keys and values', () => { + const peopleByRole = { + admin: 'jay', + user: 'fey', + guest: 'bray', + } const result = _.invert(peopleByRole) expect(result.jay).toBe('admin') expect(result.fey).toBe('user') diff --git a/tests/object/keys.test.ts b/tests/object/keys.test.ts index f0177179..b8c81822 100644 --- a/tests/object/keys.test.ts +++ b/tests/object/keys.test.ts @@ -1,11 +1,6 @@ import * as _ from 'radashi' describe('keys', () => { - test('handles bad input', () => { - expect(_.keys({})).toEqual([]) - expect(_.keys(null as any)).toEqual([]) - expect(_.keys(undefined as any)).toEqual([]) - }) test('returns correct list of keys', () => { const ra = { name: 'ra', diff --git a/tests/object/listify.test.ts b/tests/object/listify.test.ts index 45703de5..8c96b9df 100644 --- a/tests/object/listify.test.ts +++ b/tests/object/listify.test.ts @@ -1,14 +1,6 @@ import * as _ from 'radashi' describe('listify', () => { - test('handles null input', () => { - const result = _.listify(null as any as Record, () => 1) - expect(result).toEqual([]) - }) - test('handles empty object', () => { - const result = _.listify({} as Record, () => 1) - expect(result).toEqual([]) - }) test('calls toItem to convert to list', () => { type Index = 'one' | 'two' const obj: Record = { diff --git a/tests/object/mapEntries.test.ts b/tests/object/mapEntries.test.ts index 039bddea..ac4aaf23 100644 --- a/tests/object/mapEntries.test.ts +++ b/tests/object/mapEntries.test.ts @@ -1,23 +1,12 @@ import * as _ from 'radashi' -const cast = (value: any): T => value - describe('mapEntries', () => { - const peopleByRole = { - admin: 'jay', - user: 'fey', - guest: 'bray', - } - test('handles null input', () => { - const result = _.mapEntries( - cast(null), - cast<(key: never, value: never) => [string | number | symbol, unknown]>( - null, - ), - ) - expect(result).toEqual({}) - }) test('correctly maps keys and values', () => { + const peopleByRole = { + admin: 'jay', + user: 'fey', + guest: 'bray', + } const result = _.mapEntries(peopleByRole, (key, value) => [ value, key.toUpperCase(), diff --git a/tests/object/omit.test.ts b/tests/object/omit.test.ts index 8e0e770d..b92fc90b 100644 --- a/tests/object/omit.test.ts +++ b/tests/object/omit.test.ts @@ -1,25 +1,15 @@ import * as _ from 'radashi' -const cast = (value: any): T => value - describe('omit', () => { const person = { name: 'jay', age: 20, active: true, } - test('handles null input', () => { - const result = _.omit(null, []) - expect(result).toEqual({}) - }) test('handles empty keys', () => { const result = _.omit(person, []) expect(result).toEqual(person) }) - test('handles null keys', () => { - const result = _.omit(person, cast(null)) - expect(result).toEqual(person) - }) test('returns object without omitted properties', () => { const result = _.omit(person, ['name']) expect(result).toEqual({ diff --git a/tests/object/set.test.ts b/tests/object/set.test.ts index 21d3c663..2d954008 100644 --- a/tests/object/set.test.ts +++ b/tests/object/set.test.ts @@ -1,14 +1,6 @@ import * as _ from 'radashi' describe('set', () => { - test('handle bad input', () => { - expect(_.set({}, '', {})).toEqual({}) - expect(_.set({}, null as any, {})).toEqual({}) - expect(_.set({}, '', null as any)).toEqual({}) - expect(_.set(null as any, '', {})).toEqual({}) - expect(_.set(null as any, null as any, null as any)).toEqual({}) - }) - test('set a direct property', () => { expect(_.set({ foo: true }, 'foo', false)).toEqual({ foo: false }) expect(_.set({}, 'foo', 0)).toEqual({ foo: 0 }) diff --git a/tests/object/shake.test.ts b/tests/object/shake.test.ts index 4944d993..95df1c38 100644 --- a/tests/object/shake.test.ts +++ b/tests/object/shake.test.ts @@ -31,8 +31,4 @@ describe('shake', () => { r: 'x', }) }) - test('handles undefined input', () => { - const result = _.shake(undefined!) - expect(result).toEqual({}) - }) }) diff --git a/tests/string/camel.test.ts b/tests/string/camel.test.ts index 2363072d..2c098dbf 100644 --- a/tests/string/camel.test.ts +++ b/tests/string/camel.test.ts @@ -9,10 +9,6 @@ describe('camel', () => { const result = _.camel('hello') expect(result).toBe('hello') }) - test('returns empty string for empty input', () => { - const result = _.camel(null as any) - expect(result).toBe('') - }) test('a word in camel case should remain in camel case', () => { const result = _.camel('helloWorld') expect(result).toBe('helloWorld') diff --git a/tests/string/capitalize.test.ts b/tests/string/capitalize.test.ts index d8dc80e6..afc9c769 100644 --- a/tests/string/capitalize.test.ts +++ b/tests/string/capitalize.test.ts @@ -1,10 +1,6 @@ import * as _ from 'radashi' describe('capitalize', () => { - test('handles null', () => { - const result = _.capitalize(null as any) - expect(result).toBe('') - }) test('converts hello as Hello', () => { const result = _.capitalize('hello') expect(result).toBe('Hello') diff --git a/tests/string/dash.test.ts b/tests/string/dash.test.ts index 98060568..218a5711 100644 --- a/tests/string/dash.test.ts +++ b/tests/string/dash.test.ts @@ -9,10 +9,6 @@ describe('dash', () => { const result = _.dash('hello') expect(result).toBe('hello') }) - test('returns empty string for empty input', () => { - const result = _.dash(null as any) - expect(result).toBe('') - }) test('must handle strings that are camelCase', () => { const result = _.dash('helloWorld') expect(result).toBe('hello-world') diff --git a/tests/string/pascal.test.ts b/tests/string/pascal.test.ts index e0692ff8..5943bddd 100644 --- a/tests/string/pascal.test.ts +++ b/tests/string/pascal.test.ts @@ -9,10 +9,6 @@ describe('pascal', () => { const result = _.pascal('hello') expect(result).toBe('Hello') }) - test('returns empty string for empty input', () => { - const result = _.pascal(null as any) - expect(result).toBe('') - }) test('converts camelCase to PascalCase', () => { const result = _.pascal('fooBar') expect(result).toBe('FooBar') diff --git a/tests/string/snake.test.ts b/tests/string/snake.test.ts index 12e55e3f..2e803f73 100644 --- a/tests/string/snake.test.ts +++ b/tests/string/snake.test.ts @@ -27,10 +27,6 @@ describe('snake', () => { const result = _.snake('hello') expect(result).toBe('hello') }) - test('returns empty string for empty input', () => { - const result = _.snake(null as any) - expect(result).toBe('') - }) test('returns non alphanumerics with _', () => { const result = _.snake('Exobase Starter_flash AND-go') expect(result).toBe('exobase_starter_flash_and_go') diff --git a/tests/string/title.test.ts b/tests/string/title.test.ts index 78aeac76..a6513c60 100644 --- a/tests/string/title.test.ts +++ b/tests/string/title.test.ts @@ -10,8 +10,4 @@ describe('title', () => { 'Query All Items In Database', ) }) - test('returns empty string for bad input', () => { - expect(_.title(null)).toBe('') - expect(_.title(undefined)).toBe('') - }) }) diff --git a/tests/string/trim.test.ts b/tests/string/trim.test.ts index 816ae5ad..aa122a11 100644 --- a/tests/string/trim.test.ts +++ b/tests/string/trim.test.ts @@ -1,10 +1,6 @@ import * as _ from 'radashi' describe('trim', () => { - test('handles bad input', () => { - expect(_.trim(null)).toBe('') - expect(_.trim(undefined)).toBe('') - }) test('returns input string correctly trimmed', () => { expect(_.trim('\n\n\t\nhello\n\t \n', '\n\t ')).toBe('hello') expect(_.trim('hello', 'x')).toBe('hello') @@ -14,7 +10,6 @@ describe('trim', () => { expect(_.trim('//repos////', '/')).toBe('repos') expect(_.trim('/repos/:owner/:repo/', '/')).toBe('repos/:owner/:repo') }) - test('handles when char to trim is special case in regex', () => { expect(_.trim('_- hello_- ', '_- ')).toBe('hello') }) From a523f40c35cd1de102e2d617802d8abfa3bbf54a Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:20:27 -0400 Subject: [PATCH 03/12] refactor!: update more functions --- src/array/first.ts | 2 +- src/array/fork.ts | 6 ++---- src/array/last.ts | 2 +- src/array/replace.ts | 11 +++++++---- src/array/replaceOrAppend.ts | 9 ++++++--- 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/array/first.ts b/src/array/first.ts index f70fc71f..13200487 100644 --- a/src/array/first.ts +++ b/src/array/first.ts @@ -21,5 +21,5 @@ export function first< ): TArray extends readonly [infer TFirst, ...any[]] ? TFirst : TArray[number] | TDefault { - return array?.length > 0 ? array[0] : defaultValue + return array.length > 0 ? array[0] : defaultValue } diff --git a/src/array/fork.ts b/src/array/fork.ts index e7c1a37a..caa5e4ca 100644 --- a/src/array/fork.ts +++ b/src/array/fork.ts @@ -15,10 +15,8 @@ export function fork( condition: (item: T) => boolean, ): [T[], T[]] { const forked: [T[], T[]] = [[], []] - if (array) { - for (const item of array) { - forked[condition(item) ? 0 : 1].push(item) - } + for (const item of array) { + forked[condition(item) ? 0 : 1].push(item) } return forked } diff --git a/src/array/last.ts b/src/array/last.ts index 7fca7b2a..4ae16171 100644 --- a/src/array/last.ts +++ b/src/array/last.ts @@ -21,5 +21,5 @@ export function last< ): TArray extends readonly [...any[], infer TLast] ? TLast : TArray[number] | TDefault { - return array?.length > 0 ? array[array.length - 1] : defaultValue + return array.length > 0 ? array[array.length - 1] : defaultValue } diff --git a/src/array/replace.ts b/src/array/replace.ts index f8e1c4d9..3fd1fdf7 100644 --- a/src/array/replace.ts +++ b/src/array/replace.ts @@ -1,3 +1,6 @@ +// biome-ignore lint/complexity/noBannedTypes: {} represents “all types but null/undefined” +type Defined = T & ({} | null) + /** * Replace an element in an array with a new item without modifying * the array and return the new value. @@ -10,15 +13,15 @@ * ``` * @version 12.1.0 */ -export function replace( +export function replace( array: readonly T[], - newItem: T, + newItem: U, match: (item: T, idx: number) => boolean, -): T[] { +): (T | Defined)[] { if (newItem === undefined) { return [...array] } - const out = array.slice() + const out = array.slice() as (T | Defined)[] for (let index = 0; index < array.length; index++) { if (match(array[index], index)) { out[index] = newItem diff --git a/src/array/replaceOrAppend.ts b/src/array/replaceOrAppend.ts index f8d9b5f5..49eda4ad 100644 --- a/src/array/replaceOrAppend.ts +++ b/src/array/replaceOrAppend.ts @@ -1,3 +1,6 @@ +// biome-ignore lint/complexity/noBannedTypes: {} represents “all types but null/undefined” +type Defined = T & ({} | null) + /** * Replace the first occurrence of an item in an array where the * `match` function returns true. If no items match, append the new @@ -14,11 +17,11 @@ * ``` * @version 12.1.0 */ -export function replaceOrAppend( +export function replaceOrAppend( array: readonly T[], - newItem: T, + newItem: U, match: (a: T, idx: number) => boolean, -): T[] { +): (T | Defined)[] { if (newItem === undefined) { return [...array] } From 44d808312a99aed93b20158bb0c445c799b25c6b Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:59:16 -0400 Subject: [PATCH 04/12] refactor!: make `clone` more strict --- src/object/clone.ts | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/object/clone.ts b/src/object/clone.ts index a4214ab9..8f33a518 100644 --- a/src/object/clone.ts +++ b/src/object/clone.ts @@ -1,5 +1,3 @@ -import { isPrimitive } from 'radashi' - /** * Creates a shallow copy of the given object/value. * @@ -16,17 +14,7 @@ import { isPrimitive } from 'radashi' * ``` * @version 12.1.0 */ -export function clone(obj: T): T { - // Primitive values do not need cloning. - if (isPrimitive(obj)) { - return obj - } - - // Binding a function to an empty object creates a copy function. - if (typeof obj === 'function') { - return obj.bind({}) - } - +export function clone(obj: T): T { const proto = Object.getPrototypeOf(obj) const newObj = typeof proto?.constructor === 'function' From 71b00f19284fd5744f263b227837680c2a8ff602 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Sat, 25 Jan 2025 22:33:46 -0500 Subject: [PATCH 05/12] minor fixes --- src/array/replaceOrAppend.ts | 2 +- tests/object/clone.test.ts | 22 ---------------------- tests/object/crush.test.ts | 5 ----- 3 files changed, 1 insertion(+), 28 deletions(-) diff --git a/src/array/replaceOrAppend.ts b/src/array/replaceOrAppend.ts index 49eda4ad..e94ea331 100644 --- a/src/array/replaceOrAppend.ts +++ b/src/array/replaceOrAppend.ts @@ -25,7 +25,7 @@ export function replaceOrAppend( if (newItem === undefined) { return [...array] } - const out = array.slice() + const out = array.slice() as (T | Defined)[] for (let index = 0; index < array.length; index++) { if (match(array[index], index)) { out[index] = newItem diff --git a/tests/object/clone.test.ts b/tests/object/clone.test.ts index ae02d1c7..ab5e7336 100644 --- a/tests/object/clone.test.ts +++ b/tests/object/clone.test.ts @@ -1,21 +1,6 @@ import * as _ from 'radashi' describe('clone', () => { - test('copies the primitives', () => { - const arr = [ - 1.1, - 'How you doing?', - false, - Symbol('key'), - BigInt('1'), - undefined, - null, - ] - for (const elm of arr) { - const newElm = _.clone(elm) - expect(elm).toBe(newElm) - } - }) test('copies arrays', () => { const arr = [{ a: 0 }, 1, 2, 3] const result = _.clone(arr) @@ -25,13 +10,6 @@ describe('clone', () => { expect(arr[i]).toBe(result[i]) } }) - test('copies functions', () => { - const fa = () => 0 - const fb = _.clone(fa) - - expect(fa).not.toBe(fb) - expect(fa()).toBe(fb()) - }) test('copies objects (class instances) without losing the class type', () => { class Data { val = 0 diff --git a/tests/object/crush.test.ts b/tests/object/crush.test.ts index 3645a8d6..19521575 100644 --- a/tests/object/crush.test.ts +++ b/tests/object/crush.test.ts @@ -1,11 +1,6 @@ import * as _ from 'radashi' describe('crush', () => { - test('handles bad input', () => { - expect(_.crush({})).toEqual({}) - expect(_.crush(null as any)).toEqual({}) - expect(_.crush(undefined as any)).toEqual({}) - }) test('returns correctly crushed object', () => { const now = new Date() const ra = { From 54d955b019cce66466c903f0b5af32226a59ea7a Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Sat, 25 Jan 2025 22:53:52 -0500 Subject: [PATCH 06/12] better docs for `clone` [skip ci] --- docs/object/clone.mdx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/object/clone.mdx b/docs/object/clone.mdx index 3719f47d..2391b176 100644 --- a/docs/object/clone.mdx +++ b/docs/object/clone.mdx @@ -6,7 +6,9 @@ since: 12.1.0 ### Usage -Creates a shallow copy of the given object/value. +Creates a shallow copy of the given object or array. + +Notably, built-in objects like `Map`, `Set`, `Date`, and others are not supported. ```ts import * as _ from 'radashi' @@ -21,3 +23,9 @@ const gods = [ra] _.clone(ra) // => copy of ra _.clone(gods) // => copy of gods ``` + +### Differences from spread syntax + +The `clone` function is similar to spread syntax (e.g. `{...obj}`), but it also preserves the prototype of the original object. If a `constructor` property exists on the original prototype, it will be called with the `new` keyword to create a new instance, and all properties will then be copied over to it. + +Unlike spread syntax, `clone` also supports cloning arrays. From b909aca09e8ce2f4fb3ee570163fe0bc93218612 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Sat, 25 Jan 2025 22:54:05 -0500 Subject: [PATCH 07/12] better test for `clone` --- tests/object/clone.test.ts | 46 +++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/tests/object/clone.test.ts b/tests/object/clone.test.ts index ab5e7336..789c4277 100644 --- a/tests/object/clone.test.ts +++ b/tests/object/clone.test.ts @@ -6,24 +6,9 @@ describe('clone', () => { const result = _.clone(arr) expect(arr).not.toBe(result) - for (const i in result) { - expect(arr[i]).toBe(result[i]) - } - }) - test('copies objects (class instances) without losing the class type', () => { - class Data { - val = 0 - } - - const obj = new Data() - obj.val = 1 - const result = _.clone(obj) - - expect(obj).not.toBe(result) - expect(obj.constructor.name).toBe(result.constructor.name) - expect(obj.val).toBe(result.val) + expect(arr).toEqual(result) }) - test('copies all attributes from object', () => { + test('copies plain object in its entirety', () => { const obj = { x: 22, add: (a: number, b: number) => a + b, @@ -36,21 +21,30 @@ describe('clone', () => { expect(result.add(2, 2)).toBe(obj.add(2, 2)) expect(result.child.key).toBe(obj.child.key) }) - test('copies all attributes from class instance', () => { + test('copies properties and prototype from class instance', () => { class Data { - public x = 22 + public x = 0 + public get y() { + return this.x + 1 + } public add(a: number, b: number) { return a + b } - public child: any = { - key: 'yolo', - } } - const result = _.clone(new Data()) + + const data = new Data() + data.x = 22 + + const result = _.clone(data) + expect(result).not.toBe(data) + expect(result).toBeInstanceOf(Data) + expect(result.x).toBe(22) - // @warning will not copy functions from class instance - // expect(result.add(2, 2)).toBe(4) - expect(result.child.key).toBe('yolo') + expect(result.y).toBe(23) + + // biome-ignore lint/suspicious/noPrototypeBuiltins: + expect(result.hasOwnProperty('add')).toBe(false) + expect(result.add).toBe(Data.prototype.add) }) test('copies object created with Object.create(null)', () => { const obj = Object.create(null) From 11b5698770ba8faa2b8b987646d4d95653d6cc7f Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Sat, 25 Jan 2025 22:56:03 -0500 Subject: [PATCH 08/12] add `set` test for 100% coverage --- tests/object/set.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/object/set.test.ts b/tests/object/set.test.ts index 2d954008..af6ed9b5 100644 --- a/tests/object/set.test.ts +++ b/tests/object/set.test.ts @@ -59,4 +59,8 @@ describe('set', () => { cards: { '1234value': 2 }, }) }) + + test('set ignores undefined values', () => { + expect(_.set({}, 'foo', undefined)).toEqual({}) + }) }) From 1c6d0579a578f64316f0b4fe7e824a47990205c4 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Sat, 25 Jan 2025 23:24:54 -0500 Subject: [PATCH 09/12] remove more nullish checks --- src/array/boil.ts | 5 +---- src/array/intersects.ts | 3 --- src/array/unzip.ts | 3 --- src/array/zipToObject.ts | 4 ---- 4 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/array/boil.ts b/src/array/boil.ts index 92923ebb..95047497 100644 --- a/src/array/boil.ts +++ b/src/array/boil.ts @@ -14,8 +14,5 @@ export function boil( array: readonly T[], compareFunc: (a: T, b: T) => T, ): T | null { - if (!array || (array.length ?? 0) === 0) { - return null - } - return array.reduce(compareFunc) + return array.length ? array.reduce(compareFunc) : null } diff --git a/src/array/intersects.ts b/src/array/intersects.ts index ef557acd..b8e9940a 100644 --- a/src/array/intersects.ts +++ b/src/array/intersects.ts @@ -17,9 +17,6 @@ export function intersects( listB: readonly T[], identity?: (t: T) => K, ): boolean { - if (!listA || !listB) { - return false - } if (identity) { const known = new Set(listA.map(identity)) return listB.some(item => known.has(identity(item))) diff --git a/src/array/unzip.ts b/src/array/unzip.ts index 33cac0e1..5e50b3df 100644 --- a/src/array/unzip.ts +++ b/src/array/unzip.ts @@ -13,9 +13,6 @@ * @version 12.2.0 */ export function unzip(arrays: readonly (readonly T[])[]): T[][] { - if (!arrays || !arrays.length) { - return [] - } const out = new Array( arrays.reduce((max, arr) => Math.max(max, arr.length), 0), ) diff --git a/src/array/zipToObject.ts b/src/array/zipToObject.ts index bf82c473..29543f0b 100644 --- a/src/array/zipToObject.ts +++ b/src/array/zipToObject.ts @@ -22,10 +22,6 @@ export function zipToObject( keys: readonly K[], values: V | ((key: K, idx: number) => V) | readonly V[], ): Record { - if (!keys || !keys.length) { - return {} as Record - } - const getValue = isFunction(values) ? values : isArray(values) From 88ef63efada2cdfe1fe0069937c1584113c3b13d Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Sat, 25 Jan 2025 23:36:39 -0500 Subject: [PATCH 10/12] remove more nullish checks --- src/async/reduce.ts | 3 --- src/number/max.ts | 2 +- src/number/min.ts | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/async/reduce.ts b/src/async/reduce.ts index b3d2bd25..8cd0edb8 100644 --- a/src/async/reduce.ts +++ b/src/async/reduce.ts @@ -25,9 +25,6 @@ export async function reduce( reducer: (acc: K, item: T, index: number) => Promise, initialValue?: K, ): Promise { - if (!array) { - array = [] - } const indices = array.keys() let acc = initialValue // biome-ignore lint/style/noArguments: diff --git a/src/number/max.ts b/src/number/max.ts index 6d234cc8..771eab3b 100644 --- a/src/number/max.ts +++ b/src/number/max.ts @@ -19,7 +19,7 @@ export function max( array: readonly T[], getter?: (item: T) => number, ): T | null { - if (!array || (array.length ?? 0) === 0) { + if (!array.length) { return null } const get = getter ?? ((v: any) => v) diff --git a/src/number/min.ts b/src/number/min.ts index 8b94973a..770fca26 100644 --- a/src/number/min.ts +++ b/src/number/min.ts @@ -19,7 +19,7 @@ export function min( array: readonly T[], getter?: (item: T) => number, ): T | null { - if (!array || (array.length ?? 0) === 0) { + if (!array.length) { return null } const get = getter ?? ((v: any) => v) From 0e05f8e2f4d80177f17f954d8bfe25199af69ca2 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Sun, 26 Jan 2025 00:29:17 -0500 Subject: [PATCH 11/12] update tests --- tests/array/boil.test.ts | 10 ---------- tests/array/intersects.test.ts | 6 ------ tests/array/zipToObject.test.ts | 6 ------ tests/async/reduce.test.ts | 7 ------- tests/number/max.test.ts | 10 ---------- tests/number/min.test.ts | 10 ---------- 6 files changed, 49 deletions(-) diff --git a/tests/array/boil.test.ts b/tests/array/boil.test.ts index 19d4b7e9..dac5a200 100644 --- a/tests/array/boil.test.ts +++ b/tests/array/boil.test.ts @@ -1,7 +1,5 @@ import * as _ from 'radashi' -const cast = (value: any): T => value - describe('boil', () => { test('compares and keeps item based on condition', () => { const list = [ @@ -19,12 +17,4 @@ describe('boil', () => { const result = _.boil([], () => true) expect(result).toBeNull() }) - test('does not fail when provided array is null', () => { - const result = _.boil(cast(null), () => true) - expect(result).toBeNull() - }) - test('does not fail when provided array is funky shaped', () => { - const result = _.boil(cast({}), () => true) - expect(result).toBeNull() - }) }) diff --git a/tests/array/intersects.test.ts b/tests/array/intersects.test.ts index a1c0c732..11e6c2bb 100644 --- a/tests/array/intersects.test.ts +++ b/tests/array/intersects.test.ts @@ -1,7 +1,5 @@ import * as _ from 'radashi' -const cast = (value: any): T => value - describe('intersects', () => { test('returns true if list a & b have items in common', () => { const listA = ['a', 'b'] @@ -21,10 +19,6 @@ describe('intersects', () => { const result = _.intersects(listA, listB, x => x.value) expect(result).toBeTruthy() }) - test('returns false without failing if either list is null', () => { - expect(_.intersects(cast(null), [])).toBeFalsy() - expect(_.intersects([], cast(null))).toBeFalsy() - }) test('works with objects without an identity function', () => { const obj1 = { id: 1 } const obj2 = { id: 2 } diff --git a/tests/array/zipToObject.test.ts b/tests/array/zipToObject.test.ts index d7722d96..0ee1a759 100644 --- a/tests/array/zipToObject.test.ts +++ b/tests/array/zipToObject.test.ts @@ -15,10 +15,4 @@ describe('zipToObject', () => { const result = _.zipToObject(['a', 'b'], 1) expect(result).toEqual({ a: 1, b: 1 }) }) - - test('returns an empty object if bad parameters are passed', () => { - // @ts-ignore - const result = _.zipToObject() - expect(result).toEqual({}) - }) }) diff --git a/tests/async/reduce.test.ts b/tests/async/reduce.test.ts index eefa16f6..a72ff040 100644 --- a/tests/async/reduce.test.ts +++ b/tests/async/reduce.test.ts @@ -1,7 +1,5 @@ import * as _ from 'radashi' -const cast = (value: any): T => value - describe('asyncReduce', () => { const numbers = [0, 1, 2, 3, 4] const reducer = async (a: number, b: number): Promise => { @@ -38,9 +36,4 @@ describe('asyncReduce', () => { 'Reduce of empty array with no initial value', ) }) - test('throws on no init value and a null array input', async () => { - await expect(async () => - _.reduce(cast(null), reducer), - ).rejects.toThrowError('Reduce of empty array with no initial value') - }) }) diff --git a/tests/number/max.test.ts b/tests/number/max.test.ts index e8136d49..3af60620 100644 --- a/tests/number/max.test.ts +++ b/tests/number/max.test.ts @@ -1,7 +1,5 @@ import * as _ from 'radashi' -const cast = (value: any): T => value - describe('max', () => { test('returns the max value from list of number', () => { const list = [5, 5, 10, 2] @@ -20,12 +18,4 @@ describe('max', () => { expect(result!.game).toBe('e') expect(result!.score).toBe(500) }) - test('does not fail when provided array is null', () => { - const result = _.max(cast(null)) - expect(result).toBeNull() - }) - test('does not fail when provided array is funky shaped', () => { - const result = _.max(cast({})) - expect(result).toBeNull() - }) }) diff --git a/tests/number/min.test.ts b/tests/number/min.test.ts index fbf05e48..c3fd5ea8 100644 --- a/tests/number/min.test.ts +++ b/tests/number/min.test.ts @@ -1,7 +1,5 @@ import * as _ from 'radashi' -const cast = (value: any): T => value - describe('min', () => { test('returns the min value from list of number', () => { const list = [5, 5, 10, 2] @@ -20,12 +18,4 @@ describe('min', () => { expect(result!.game).toBe('a') expect(result!.score).toBe(100) }) - test('does not fail when provided array is null', () => { - const result = _.min(cast(null)) - expect(result).toBeNull() - }) - test('does not fail when provided array is funky shaped', () => { - const result = _.min(cast({})) - expect(result).toBeNull() - }) }) From 61a1a2bd7314388d1b940f5f84a39ca44f55b7ae Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Sun, 26 Jan 2025 00:31:30 -0500 Subject: [PATCH 12/12] fix test coverage --- tests/number/max.test.ts | 4 ++++ tests/number/min.test.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/tests/number/max.test.ts b/tests/number/max.test.ts index 3af60620..437bd922 100644 --- a/tests/number/max.test.ts +++ b/tests/number/max.test.ts @@ -18,4 +18,8 @@ describe('max', () => { expect(result!.game).toBe('e') expect(result!.score).toBe(500) }) + test('returns null if array is empty', () => { + const result = _.max([]) + expect(result).toBeNull() + }) }) diff --git a/tests/number/min.test.ts b/tests/number/min.test.ts index c3fd5ea8..a1dd14a6 100644 --- a/tests/number/min.test.ts +++ b/tests/number/min.test.ts @@ -18,4 +18,8 @@ describe('min', () => { expect(result!.game).toBe('a') expect(result!.score).toBe(100) }) + test('returns null if array is empty', () => { + const result = _.min([]) + expect(result).toBeNull() + }) })