@splendidlabz/utils 1.11.2 → 1.11.4

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 (228) hide show
  1. package/dist/cjs/dom/accessibility.cjs +2 -0
  2. package/dist/cjs/dom/actions/index.cjs +392 -7
  3. package/dist/cjs/dom/actions/masonry.cjs +97 -6
  4. package/dist/cjs/dom/actions/prefer-horizontal-scroll.cjs +58 -14
  5. package/dist/cjs/dom/actions/sticky.cjs +126 -6
  6. package/dist/cjs/dom/bounding-box.cjs +2 -0
  7. package/dist/cjs/dom/clipboard.cjs +2 -0
  8. package/dist/cjs/dom/cookie.cjs +3 -1
  9. package/dist/cjs/dom/css-vars.cjs +13 -3
  10. package/dist/cjs/dom/events.cjs +2 -0
  11. package/dist/cjs/dom/focusable.cjs +2 -0
  12. package/dist/cjs/dom/font-size.cjs +15 -3
  13. package/dist/cjs/dom/get-element.cjs +3 -1
  14. package/dist/cjs/dom/hash.cjs +2 -0
  15. package/dist/cjs/dom/index.cjs +1249 -47
  16. package/dist/cjs/dom/keyboard.cjs +2 -0
  17. package/dist/cjs/dom/local-store.cjs +3 -1
  18. package/dist/cjs/dom/media.cjs +2 -0
  19. package/dist/cjs/dom/observers/index.cjs +235 -9
  20. package/dist/cjs/dom/observers/intersection-observer.cjs +38 -6
  21. package/dist/cjs/dom/observers/mutation-observer.cjs +38 -6
  22. package/dist/cjs/dom/observers/observer.cjs +12 -2
  23. package/dist/cjs/dom/observers/resize-observer.cjs +38 -6
  24. package/dist/cjs/dom/observers/scroll-observer.cjs +62 -3
  25. package/dist/cjs/dom/pkce.cjs +13 -3
  26. package/dist/cjs/dom/query-params.cjs +3 -1
  27. package/dist/cjs/dom/random-string.cjs +2 -0
  28. package/dist/cjs/dom/sanitize.cjs +27 -3
  29. package/dist/cjs/dom/session-store.cjs +3 -1
  30. package/dist/cjs/dom/spam-prevention.cjs +2 -0
  31. package/dist/cjs/dom/trap-focus.cjs +131 -9
  32. package/dist/cjs/dom/ui/aria-current.cjs +2 -0
  33. package/dist/cjs/dom/ui/inconsistent-button-fix.cjs +2 -0
  34. package/dist/cjs/dom/ui/index.cjs +87 -9
  35. package/dist/cjs/dom/ui/scroll-container.cjs +2 -0
  36. package/dist/cjs/dom/ui/traverse-and-scramble.cjs +2 -0
  37. package/dist/cjs/lib/arrays/index.cjs +121 -13
  38. package/dist/cjs/lib/arrays/item-position.cjs +2 -0
  39. package/dist/cjs/lib/arrays/join.cjs +10 -2
  40. package/dist/cjs/lib/arrays/shuffle.cjs +2 -0
  41. package/dist/cjs/lib/arrays/sort.cjs +11 -3
  42. package/dist/cjs/lib/arrays/split-array.cjs +2 -0
  43. package/dist/cjs/lib/arrays/unique.cjs +2 -0
  44. package/dist/cjs/lib/auth/index.cjs +35 -3
  45. package/dist/cjs/lib/auth/route-manager.cjs +2 -0
  46. package/dist/cjs/lib/checks.cjs +2 -0
  47. package/dist/cjs/lib/date/days.cjs +3 -1
  48. package/dist/cjs/lib/date/index.cjs +190 -7
  49. package/dist/cjs/lib/date/months.cjs +3 -1
  50. package/dist/cjs/lib/date/time.cjs +14 -2
  51. package/dist/cjs/lib/form/form-data.cjs +2 -0
  52. package/dist/cjs/lib/form/index.cjs +104 -5
  53. package/dist/cjs/lib/form/sanitize.cjs +2 -0
  54. package/dist/cjs/lib/functions/callback.cjs +2 -0
  55. package/dist/cjs/lib/functions/debounce.cjs +2 -0
  56. package/dist/cjs/lib/functions/env.cjs +2 -0
  57. package/dist/cjs/lib/functions/functional.cjs +2 -0
  58. package/dist/cjs/lib/functions/index.cjs +124 -11
  59. package/dist/cjs/lib/functions/throttle.cjs +2 -0
  60. package/dist/cjs/lib/functions/timeout.cjs +2 -0
  61. package/dist/cjs/lib/hash.cjs +2 -0
  62. package/dist/cjs/lib/index.cjs +1160 -29
  63. package/dist/cjs/lib/numbers/index.cjs +17 -4
  64. package/dist/cjs/lib/numbers/math.cjs +2 -0
  65. package/dist/cjs/lib/objects/camelcase-keys.cjs +45 -4
  66. package/dist/cjs/lib/objects/empty.cjs +2 -0
  67. package/dist/cjs/lib/objects/equal.cjs +2 -0
  68. package/dist/cjs/lib/objects/extend.cjs +117 -6
  69. package/dist/cjs/lib/objects/flatten.cjs +11 -3
  70. package/dist/cjs/lib/objects/index.cjs +360 -27
  71. package/dist/cjs/lib/objects/json.cjs +2 -0
  72. package/dist/cjs/lib/objects/loop.cjs +2 -0
  73. package/dist/cjs/lib/objects/mix/mix.cjs +5 -3
  74. package/dist/cjs/lib/objects/nested-property.cjs +2 -0
  75. package/dist/cjs/lib/objects/normalize-object.cjs +78 -4
  76. package/dist/cjs/lib/objects/omit-empty.cjs +2 -0
  77. package/dist/cjs/lib/objects/size.cjs +2 -0
  78. package/dist/cjs/lib/objects/split.cjs +2 -0
  79. package/dist/cjs/lib/promises/index.cjs +51 -3
  80. package/dist/cjs/lib/promises/reject.cjs +19 -2
  81. package/dist/cjs/lib/sse.cjs +29 -4
  82. package/dist/cjs/lib/strings/convert-case/convert-case.cjs +2 -0
  83. package/dist/cjs/lib/strings/index.cjs +179 -11
  84. package/dist/cjs/lib/strings/markdown.cjs +3 -1
  85. package/dist/cjs/lib/strings/name.cjs +2 -0
  86. package/dist/cjs/lib/strings/pluralize.cjs +3 -1
  87. package/dist/cjs/lib/strings/query-string.cjs +2 -0
  88. package/dist/cjs/lib/style/index.cjs +31 -2
  89. package/dist/cjs/lib/symbols/index.cjs +22 -3
  90. package/dist/cjs/lib/symbols/symbols.cjs +2 -0
  91. package/dist/cjs/node/context.cjs +0 -0
  92. package/dist/cjs/node/dirname.cjs +4 -2
  93. package/dist/cjs/node/file-cache.cjs +45 -3
  94. package/dist/cjs/node/file.cjs +4 -2
  95. package/dist/cjs/node/hash.cjs +3 -1
  96. package/dist/cjs/node/index.cjs +278 -15
  97. package/dist/cjs/node/pkce.cjs +20 -8
  98. package/dist/cjs/node/random-string.cjs +3 -1
  99. package/dist/cjs/node/sanitize.cjs +27 -3
  100. package/dist/cjs/node/uuid.cjs +2 -0
  101. package/dist/esm/node/context.js +0 -0
  102. package/dist/esm/node/file-cache.js +1 -1
  103. package/dist/esm/node/pkce.js +3 -2
  104. package/dist/types/node/context.d.cts +2 -0
  105. package/dist/types/node/index.d.cts +1 -1
  106. package/dist/types/node/pkce.d.cts +7 -1
  107. package/package.json +5 -2
  108. package/CHANGELOG.md +0 -231
  109. package/eslint.config.js +0 -2
  110. package/src/dom/accessibility.js +0 -58
  111. package/src/dom/actions/index.js +0 -3
  112. package/src/dom/actions/masonry.js +0 -43
  113. package/src/dom/actions/prefer-horizontal-scroll.js +0 -121
  114. package/src/dom/actions/sticky.js +0 -85
  115. package/src/dom/bounding-box.js +0 -82
  116. package/src/dom/clipboard.js +0 -14
  117. package/src/dom/cookie.js +0 -32
  118. package/src/dom/css-vars.js +0 -19
  119. package/src/dom/events.js +0 -71
  120. package/src/dom/focusable.js +0 -165
  121. package/src/dom/font-size.js +0 -40
  122. package/src/dom/get-element.js +0 -176
  123. package/src/dom/hash.js +0 -9
  124. package/src/dom/index.js +0 -23
  125. package/src/dom/keyboard.js +0 -44
  126. package/src/dom/local-store.js +0 -66
  127. package/src/dom/media.js +0 -30
  128. package/src/dom/observers/index.js +0 -4
  129. package/src/dom/observers/intersection-observer.js +0 -43
  130. package/src/dom/observers/mutation-observer.js +0 -51
  131. package/src/dom/observers/observer.js +0 -18
  132. package/src/dom/observers/resize-observer.js +0 -66
  133. package/src/dom/observers/scroll-observer.js +0 -146
  134. package/src/dom/pkce.js +0 -34
  135. package/src/dom/query-params.js +0 -14
  136. package/src/dom/random-string.js +0 -18
  137. package/src/dom/random-string.test.js +0 -13
  138. package/src/dom/sanitize.js +0 -16
  139. package/src/dom/session-store.js +0 -29
  140. package/src/dom/spam-prevention.js +0 -40
  141. package/src/dom/trap-focus.js +0 -70
  142. package/src/dom/ui/aria-current.js +0 -20
  143. package/src/dom/ui/inconsistent-button-fix.js +0 -20
  144. package/src/dom/ui/index.js +0 -4
  145. package/src/dom/ui/scroll-container.js +0 -25
  146. package/src/dom/ui/traverse-and-scramble.js +0 -61
  147. package/src/lib/arrays/index.js +0 -6
  148. package/src/lib/arrays/item-position.js +0 -23
  149. package/src/lib/arrays/join.js +0 -20
  150. package/src/lib/arrays/shuffle.js +0 -21
  151. package/src/lib/arrays/sort.js +0 -88
  152. package/src/lib/arrays/sort.spec.js +0 -123
  153. package/src/lib/arrays/split-array.js +0 -15
  154. package/src/lib/arrays/unique.js +0 -3
  155. package/src/lib/auth/index.js +0 -1
  156. package/src/lib/auth/route-manager.js +0 -45
  157. package/src/lib/checks.js +0 -57
  158. package/src/lib/date/days.js +0 -9
  159. package/src/lib/date/index.js +0 -3
  160. package/src/lib/date/months.js +0 -74
  161. package/src/lib/date/time.js +0 -83
  162. package/src/lib/form/form-data.js +0 -73
  163. package/src/lib/form/index.js +0 -2
  164. package/src/lib/form/sanitize.js +0 -80
  165. package/src/lib/form/sanitize.spec.js +0 -126
  166. package/src/lib/functions/callback.js +0 -8
  167. package/src/lib/functions/debounce.js +0 -18
  168. package/src/lib/functions/env.js +0 -5
  169. package/src/lib/functions/functional.js +0 -113
  170. package/src/lib/functions/functional.test.js +0 -196
  171. package/src/lib/functions/index.js +0 -5
  172. package/src/lib/functions/throttle.js +0 -11
  173. package/src/lib/functions/timeout.js +0 -15
  174. package/src/lib/hash.js +0 -18
  175. package/src/lib/hash.spec.js +0 -163
  176. package/src/lib/index.js +0 -14
  177. package/src/lib/numbers/index.js +0 -12
  178. package/src/lib/numbers/math.js +0 -9
  179. package/src/lib/numbers/split-unit.test.js +0 -45
  180. package/src/lib/objects/camelcase-keys.js +0 -17
  181. package/src/lib/objects/camelcase-keys.test.js +0 -72
  182. package/src/lib/objects/empty.js +0 -10
  183. package/src/lib/objects/equal.js +0 -9
  184. package/src/lib/objects/extend.js +0 -16
  185. package/src/lib/objects/extend.test.js +0 -35
  186. package/src/lib/objects/flatten.js +0 -19
  187. package/src/lib/objects/index.js +0 -13
  188. package/src/lib/objects/json.js +0 -7
  189. package/src/lib/objects/loop.js +0 -11
  190. package/src/lib/objects/loop.test.js +0 -31
  191. package/src/lib/objects/mix/mix.js +0 -191
  192. package/src/lib/objects/mix/mix.md +0 -78
  193. package/src/lib/objects/mix/mix.test.js +0 -445
  194. package/src/lib/objects/nested-property.js +0 -41
  195. package/src/lib/objects/normalize-object.js +0 -10
  196. package/src/lib/objects/omit-empty.js +0 -23
  197. package/src/lib/objects/omit-empty.test.js +0 -85
  198. package/src/lib/objects/size.js +0 -41
  199. package/src/lib/objects/split.js +0 -19
  200. package/src/lib/promises/index.js +0 -1
  201. package/src/lib/promises/reject.js +0 -17
  202. package/src/lib/sse.js +0 -61
  203. package/src/lib/strings/convert-case/convert-case.js +0 -86
  204. package/src/lib/strings/convert-case/convert-case.md +0 -44
  205. package/src/lib/strings/convert-case/convert-case.test.js +0 -50
  206. package/src/lib/strings/index.js +0 -5
  207. package/src/lib/strings/markdown.js +0 -71
  208. package/src/lib/strings/name.js +0 -24
  209. package/src/lib/strings/name.test.js +0 -67
  210. package/src/lib/strings/pluralize.js +0 -2
  211. package/src/lib/strings/query-string.js +0 -18
  212. package/src/lib/style/index.js +0 -13
  213. package/src/lib/symbols/index.js +0 -1
  214. package/src/lib/symbols/symbols.js +0 -9
  215. package/src/node/dirname.js +0 -17
  216. package/src/node/file-cache.js +0 -184
  217. package/src/node/file.js +0 -13
  218. package/src/node/hash.js +0 -5
  219. package/src/node/index.js +0 -7
  220. package/src/node/pkce.js +0 -33
  221. package/src/node/random-string.js +0 -10
  222. package/src/node/random-string.test.js +0 -15
  223. package/src/node/sanitize.js +0 -13
  224. package/src/node/sanitize.spec.js +0 -142
  225. package/src/node/uuid.js +0 -5
  226. package/tsconfig.json +0 -23
  227. package/tsup.config.js +0 -38
  228. package/tsup.config.mjs +0 -45
