@radio-garden/ditojs-utils 2.85.0-0.5067ad799
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/README.md +6 -0
- package/package.json +43 -0
- package/src/__snapshots__/index.test.js.snap +88 -0
- package/src/array/flatten.js +14 -0
- package/src/array/flattten.test.js +29 -0
- package/src/array/index.js +2 -0
- package/src/array/shuffle.js +11 -0
- package/src/array/shuffle.test.js +25 -0
- package/src/base/base.js +118 -0
- package/src/base/base.test.js +590 -0
- package/src/base/index.js +1 -0
- package/src/class/index.js +1 -0
- package/src/class/mixin.js +29 -0
- package/src/class/mixin.test.js +70 -0
- package/src/dataPath/getEntriesAtDataPath.js +67 -0
- package/src/dataPath/getEntriesAtDataPath.test.js +204 -0
- package/src/dataPath/getValueAtDataPath.js +45 -0
- package/src/dataPath/getValueAtDataPath.test.js +140 -0
- package/src/dataPath/index.js +6 -0
- package/src/dataPath/normalizeDataPath.js +27 -0
- package/src/dataPath/normalizeDataPath.test.js +36 -0
- package/src/dataPath/parseDataPath.js +16 -0
- package/src/dataPath/parseDataPath.test.js +67 -0
- package/src/dataPath/setDataPathEntries.js +8 -0
- package/src/dataPath/setDataPathEntries.test.js +36 -0
- package/src/dataPath/setValueAtDataPath.js +13 -0
- package/src/dataPath/setValueAtDataPath.test.js +34 -0
- package/src/function/deprecate.js +9 -0
- package/src/function/index.js +4 -0
- package/src/function/toAsync.js +9 -0
- package/src/function/toAsync.test.js +31 -0
- package/src/function/toCallback.js +11 -0
- package/src/function/toCallback.test.js +29 -0
- package/src/function/toPromiseCallback.js +9 -0
- package/src/function/toPromiseCallback.test.js +24 -0
- package/src/html/escapeHtml.js +11 -0
- package/src/html/escapeHtml.test.js +19 -0
- package/src/html/index.js +2 -0
- package/src/html/stripHtml.js +23 -0
- package/src/html/stripHtml.test.js +37 -0
- package/src/index.js +10 -0
- package/src/index.test.js +7 -0
- package/src/object/asCallback.js +9 -0
- package/src/object/clone.js +75 -0
- package/src/object/clone.test.js +131 -0
- package/src/object/equals.js +36 -0
- package/src/object/equals.test.js +269 -0
- package/src/object/groupBy.js +15 -0
- package/src/object/groupBy.test.js +70 -0
- package/src/object/index.js +8 -0
- package/src/object/mapKeys.js +7 -0
- package/src/object/mapKeys.test.js +16 -0
- package/src/object/mapValues.js +9 -0
- package/src/object/mapValues.test.js +38 -0
- package/src/object/mergeDeeply.js +47 -0
- package/src/object/mergeDeeply.test.js +152 -0
- package/src/object/pick.js +11 -0
- package/src/object/pick.test.js +23 -0
- package/src/object/pickBy.js +11 -0
- package/src/object/pickBy.test.js +48 -0
- package/src/promise/index.js +2 -0
- package/src/promise/mapConcurrently.js +33 -0
- package/src/promise/mapSequentially.js +9 -0
- package/src/string/camelize.js +14 -0
- package/src/string/camelize.test.js +37 -0
- package/src/string/capitalize.js +7 -0
- package/src/string/capitalize.test.js +33 -0
- package/src/string/decamelize.js +27 -0
- package/src/string/decamelize.test.js +83 -0
- package/src/string/deindent.js +69 -0
- package/src/string/deindent.test.js +181 -0
- package/src/string/escapeRegexp.js +3 -0
- package/src/string/format.js +109 -0
- package/src/string/format.test.js +196 -0
- package/src/string/formatDate.js +13 -0
- package/src/string/formatDate.test.js +28 -0
- package/src/string/getCommonPrefix.js +35 -0
- package/src/string/getCommonPrefix.test.js +23 -0
- package/src/string/index.js +15 -0
- package/src/string/isAbsoluteUrl.js +7 -0
- package/src/string/isAbsoluteUrl.test.js +15 -0
- package/src/string/isCreditCard.js +21 -0
- package/src/string/isCreditCard.test.js +50 -0
- package/src/string/isDomain.js +9 -0
- package/src/string/isDomain.test.js +15 -0
- package/src/string/isEmail.js +6 -0
- package/src/string/isEmail.test.js +37 -0
- package/src/string/isHostname.js +8 -0
- package/src/string/isHostname.test.js +12 -0
- package/src/string/isUrl.js +23 -0
- package/src/string/isUrl.test.js +1595 -0
- package/src/string/labelize.js +17 -0
- package/src/string/labelize.test.js +39 -0
- package/src/timer/debounce.js +34 -0
- package/src/timer/debounce.test.js +101 -0
- package/src/timer/debounceAsync.js +60 -0
- package/src/timer/debounceAsync.test.js +143 -0
- package/src/timer/index.js +2 -0
- package/types/index.d.ts +939 -0
- package/types/tests/base.test-d.ts +172 -0
- package/types/tests/datapath.test-d.ts +75 -0
- package/types/tests/function.test-d.ts +137 -0
- package/types/tests/object.test-d.ts +190 -0
- package/types/tests/promise.test-d.ts +66 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { setValueAtDataPath } from './setValueAtDataPath.js'
|
|
2
|
+
|
|
3
|
+
describe('setValueAtDataPath()', () => {
|
|
4
|
+
const data = {
|
|
5
|
+
object: {
|
|
6
|
+
array: [
|
|
7
|
+
{}
|
|
8
|
+
],
|
|
9
|
+
number: 10
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const add = { prop: 'new' }
|
|
14
|
+
|
|
15
|
+
it('should add data at a path to a given object', () => {
|
|
16
|
+
expect(() => setValueAtDataPath(data, 'object.array[0].added', add))
|
|
17
|
+
.not.toThrow()
|
|
18
|
+
expect(data.object.array[0].added).toStrictEqual(add)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('should add data at a path to a given array', () => {
|
|
22
|
+
expect(() => setValueAtDataPath(data, 'object.array[1]', add)).not.toThrow()
|
|
23
|
+
expect(data.object.array[1]).toStrictEqual(add)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should throw an error with faulty paths', () => {
|
|
27
|
+
expect(() => setValueAtDataPath(data, 'object/unknown/prop', add)).toThrow()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('should throw an error with invalid target', () => {
|
|
31
|
+
expect(() => setValueAtDataPath(data, 'object/number/invalid', add))
|
|
32
|
+
.toThrow('Invalid path: object/number/invalid')
|
|
33
|
+
})
|
|
34
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { toAsync } from './toAsync.js'
|
|
2
|
+
|
|
3
|
+
describe('toAsync()', () => {
|
|
4
|
+
it('should convert callback functions to async', async () => {
|
|
5
|
+
expect.assertions(1)
|
|
6
|
+
const asyncFunc = toAsync(function (toResolve, callback) {
|
|
7
|
+
process.nextTick(() => {
|
|
8
|
+
callback(null, toResolve)
|
|
9
|
+
})
|
|
10
|
+
})
|
|
11
|
+
const expected = 10
|
|
12
|
+
const actual = await asyncFunc(expected)
|
|
13
|
+
expect(actual).toBe(expected)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should convert callback errors to exceptions', async () => {
|
|
17
|
+
expect.assertions(1)
|
|
18
|
+
const error = new Error('This error is intentional')
|
|
19
|
+
const throwError = toAsync(function (toReject, callback) {
|
|
20
|
+
process.nextTick(() => {
|
|
21
|
+
callback(toReject)
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
await throwError(error)
|
|
27
|
+
} catch (err) {
|
|
28
|
+
expect(err).toBe(error)
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { toCallback } from './toCallback.js'
|
|
2
|
+
|
|
3
|
+
describe('toCallback()', () => {
|
|
4
|
+
it('should convert async functions to callbacks', async () => {
|
|
5
|
+
expect.assertions(2)
|
|
6
|
+
const callback = toCallback(async result => {
|
|
7
|
+
await Promise.resolve()
|
|
8
|
+
return result
|
|
9
|
+
})
|
|
10
|
+
const expected = 10
|
|
11
|
+
callback(expected, (err, actual) => {
|
|
12
|
+
expect(err).toBeNull()
|
|
13
|
+
expect(actual).toBe(expected)
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('should convert async exceptions to callback errors', async () => {
|
|
18
|
+
expect.assertions(2)
|
|
19
|
+
const error = new Error('This error is intentional')
|
|
20
|
+
const callback = toCallback(async error => {
|
|
21
|
+
await Promise.resolve()
|
|
22
|
+
throw error
|
|
23
|
+
})
|
|
24
|
+
callback(error, (err, actual) => {
|
|
25
|
+
expect(err).toBe(error)
|
|
26
|
+
expect(actual).toBe(undefined)
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { toPromiseCallback } from './toPromiseCallback.js'
|
|
2
|
+
import { vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
describe('toPromiseCallback()', () => {
|
|
5
|
+
it('should call reject() when called with an error', () => {
|
|
6
|
+
const error = new Error('My Error')
|
|
7
|
+
const resolve = vi.fn()
|
|
8
|
+
const reject = vi.fn()
|
|
9
|
+
const callback = toPromiseCallback(resolve, reject)
|
|
10
|
+
callback(error)
|
|
11
|
+
expect(resolve).not.toBeCalled()
|
|
12
|
+
expect(reject).toBeCalledWith(error)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('should call resolve() when called with a result', () => {
|
|
16
|
+
const result = 42
|
|
17
|
+
const resolve = vi.fn()
|
|
18
|
+
const reject = vi.fn()
|
|
19
|
+
const callback = toPromiseCallback(resolve, reject)
|
|
20
|
+
callback(null, result)
|
|
21
|
+
expect(reject).not.toBeCalled()
|
|
22
|
+
expect(resolve).toBeCalledWith(result)
|
|
23
|
+
})
|
|
24
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { escapeHtml } from './escapeHtml.js'
|
|
2
|
+
|
|
3
|
+
describe('escapeHtml()', () => {
|
|
4
|
+
it('should escape quotes, ampersands, and smaller/greater than signs', () => {
|
|
5
|
+
expect(escapeHtml('<div id="me, myself & i"/>'))
|
|
6
|
+
.toBe('<div id="me, myself & i"/>')
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it('should return an empty string if nothing can be processed', () => {
|
|
10
|
+
expect(escapeHtml()).toBe('')
|
|
11
|
+
expect(escapeHtml(null)).toBe('')
|
|
12
|
+
expect(escapeHtml('')).toBe('')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('should handle falsy values correctly', () => {
|
|
16
|
+
expect(escapeHtml(0)).toBe('0')
|
|
17
|
+
expect(escapeHtml(false)).toBe('false')
|
|
18
|
+
})
|
|
19
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { deprecate } from '../function/deprecate.js'
|
|
2
|
+
|
|
3
|
+
export function stripHtml(html) {
|
|
4
|
+
return html != null
|
|
5
|
+
? html
|
|
6
|
+
.toString()
|
|
7
|
+
.replace(/<br\s*\/?>/gi, '\n')
|
|
8
|
+
.replace(/<\/p>/gi, '\n')
|
|
9
|
+
.replace(/<[^>]+>/g, '')
|
|
10
|
+
.trim()
|
|
11
|
+
: ''
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @deprecated Use stripHtml() instead
|
|
16
|
+
*/
|
|
17
|
+
export function stripTags(html) {
|
|
18
|
+
deprecate(
|
|
19
|
+
'The `stripTags` function is deprecated in favour of `stripHtml`. ' +
|
|
20
|
+
'Update your code to use `stripHtml` instead.'
|
|
21
|
+
)
|
|
22
|
+
return stripHtml(html)
|
|
23
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { stripHtml } from './stripHtml.js'
|
|
2
|
+
|
|
3
|
+
describe('stripHtml()', () => {
|
|
4
|
+
it('should remove html tags from strings', () => {
|
|
5
|
+
expect(stripHtml('<p><b>this</b> is a marked-up string</p>'))
|
|
6
|
+
.toBe('this is a marked-up string')
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it('should return an empty string if nothing can be processed', () => {
|
|
10
|
+
expect(stripHtml()).toBe('')
|
|
11
|
+
expect(stripHtml(null)).toBe('')
|
|
12
|
+
expect(stripHtml('')).toBe('')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('should handle falsy values correctly', () => {
|
|
16
|
+
expect(stripHtml(0)).toBe('0')
|
|
17
|
+
expect(stripHtml(false)).toBe('false')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('should convert <br> tags to line-breaks', () => {
|
|
21
|
+
expect(stripHtml('hello<br>world')).toBe('hello\nworld')
|
|
22
|
+
expect(stripHtml('hello<br/>world')).toBe('hello\nworld')
|
|
23
|
+
expect(stripHtml('hello<br />world')).toBe('hello\nworld')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should convert <p> tags to line-breaks', () => {
|
|
27
|
+
expect(stripHtml('<p>First paragraph</p><p>Second paragraph</p>'))
|
|
28
|
+
.toBe('First paragraph\nSecond paragraph')
|
|
29
|
+
expect(stripHtml('<p>Only paragraph</p>'))
|
|
30
|
+
.toBe('Only paragraph')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('should trim leading and trailing whitespace', () => {
|
|
34
|
+
expect(stripHtml(' <p>text</p> ')).toBe('text')
|
|
35
|
+
expect(stripHtml('<p> text </p>')).toBe('text')
|
|
36
|
+
})
|
|
37
|
+
})
|
package/src/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './base/index.js'
|
|
2
|
+
export * from './class/index.js'
|
|
3
|
+
export * from './array/index.js'
|
|
4
|
+
export * from './object/index.js'
|
|
5
|
+
export * from './string/index.js'
|
|
6
|
+
export * from './timer/index.js'
|
|
7
|
+
export * from './promise/index.js'
|
|
8
|
+
export * from './function/index.js'
|
|
9
|
+
export * from './dataPath/index.js'
|
|
10
|
+
export * from './html/index.js'
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isArray,
|
|
3
|
+
isObject,
|
|
4
|
+
isDate,
|
|
5
|
+
isRegExp,
|
|
6
|
+
isFunction,
|
|
7
|
+
isPromise
|
|
8
|
+
} from '../base/index.js'
|
|
9
|
+
import { pick } from './pick.js'
|
|
10
|
+
|
|
11
|
+
export function clone(value, options) {
|
|
12
|
+
const {
|
|
13
|
+
shallow = false,
|
|
14
|
+
enumerable = true,
|
|
15
|
+
descriptors = !enumerable,
|
|
16
|
+
transferables = null,
|
|
17
|
+
processValue = null
|
|
18
|
+
} = options ?? {}
|
|
19
|
+
|
|
20
|
+
const clones = new Map()
|
|
21
|
+
|
|
22
|
+
const storeClone = (value, copy) => {
|
|
23
|
+
clones.set(value, copy)
|
|
24
|
+
return copy
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const handleValue = value =>
|
|
28
|
+
shallow || transferables?.includes(value)
|
|
29
|
+
? value
|
|
30
|
+
: clones.has(value)
|
|
31
|
+
? clones.get(value)
|
|
32
|
+
: cloneValue(value)
|
|
33
|
+
|
|
34
|
+
const cloneValue = value => {
|
|
35
|
+
let copy = value
|
|
36
|
+
if (isDate(value)) {
|
|
37
|
+
copy = storeClone(value, new value.constructor(+value))
|
|
38
|
+
} else if (isRegExp(value)) {
|
|
39
|
+
copy = storeClone(value, new value.constructor(value))
|
|
40
|
+
} else if (isArray(value)) {
|
|
41
|
+
copy = storeClone(value, new value.constructor(value.length))
|
|
42
|
+
for (let i = 0, l = value.length; i < l; i++) {
|
|
43
|
+
copy[i] = handleValue(value[i])
|
|
44
|
+
}
|
|
45
|
+
} else if (isObject(value)) {
|
|
46
|
+
// Rely on arg.clone() if it exists and assume it creates an actual clone.
|
|
47
|
+
if (isFunction(value.clone)) {
|
|
48
|
+
copy = storeClone(value, value.clone(options))
|
|
49
|
+
} else if (isPromise(value)) {
|
|
50
|
+
// https://stackoverflow.com/questions/37063293/can-i-clone-a-promise
|
|
51
|
+
copy = storeClone(value, value.then())
|
|
52
|
+
} else {
|
|
53
|
+
// Prevent calling the actual constructor since it is not guaranteed to
|
|
54
|
+
// work as intended here, and only clone non-inherited own properties.
|
|
55
|
+
copy = storeClone(value, Object.create(Object.getPrototypeOf(value)))
|
|
56
|
+
clones.set(value, copy)
|
|
57
|
+
const keys = enumerable ? Object.keys(value) : Reflect.ownKeys(value)
|
|
58
|
+
for (const key of keys) {
|
|
59
|
+
if (descriptors) {
|
|
60
|
+
const desc = Reflect.getOwnPropertyDescriptor(value, key)
|
|
61
|
+
if (desc.value != null) {
|
|
62
|
+
desc.value = handleValue(desc.value)
|
|
63
|
+
}
|
|
64
|
+
Reflect.defineProperty(copy, key, desc)
|
|
65
|
+
} else {
|
|
66
|
+
copy[key] = handleValue(value[key])
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return pick(processValue?.(copy), copy)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return cloneValue(value)
|
|
75
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { clone } from './clone.js'
|
|
2
|
+
import { vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
describe('clone()', () => {
|
|
5
|
+
it('should clone objects', () => {
|
|
6
|
+
const object = { a: 1, b: 2 }
|
|
7
|
+
const copy = clone(object)
|
|
8
|
+
expect(copy).toEqual(object)
|
|
9
|
+
expect(copy).not.toBe(object)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('should clone arrays', () => {
|
|
13
|
+
const array = [1, 2, 3]
|
|
14
|
+
const copy = clone(array)
|
|
15
|
+
expect(copy).toStrictEqual(array)
|
|
16
|
+
expect(copy).not.toBe(array)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should clone dates', () => {
|
|
20
|
+
const date = new Date(2012, 5, 9)
|
|
21
|
+
const copy = clone(date)
|
|
22
|
+
expect(copy).toStrictEqual(date)
|
|
23
|
+
expect(copy).not.toBe(date)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should clone regular expressions', () => {
|
|
27
|
+
const regexp = /regexp/gi
|
|
28
|
+
const copy = clone(regexp)
|
|
29
|
+
expect(copy).toStrictEqual(regexp)
|
|
30
|
+
expect(copy).not.toBe(regexp)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('should return functions unmodified', () => {
|
|
34
|
+
const func = () => {}
|
|
35
|
+
expect(clone(func)).toBe(func)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('should clone nested objects and arrays', () => {
|
|
39
|
+
const object = { a: [1, 2, 3], b: { c: 4, d: 5 } }
|
|
40
|
+
const copy = clone(object)
|
|
41
|
+
expect(copy).toStrictEqual(object)
|
|
42
|
+
expect(copy).not.toBe(object)
|
|
43
|
+
expect(copy.a).not.toBe(object.a)
|
|
44
|
+
expect(copy.b).not.toBe(object.b)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should support shallow-cloning', () => {
|
|
48
|
+
const object = { a: [1, 2, 3] }
|
|
49
|
+
const copy = clone(object, { shallow: true })
|
|
50
|
+
expect(object.a).toBe(copy.a)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('should use clone() methods if available', () => {
|
|
54
|
+
const object = {
|
|
55
|
+
a: 1,
|
|
56
|
+
clone: vi.fn(() => ({ b: 2 }))
|
|
57
|
+
}
|
|
58
|
+
const copy = clone(object)
|
|
59
|
+
expect(object.clone).toBeCalledTimes(1)
|
|
60
|
+
expect(copy).toStrictEqual({ b: 2 })
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('should preserve identity', async () => {
|
|
64
|
+
const object = { a: { b: 1 } }
|
|
65
|
+
object.c = object.a
|
|
66
|
+
const copy = clone(object)
|
|
67
|
+
expect(copy).toEqual(object)
|
|
68
|
+
expect(copy.c).toBe(copy.a)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('should handle circular references', async () => {
|
|
72
|
+
const object = { a: { b: 1 } }
|
|
73
|
+
object.c = object
|
|
74
|
+
const copy = clone(object)
|
|
75
|
+
expect(copy).toEqual(object)
|
|
76
|
+
expect(copy.c).toBe(copy)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('should handle non-enumerable properties', async () => {
|
|
80
|
+
const object = { a: 1 }
|
|
81
|
+
Object.defineProperty(object, 'b', { value: 2, enumerable: false })
|
|
82
|
+
const copy = clone(object, { enumerable: false })
|
|
83
|
+
expect(copy.a).toEqual(1)
|
|
84
|
+
expect(copy.b).toEqual(2)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('should transform cloned values by `processValue`', () => {
|
|
88
|
+
const object = {
|
|
89
|
+
a: { b: 1, c: 2 },
|
|
90
|
+
d: { e: 3, f: 4 }
|
|
91
|
+
}
|
|
92
|
+
const copy = clone(object, {
|
|
93
|
+
processValue: value => {
|
|
94
|
+
if (typeof value === 'object') {
|
|
95
|
+
value.g = 5
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
const expected = {
|
|
100
|
+
a: { b: 1, c: 2, g: 5 },
|
|
101
|
+
d: { e: 3, f: 4, g: 5 },
|
|
102
|
+
g: 5
|
|
103
|
+
}
|
|
104
|
+
expect(copy).toStrictEqual(expected)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('should call `processValue` after cloning all children', () => {
|
|
108
|
+
const array = [
|
|
109
|
+
{ a: 1, b: 2 },
|
|
110
|
+
{ a: 3, b: 4 }
|
|
111
|
+
]
|
|
112
|
+
const copy = clone(array, {
|
|
113
|
+
processValue: value => {
|
|
114
|
+
if (typeof value === 'object') {
|
|
115
|
+
delete value.b
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
const expected = [
|
|
120
|
+
{ a: 1 },
|
|
121
|
+
{ a: 3 }
|
|
122
|
+
]
|
|
123
|
+
expect(copy).toStrictEqual(expected)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('should handle promises', async () => {
|
|
127
|
+
const promise = (async () => 1)()
|
|
128
|
+
const result = clone(promise)
|
|
129
|
+
expect(await result).toStrictEqual(1)
|
|
130
|
+
})
|
|
131
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { is, isArray, isObject } from '../base/index.js'
|
|
2
|
+
|
|
3
|
+
export function equals(arg1, arg2) {
|
|
4
|
+
if (arg1 === arg2) {
|
|
5
|
+
return true
|
|
6
|
+
}
|
|
7
|
+
if (arg1 != null && arg2 != null) {
|
|
8
|
+
arg1 = arg1.valueOf()
|
|
9
|
+
arg2 = arg2.valueOf()
|
|
10
|
+
if (is(arg1, arg2)) {
|
|
11
|
+
return true
|
|
12
|
+
}
|
|
13
|
+
if (isArray(arg1) && isArray(arg2)) {
|
|
14
|
+
let { length } = arg1
|
|
15
|
+
if (length === arg2.length) {
|
|
16
|
+
while (length--) {
|
|
17
|
+
if (!equals(arg1[length], arg2[length])) {
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return true
|
|
22
|
+
}
|
|
23
|
+
} else if (isObject(arg1) && isObject(arg2)) {
|
|
24
|
+
const keys = Object.keys(arg1)
|
|
25
|
+
if (keys.length === Object.keys(arg2).length) {
|
|
26
|
+
for (const key of keys) {
|
|
27
|
+
if (!(arg2.hasOwnProperty(key) && equals(arg1[key], arg2[key]))) {
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return true
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return false
|
|
36
|
+
}
|