@@ -1,191 +0,0 @@
1
- const DEFAULT_OPTIONS = { array: 'replace' } // Can be concat or replace
2
-
3
- /**
4
- * Deep merges multiple objects into a new object with intelligent handling of arrays and nested objects.
5
- *
6
- * @param {...Object} sources - Objects to merge. Null and undefined values are skipped.
7
- * @return {Object} A new object containing the merged properties from all sources.
8
- *
9
- * @example
10
- * const obj1 = { a: 1, b: { x: 10 } }
11
- * const obj2 = { b: { y: 20 }, c: 3 }
12
- * const result = mix(obj1, obj2)
13
- * // Returns: { a: 1, b: { x: 10, y: 20 }, c: 3 }
14
- */
15
- export const mix = createMix()
16
- export const concatMix = createMix({ array: 'concat' })
17
-
18
- /**
19
- * Creates a customized mix function with specific options for array handling.
20
- *
21
- * @param {Object} [userOptions={}] - Configuration options for the mix function.
22
- * @param {('replace'|'concat')} [userOptions.array='replace'] - How to handle array merging:
23
- * - 'replace': Arrays from later sources completely replace arrays from earlier sources
24
- * - 'concat': Arrays are concatenated together
25
- * @return {Function} A mix function configured with the provided options.
26
- *
27
- * @example
28
- * // Create mix function that concatenates arrays
29
- * const concatMix = createMix({ array: 'concat' })
30
- * const obj1 = { items: [1, 2] }
31
- * const obj2 = { items: [3, 4] }
32
- * const result = concatMix(obj1, obj2)
33
- * // Returns: { items: [1, 2, 3, 4] }
34
- *
35
- * @example
36
- * // Create mix function that replaces arrays (default behavior)
37
- * const replaceMix = createMix({ array: 'replace' })
38
- * const obj1 = { items: [1, 2] }
39
- * const obj2 = { items: [3, 4] }
40
- * const result = replaceMix(obj1, obj2)
41
- * // Returns: { items: [3, 4] }
42
- */
43
- export function createMix(userOptions = {}) {
44
- const options = { ...DEFAULT_OPTIONS, ...userOptions }
45
-
46
- function _mix(...sources) {
47
- const result = {}
48
- for (const source of sources) {
49
- // Skip null or undefined sources
50
- if (source == null) continue
51
- merge(result, source, options)
52
- }
53
- return result
54
- }
55
-
56
- return _mix
57
- }
58
-
59
- /**
60
- * Recursively merges properties from input object into output object.
61
- * Handles property descriptors, prevents prototype pollution, and deep clones values.
62
- *
63
- * @param {Object} output - The target object to merge properties into.
64
- * @param {Object} input - The source object to merge properties from.
65
- * @param {Object} options - Merge options including array handling strategy.
66
- * @property {('replace'|'concat')} options.array - How to handle array merging.
67
- */
68
- function merge(output, input, options) {
69
- const props = Object.keys(input)
70
-
71
- for (const prop of props) {
72
- // Prevents Prototype Pollution
73
- if (prop === '__proto__') continue
74
-
75
- const descriptor = Object.getOwnPropertyDescriptor(input, prop)
76
- const value = descriptor.value
77
- if (value) descriptor.value = cloneDescriptorValue(value)
78
-
79
- // If don't have prop => Define property
80
- if (!output[prop]) {
81
- Object.defineProperty(output, prop, descriptor)
82
- continue
83
- }
84
-
85
- // If have prop, but type is not object => Overwrite by redefining property
86
- if (typeof output[prop] !== 'object') {
87
- Object.defineProperty(output, prop, descriptor)
88
- continue
89
- }
90
-
91
- // Incoming non-object (primitive/null) => always overwrite
92
- if (descriptor.value == null || typeof descriptor.value !== 'object') {
93
- Object.defineProperty(output, prop, descriptor)
94
- continue
95
- }
96
-
97
- // If have prop, and incoming is Array => concat or replace
98
- if (objectType(descriptor.value) === '[object Array]') {
99
- output[prop] =
100
- options.array === 'replace'
101
- ? descriptor.value.slice()
102
- : output[prop].concat(descriptor.value)
103
- continue
104
- }
105
-
106
- // If have prop, but type is Object => Merge.
107
- merge(output[prop], descriptor.value, options)
108
-
109
- // Not sure if I have to deal with overwriting maps / dates. When it comes, I guess!
110
- }
111
- }
112
-
113
- /**
114
- * Creates a deep clone of a property descriptor value.
115
- * Handles arrays, objects, dates, maps, sets, and other types appropriately.
116
- *
117
- * @param {*} value - The value to clone.
118
- * @return {*} A deep clone of the input value.
119
- * @property {Array} value - Cloned recursively with all elements deep cloned.
120
- * @property {Object} value - Cloned with all properties and descriptors preserved.
121
- * @property {Date} value - Cloned as new Date with same time value.
122
- * @property {Map} value - Cloned with all entries deep cloned.
123
- * @property {Set} value - Cloned with all entries deep cloned.
124
- */
125
- function cloneDescriptorValue(value) {
126
- // Arrays
127
- if (objectType(value) === '[object Array]') {
128
- const array = []
129
- for (let v of value) {
130
- v = cloneDescriptorValue(v)
131
- array.push(v)
132
- }
133
- return array
134
- }
135
-
136
- // Objects
137
- if (objectType(value) === '[object Object]') {
138
- const obj = {}
139
- const props = Object.keys(value)
140
- for (const prop of props) {
141
- const descriptor = Object.getOwnPropertyDescriptor(value, prop)
142
- if (descriptor.value)
143
- descriptor.value = cloneDescriptorValue(descriptor.value)
144
- Object.defineProperty(obj, prop, descriptor)
145
- }
146
- return obj
147
- }
148
-
149
- // Other Types of Objects
150
- if (objectType(value) === '[object Date]') {
151
- return new Date(value.getTime())
152
- }
153
-
154
- if (objectType(value) === '[object Map]') {
155
- const map = new Map()
156
- for (const entry of value) {
157
- map.set(entry[0], cloneDescriptorValue(entry[1]))
158
- }
159
- return map
160
- }
161
-
162
- if (objectType(value) === '[object Set]') {
163
- const set = new Set()
164
- for (const entry of value.entries()) {
165
- set.add(cloneDescriptorValue(entry[0]))
166
- }
167
- return set
168
- }
169
-
170
- // Types we don't need to clone or cannot clone.
171
- // Examples:
172
- // - Primitives don't need to clone
173
- // - Functions cannot clone
174
- return value
175
- }
176
-
177
- /**
178
- * Returns the precise object type of a value using Object.prototype.toString.
179
- *
180
- * @param {*} value - The value to get the type of.
181
- * @return {string} The object type string (e.g., '[object Array]', '[object Object]', '[object Date]').
182
- *
183
- * @example
184
- * objectType([]) // '[object Array]'
185
- * objectType({}) // '[object Object]'
186
- * objectType(new Date()) // '[object Date]'
187
- * objectType(null) // '[object Null]'
188
- */
189
- function objectType(value) {
190
- return Object.prototype.toString.call(value)
191
- }
@@ -1,78 +0,0 @@
1
- # Mix
2
-
3
- - Merges objects. Like `Object.assign`, but performs a deep merge so won't be any mutation.
4
- - [Why I created `mix`](https://zellwk.com/blog/copy-properties-of-one-object-to-another-object)
5
-
6
- ## Usage
7
-
8
- Import the module and use it.
9
-
10
- ```js
11
- import mix from '@zellwk/javascript/mix'
12
- mix(target, source)
13
- ```
14
-
15
- ## Example
16
-
17
- Pass objects into mix to combine them. Mix creates a new object for you automatically. Nothing will get mutated.
18
-
19
- ```js
20
- let count = 0
21
- const one = { one: 'one' }
22
- const two = { two: 'two' }
23
- const mixed = mix(one, two)
24
-
25
- // Output
26
- // {
27
- // one: 'one',
28
- // two: 'two',
29
- // }
30
- ```
31
-
32
- ## Features
33
-
34
- 1. It copies accessors.
35
- 2. It performs a deep merge so no mutation can happen
36
-
37
- ### Copying accessors
38
-
39
- Mix copies accessors along with other properties. Most other deep merging libraries don't do this. `Object.assign` doesn't do this either.
40
-
41
- ```js
42
- let count = 0
43
- const one = { one: 'one' }
44
- const two = { two: 'two' }
45
- const three = {
46
- get count () {
47
- return count
48
- },
49
- set count (value) {
50
- count = value
51
- }
52
- }
53
- const mixed = mix({}, one, two, three)
54
-
55
- // Output
56
- // {
57
- // one: 'one',
58
- // two: 'two',
59
- // get count () { return count } ,
60
- // set count (value) { count = value }
61
- // }
62
- ```
63
-
64
- ### Deep Merging
65
-
66
- `mix` copies nested objects and arrays so you don't have to worry about mutation.
67
-
68
- ```js
69
- const one = {}
70
- const two = { nested: { value: 'two' } }
71
- const three = mix(one, two)
72
-
73
- // Nested values do not get mutated
74
- three.nested.value = 'three'
75
- console.log(two.nested.value) // 'two'
76
- ```
77
-
78
- That's it!
@@ -1,445 +0,0 @@
1
- import { expect, it } from 'vitest'
2
-
3
- import { concatMix, createMix, mix } from './mix.js'
4
-
5
- it('Merge objects', () => {
6
- const one = { one: 'one', overwrite: 'nope' }
7
- const two = { two: 'two', overwrite: 'yes' }
8
- const three = mix(one, two)
9
-
10
- expect(three).toEqual({
11
- one: 'one',
12
- two: 'two',
13
- overwrite: 'yes',
14
- })
15
- })
16
-
17
- it('Empty + Nested', () => {
18
- const one = {}
19
- const two = {
20
- nested: {
21
- value: 'nested-two',
22
- deep: {
23
- value: 'deep-two',
24
- },
25
- },
26
- }
27
- const three = mix(one, two)
28
-
29
- expect(three).toEqual(two)
30
- expect(three.nested.value).toBe('nested-two')
31
- expect(three.nested.deep.value).toBe('deep-two')
32
-
33
- // Prevents mutation of nested object
34
- three.nested.value = 'three'
35
- expect(two.nested.value).toBe('nested-two')
36
-
37
- // Prevents mutation of deeply nested object
38
- three.nested.deep.value = 'deep-three'
39
- expect(two.nested.deep.value).toBe('deep-two')
40
- })
41
-
42
- it('Double Nested Objects', () => {
43
- const one = {
44
- one: 'one',
45
- nested: {
46
- one: 'nested-one',
47
- deep: {
48
- one: 'deep-one',
49
- },
50
- },
51
- }
52
-
53
- const two = {
54
- two: 'two',
55
- nested: {
56
- two: 'nested-two',
57
- deep: {
58
- two: 'deep-two',
59
- },
60
- },
61
- }
62
-
63
- const three = mix(one, two)
64
-
65
- expect(three.one).toBe('one')
66
- expect(three.two).toBe('two')
67
- expect(three.nested.one).toBe('nested-one')
68
- expect(three.nested.two).toBe('nested-two')
69
- expect(three.nested.deep.one).toBe('deep-one')
70
- expect(three.nested.deep.two).toBe('deep-two')
71
- })
72
-
73
- it('Accessors', () => {
74
- let count = 0
75
- const one = {}
76
- const two = {
77
- get count() {
78
- return count
79
- },
80
- set count(value) {
81
- count = value
82
- },
83
- }
84
-
85
- const three = mix(one, two)
86
- expect(three).toEqual(two)
87
-
88
- const propertyDescriptor = Object.getOwnPropertyDescriptor(three, 'count')
89
- three.count = 3
90
- expect(three.count).toBe(3)
91
- expect(typeof propertyDescriptor.get).toBe('function')
92
- expect(typeof propertyDescriptor.set).toBe('function')
93
- expect(two.count !== three.count)
94
- })
95
-
96
- it('Empty + Nested accessors', () => {
97
- let count = 0
98
- const one = {}
99
- const two = {
100
- nested: {
101
- get count() {
102
- return count
103
- },
104
- set count(value) {
105
- count = value
106
- },
107
- },
108
- }
109
-
110
- const three = mix(one, two)
111
- expect(three).toEqual(two)
112
- expect(two.nested).not.toBe(three.nested)
113
-
114
- const descriptor = Object.getOwnPropertyDescriptor(three.nested, 'count')
115
- expect(typeof descriptor.get).toBe('function')
116
- expect(typeof descriptor.set).toBe('function')
117
- })
118
-
119
- it('Double nested with Accessors', () => {
120
- let count = 0
121
- const one = {
122
- nested: {
123
- get accessOne() {
124
- return count
125
- },
126
- set accessOne(value) {
127
- count = value
128
- },
129
- },
130
- }
131
- const two = {
132
- nested: {
133
- get accessTwo() {
134
- return count
135
- },
136
- set accessTwo(value) {
137
- count = value
138
- },
139
- },
140
- }
141
-
142
- const three = mix(one, two)
143
- expect(one.nested).not.toBe(three.nested)
144
- expect(two.nested).not.toBe(three.nested)
145
-
146
- const accessOne = Object.getOwnPropertyDescriptor(three.nested, 'accessOne')
147
- const accessTwo = Object.getOwnPropertyDescriptor(three.nested, 'accessTwo')
148
-
149
- expect(typeof accessOne.get).toBe('function')
150
- expect(typeof accessOne.set).toBe('function')
151
- expect(typeof accessTwo.get).toBe('function')
152
- expect(typeof accessTwo.set).toBe('function')
153
- })
154
-
155
- it('Nested arrays', () => {
156
- const one = {}
157
- const two = { array: [1, 2, 3] }
158
- const three = mix(one, two)
159
-
160
- expect(three).toEqual(two)
161
- expect(typeof three.array.push).toBe('function')
162
-
163
- // Prevent mutation
164
- three.array.push(4)
165
- expect(two.array).toHaveLength(3)
166
- expect(three.array).toHaveLength(4)
167
- })
168
-
169
- it('Double objects with nested same array', () => {
170
- const one = {
171
- array: [1, 2, 3],
172
- }
173
- const two = {
174
- array: [4, 5, 6],
175
- }
176
-
177
- const three = mix(one, two)
178
- expect(typeof three.array.push).toBe('function')
179
- expect(three.array).toHaveLength(3)
180
- expect(three.array).toEqual([4, 5, 6])
181
- })
182
-
183
- it('Objects in Arrays', () => {
184
- const one = {}
185
- const two = {
186
- array: [
187
- 1,
188
- { value: 2 },
189
- 3,
190
- {
191
- get count() {
192
- return 1
193
- },
194
- },
195
- ],
196
- }
197
- const three = mix(one, two)
198
-
199
- expect(three).toEqual(two)
200
- expect(typeof three.array.push).toBe('function')
201
- expect(two.array[1]).not.toBe(three.array[1])
202
-
203
- // Ensures accessors are copied over
204
- const descriptor = Object.getOwnPropertyDescriptor(three.array[3], 'count')
205
- expect(typeof descriptor.get).toBe('function')
206
- })
207
-
208
- it('Deep nested arrays', () => {
209
- const one = {}
210
- const two = {
211
- array: [1, [2], 3],
212
- }
213
- const three = mix(one, two)
214
-
215
- expect(three).toEqual(two)
216
- expect(three.array).toHaveLength(3)
217
- expect(three.array[1]).toHaveLength(1)
218
- expect(two.array[1]).not.toBe(three.array[1])
219
- })
220
-
221
- it('concatMix: concatenates arrays', () => {
222
- const one = {
223
- array: [1, 2, 3],
224
- }
225
- const two = {
226
- array: [4, 5, 6],
227
- }
228
-
229
- const three = concatMix(one, two)
230
- expect(typeof three.array.push).toBe('function')
231
- expect(three.array).toHaveLength(6)
232
- expect(three.array).toEqual([1, 2, 3, 4, 5, 6])
233
- })
234
-
235
- it('concatMix: empty array concat', () => {
236
- const one = {}
237
- const two = { array: [1, 2, 3] }
238
- const three = concatMix(one, two)
239
-
240
- expect(three).toEqual(two)
241
- expect(typeof three.array.push).toBe('function')
242
- expect(three.array).toHaveLength(3)
243
-
244
- // Prevent mutation
245
- three.array.push(4)
246
- expect(two.array).toHaveLength(3)
247
- expect(three.array).toHaveLength(4)
248
- })
249
-
250
- it('concatMix: nested objects with concatenated arrays', () => {
251
- const one = {
252
- nested: {
253
- items: [1, 2],
254
- other: 'value1',
255
- },
256
- }
257
- const two = {
258
- nested: {
259
- items: [3, 4],
260
- other: 'value2',
261
- },
262
- }
263
-
264
- const three = concatMix(one, two)
265
- expect(three.nested.items).toEqual([1, 2, 3, 4])
266
- expect(three.nested.other).toBe('value2')
267
- })
268
-
269
- it('Functions (cannot be cloned)', () => {
270
- const one = {}
271
- const two = {
272
- nested: {
273
- identity(i) {
274
- return i
275
- },
276
- deep: {
277
- identity(i) {
278
- return i
279
- },
280
- },
281
- },
282
- }
283
-
284
- const three = mix(one, two)
285
- expect(three).toEqual(two)
286
- expect(three.nested.identity).toBe(two.nested.identity)
287
- expect(three.nested.deep.identity).toBe(two.nested.deep.identity)
288
- })
289
-
290
- it('Nested Dates', () => {
291
- const one = {}
292
- const two = {
293
- date: new Date(),
294
- nested: {
295
- date: new Date(),
296
- },
297
- }
298
- const three = mix(one, two)
299
-
300
- expect(three.date).not.toBe(two.date)
301
- expect(three.date.getTime()).toBe(two.date.getTime())
302
- expect(three.nested.date).not.toBe(two.nested.date)
303
- })
304
-
305
- it('Nested Maps', () => {
306
- const one = {}
307
- const two = {
308
- map: new Map(),
309
- nested: {
310
- map: new Map(),
311
- },
312
- }
313
- const a = { one: 1 }
314
- const b = { two: 2 }
315
- two.map.set('item', { one: 1 })
316
- two.map.set(a, b)
317
- two.nested.map.set('item', { one: 1 })
318
-
319
- const three = mix(one, two)
320
-
321
- expect(three.map).not.toBe(two.map)
322
- expect(three.map).toEqual(two.map)
323
- expect(three.map.get('item')).toEqual({ one: 1 })
324
- expect(three.map.get('item')).not.toBe(two.map.get('item'))
325
-
326
- expect(three.nested.map).toEqual(two.nested.map)
327
- expect(three.nested.map).not.toBe(two.nested.map)
328
-
329
- expect(three.map.get(a)).toEqual(b)
330
- expect(three.map.get(a)).not.toBe(two.map.get(a))
331
-
332
- // Note: WeakMap keys are not enumerable... so they cannot be cloned.
333
- // That's why we're not testing WeakMap
334
- })
335
-
336
- it('Nested Sets', () => {
337
- const one = {}
338
- const two = {
339
- set: new Set(),
340
- nested: {
341
- set: new Set(),
342
- },
343
- }
344
- const a = { object: 'test' }
345
- two.set.add('one')
346
- two.set.add(a)
347
- two.nested.set.add('nested')
348
-
349
- const three = mix(one, two)
350
-
351
- expect(three.set).not.toBe(two.set)
352
- expect(three.set).toEqual(two.set)
353
- expect(three.set.has('one')).toBe(true)
354
- expect(three.set.has(a)).toBe(false)
355
-
356
- expect(three.nested.set).not.toBe(two.nested.set)
357
- expect(three.nested.set).toEqual(two.nested.set)
358
- expect(three.nested.set.has('nested')).toBe(true)
359
-
360
- // Note: WeakSet contains objects only... so there's no point creating a deep clone. You won't be able to access them.
361
- // That's why we're not testing WeakSet
362
- })
363
-
364
- // Prevents Prototype Pollution
365
- // https://codeburst.io/what-is-prototype-pollution-49482fc4b638
366
- it('Safe Merge', () => {
367
- const polluted = JSON.parse('{"__proto__": {"admin": true}}')
368
- const mixed = mix(polluted)
369
-
370
- /* eslint-disable */
371
- expect(mixed.__proto__).toEqual({})
372
- /* eslint-enable */
373
- })
374
-
375
- it('should skip null and undefined sources', () => {
376
- const obj1 = { a: 1 }
377
- const obj2 = { b: 2 }
378
- const expected = { a: 1, b: 2 }
379
-
380
- expect(mix(obj1, null, obj2)).toEqual(expected)
381
- expect(mix(null, obj1, obj2)).toEqual(expected)
382
- expect(mix(obj1, obj2, null)).toEqual(expected)
383
-
384
- expect(mix(obj1, undefined, obj2)).toEqual(expected)
385
- expect(mix(undefined, obj1, obj2)).toEqual(expected)
386
- expect(mix(obj1, obj2, undefined)).toEqual(expected)
387
-
388
- expect(mix(null, obj1, undefined, obj2, null)).toEqual(expected)
389
- })
390
-
391
- it('should not skip null and false properties', () => {
392
- const obj1 = { a: 1, b: 2, c: 3 }
393
- const obj2 = { a: null, b: false, c: undefined }
394
- const expected = { a: null, b: false, c: undefined }
395
-
396
- expect(mix(obj1, obj2)).toEqual(expected)
397
- })
398
-
399
- it('Real Nested Example', () => {
400
- const defaultOpts = {
401
- transforms: {
402
- extractFromFileName: { date: 'pubDate', slug: 'slug' },
403
- setLatestDate: {
404
- targetProperty: 'date',
405
- sourceProperties: ['pubDate', 'updateDate'],
406
- },
407
- generateExcerpt: true,
408
- stripFileNumbering: true,
409
- stripMore: true,
410
- },
411
- sort: {
412
- props: ['data.date', 'data.pubDate', 'data.title'],
413
- reverse: true,
414
- },
415
- filter: 'published',
416
- }
417
-
418
- const newOptions = {
419
- transforms: {
420
- extractFromFileName: false,
421
- generateExcerpt: false,
422
- stripMore: false,
423
- // stripFileNumbering: true,
424
- },
425
- sort: { props: ['filePath'], reverse: false },
426
- filter: false,
427
- }
428
- const expected = {
429
- transforms: {
430
- setLatestDate: {
431
- targetProperty: 'date',
432
- sourceProperties: ['pubDate', 'updateDate'],
433
- },
434
- extractFromFileName: false,
435
- generateExcerpt: false,
436
- stripFileNumbering: true,
437
- stripMore: false,
438
- },
439
- sort: { props: ['filePath'], reverse: false },
440
- filter: false,
441
- }
442
-
443
- const mixReplace = createMix({ array: 'replace' })
444
- expect(mixReplace(defaultOpts, newOptions)).toEqual(expected)
445
- })