@codeleap/styles 5.8.2 → 5.8.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.
- package/package.json +9 -15
- package/package.json.bak +8 -14
- package/src/classes/Cacher.ts +169 -0
- package/src/classes/StaleControl.ts +125 -0
- package/src/classes/StyleCache.ts +116 -0
- package/src/classes/StylePersistor.ts +62 -0
- package/src/{lib → classes}/StyleRegistry.ts +7 -12
- package/src/classes/index.ts +2 -0
- package/src/classes/tests/Cache.spec.ts +371 -0
- package/src/classes/tests/StaleControl.spec.ts +175 -0
- package/src/classes/tests/StyleCache.spec.ts +452 -0
- package/src/classes/tests/StylePersistor.spec.ts +231 -0
- package/src/{lib/constants.ts → constants.ts} +1 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/tests/useCompositionStyles.spec.ts +107 -0
- package/src/hooks/tests/useStyleObserver.spec.ts +89 -0
- package/src/hooks/useCompositionStyles.ts +33 -0
- package/src/hooks/useNestedStylesByKey.ts +13 -0
- package/src/hooks/useStyleObserver.ts +19 -0
- package/src/hooks/useTheme.ts +16 -0
- package/src/index.ts +9 -5
- package/src/lib/createStyles.ts +10 -1
- package/src/lib/createTheme.ts +22 -13
- package/src/lib/index.ts +1 -10
- package/src/lib/tests/createStyles.spec.ts +151 -0
- package/src/tests/colors/baseColors.ts +166 -0
- package/src/tests/colors/darkMode.ts +232 -0
- package/src/tests/colors/lightMode.ts +285 -0
- package/src/tests/measures.ts +31 -0
- package/src/tests/theme.ts +58 -0
- package/src/theme/generateColorScheme.ts +53 -0
- package/src/theme/index.ts +3 -0
- package/src/theme/tests/generateColorScheme.spec.ts +118 -0
- package/src/theme/tests/themeStore.spec.ts +698 -0
- package/src/theme/tests/validateTheme.spec.ts +173 -0
- package/src/{lib → theme}/themeStore.ts +68 -3
- package/src/{lib → theme}/validateTheme.ts +13 -0
- package/src/tools/colors.ts +83 -39
- package/src/tools/deepClone.ts +10 -0
- package/src/tools/deepmerge.ts +10 -0
- package/src/{lib → tools}/hashKey.ts +7 -0
- package/src/tools/index.ts +6 -1
- package/src/tools/minifier.ts +38 -0
- package/src/tools/tests/colors.spec.ts +233 -0
- package/src/tools/tests/deepClone.spec.ts +102 -0
- package/src/tools/tests/deepmerge.spec.ts +155 -0
- package/src/tools/tests/hashKey.spec.ts +69 -0
- package/src/tools/tests/minifier.spec.ts +173 -0
- package/src/types/store.ts +2 -2
- package/src/types/style.ts +3 -3
- package/src/types/theme.ts +4 -4
- package/src/{lib/utils.ts → utils.ts} +3 -3
- package/src/{lib → variants}/borderCreator.ts +2 -2
- package/src/{lib → variants}/createAppVariants.ts +1 -1
- package/src/{lib → variants}/dynamicVariants.ts +1 -1
- package/src/variants/index.ts +6 -0
- package/src/{lib → variants}/spacing.ts +37 -24
- package/src/variants/tests/borderCreator.spec.ts +180 -0
- package/src/variants/tests/dynamicVariants.spec.ts +194 -0
- package/src/variants/tests/spacing.spec.ts +177 -0
- package/src/lib/Cacher.ts +0 -112
- package/src/lib/StaleControl.ts +0 -73
- package/src/lib/StyleCache.ts +0 -81
- package/src/lib/StylePersistor.ts +0 -28
- package/src/lib/hooks.ts +0 -64
- package/src/lib/minifier.ts +0 -20
- /package/src/{lib → tools}/multiplierProperty.ts +0 -0
- /package/src/{lib → variants}/defaultVariants.ts +0 -0
- /package/src/{lib → variants}/mediaQuery.ts +0 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { test, expect, describe } from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
hexToHSL,
|
|
4
|
+
hslToHex,
|
|
5
|
+
hexToRGB,
|
|
6
|
+
hslToRGB,
|
|
7
|
+
getLuminance,
|
|
8
|
+
getTextColor
|
|
9
|
+
} from '../colors'
|
|
10
|
+
|
|
11
|
+
describe('colors', () => {
|
|
12
|
+
describe('hexToHSL', () => {
|
|
13
|
+
test('should convert red hex to HSL', () => {
|
|
14
|
+
const result = hexToHSL('#ff0000')
|
|
15
|
+
expect(result).toEqual({ h: 0, s: 100, l: 50 })
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('should convert green hex to HSL', () => {
|
|
19
|
+
const result = hexToHSL('#00ff00')
|
|
20
|
+
expect(result).toEqual({ h: 120, s: 100, l: 50 })
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('should convert blue hex to HSL', () => {
|
|
24
|
+
const result = hexToHSL('#0000ff')
|
|
25
|
+
expect(result).toEqual({ h: 240, s: 100, l: 50 })
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('should convert white to HSL', () => {
|
|
29
|
+
const result = hexToHSL('#ffffff')
|
|
30
|
+
expect(result).toEqual({ h: 0, s: 0, l: 100 })
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('should convert black to HSL', () => {
|
|
34
|
+
const result = hexToHSL('#000000')
|
|
35
|
+
expect(result).toEqual({ h: 0, s: 0, l: 0 })
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('should handle custom colors', () => {
|
|
39
|
+
const result = hexToHSL('#ff5733')
|
|
40
|
+
expect(result.h).toBeGreaterThanOrEqual(0)
|
|
41
|
+
expect(result.h).toBeLessThanOrEqual(360)
|
|
42
|
+
expect(result.s).toBeGreaterThanOrEqual(0)
|
|
43
|
+
expect(result.s).toBeLessThanOrEqual(100)
|
|
44
|
+
expect(result.l).toBeGreaterThanOrEqual(0)
|
|
45
|
+
expect(result.l).toBeLessThanOrEqual(100)
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
describe('hslToHex', () => {
|
|
50
|
+
test('should convert HSL to red hex', () => {
|
|
51
|
+
const result = hslToHex(0, 100, 50)
|
|
52
|
+
expect(result).toBe('#ff0000')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('should convert HSL to green hex', () => {
|
|
56
|
+
const result = hslToHex(120, 100, 50)
|
|
57
|
+
expect(result).toBe('#00ff00')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('should convert HSL to blue hex', () => {
|
|
61
|
+
const result = hslToHex(240, 100, 50)
|
|
62
|
+
expect(result).toBe('#0000ff')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('should convert HSL to white hex', () => {
|
|
66
|
+
const result = hslToHex(0, 0, 100)
|
|
67
|
+
expect(result).toBe('#ffffff')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('should convert HSL to black hex', () => {
|
|
71
|
+
const result = hslToHex(0, 0, 0)
|
|
72
|
+
expect(result).toBe('#000000')
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe('hexToRGB', () => {
|
|
77
|
+
test('should convert red hex to RGB', () => {
|
|
78
|
+
const result = hexToRGB('#ff0000')
|
|
79
|
+
expect(result).toEqual({ r: 255, g: 0, b: 0 })
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('should convert green hex to RGB', () => {
|
|
83
|
+
const result = hexToRGB('#00ff00')
|
|
84
|
+
expect(result).toEqual({ r: 0, g: 255, b: 0 })
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('should convert blue hex to RGB', () => {
|
|
88
|
+
const result = hexToRGB('#0000ff')
|
|
89
|
+
expect(result).toEqual({ r: 0, g: 0, b: 255 })
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('should convert white hex to RGB', () => {
|
|
93
|
+
const result = hexToRGB('#ffffff')
|
|
94
|
+
expect(result).toEqual({ r: 255, g: 255, b: 255 })
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('should convert black hex to RGB', () => {
|
|
98
|
+
const result = hexToRGB('#000000')
|
|
99
|
+
expect(result).toEqual({ r: 0, g: 0, b: 0 })
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
describe('hslToRGB', () => {
|
|
104
|
+
test('should convert red HSL to RGB', () => {
|
|
105
|
+
const result = hslToRGB(0, 100, 50)
|
|
106
|
+
expect(result).toEqual({ r: 255, g: 0, b: 0 })
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('should convert green HSL to RGB', () => {
|
|
110
|
+
const result = hslToRGB(120, 100, 50)
|
|
111
|
+
expect(result).toEqual({ r: 0, g: 255, b: 0 })
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test('should convert blue HSL to RGB', () => {
|
|
115
|
+
const result = hslToRGB(240, 100, 50)
|
|
116
|
+
expect(result).toEqual({ r: 0, g: 0, b: 255 })
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test('should convert white HSL to RGB', () => {
|
|
120
|
+
const result = hslToRGB(0, 0, 100)
|
|
121
|
+
expect(result).toEqual({ r: 255, g: 255, b: 255 })
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test('should convert black HSL to RGB', () => {
|
|
125
|
+
const result = hslToRGB(0, 0, 0)
|
|
126
|
+
expect(result).toEqual({ r: 0, g: 0, b: 0 })
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
describe('conversion roundtrip', () => {
|
|
131
|
+
test('hex -> HSL -> hex should maintain color integrity', () => {
|
|
132
|
+
const originalHex = '#ff5733'
|
|
133
|
+
const hsl = hexToHSL(originalHex)
|
|
134
|
+
const backToHex = hslToHex(hsl.h, hsl.s, hsl.l)
|
|
135
|
+
|
|
136
|
+
// Allow for small rounding differences in conversion
|
|
137
|
+
const original = hexToRGB(originalHex)
|
|
138
|
+
const converted = hexToRGB(backToHex)
|
|
139
|
+
|
|
140
|
+
expect(Math.abs(original.r - converted.r)).toBeLessThanOrEqual(1)
|
|
141
|
+
expect(Math.abs(original.g - converted.g)).toBeLessThanOrEqual(1)
|
|
142
|
+
expect(Math.abs(original.b - converted.b)).toBeLessThanOrEqual(1)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test('should work with primary colors (exact conversion)', () => {
|
|
146
|
+
const colors = ['#ff0000', '#00ff00', '#0000ff', '#ffffff', '#000000']
|
|
147
|
+
|
|
148
|
+
colors.forEach(hex => {
|
|
149
|
+
const hsl = hexToHSL(hex)
|
|
150
|
+
const backToHex = hslToHex(hsl.h, hsl.s, hsl.l)
|
|
151
|
+
expect(backToHex).toBe(hex)
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
describe('getLuminance', () => {
|
|
157
|
+
test('should return 1 for white', () => {
|
|
158
|
+
const luminance = getLuminance({ r: 255, g: 255, b: 255 })
|
|
159
|
+
expect(luminance).toBeCloseTo(1, 2)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
test('should return 0 for black', () => {
|
|
163
|
+
const luminance = getLuminance({ r: 0, g: 0, b: 0 })
|
|
164
|
+
expect(luminance).toBeCloseTo(0, 2)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test('should return values between 0 and 1', () => {
|
|
168
|
+
const colors = [
|
|
169
|
+
{ r: 255, g: 0, b: 0 }, // Red
|
|
170
|
+
{ r: 0, g: 255, b: 0 }, // Green
|
|
171
|
+
{ r: 0, g: 0, b: 255 }, // Blue
|
|
172
|
+
{ r: 128, g: 128, b: 128 } // Gray
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
colors.forEach(color => {
|
|
176
|
+
const luminance = getLuminance(color)
|
|
177
|
+
expect(luminance).toBeGreaterThanOrEqual(0)
|
|
178
|
+
expect(luminance).toBeLessThanOrEqual(1)
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
test('should calculate different values for different colors', () => {
|
|
183
|
+
const red = getLuminance({ r: 255, g: 0, b: 0 })
|
|
184
|
+
const green = getLuminance({ r: 0, g: 255, b: 0 })
|
|
185
|
+
const blue = getLuminance({ r: 0, g: 0, b: 255 })
|
|
186
|
+
|
|
187
|
+
expect(red).not.toBe(green)
|
|
188
|
+
expect(green).not.toBe(blue)
|
|
189
|
+
expect(red).not.toBe(blue)
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
describe('getTextColor', () => {
|
|
194
|
+
test('should return dark text for light backgrounds', () => {
|
|
195
|
+
const textColor = getTextColor('#ffffff') // White background
|
|
196
|
+
expect(textColor).toBe('black')
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
test('should return light text for dark backgrounds', () => {
|
|
200
|
+
const textColor = getTextColor('#000000') // Black background
|
|
201
|
+
expect(textColor).toBe('white')
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
test('should use custom colors when provided', () => {
|
|
205
|
+
const textColor = getTextColor('#ffffff', '#333333', '#f0f0f0')
|
|
206
|
+
expect(textColor).toBe('#333333')
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
test('should handle medium brightness colors', () => {
|
|
210
|
+
const lightGray = getTextColor('#cccccc')
|
|
211
|
+
const darkGray = getTextColor('#333333')
|
|
212
|
+
|
|
213
|
+
expect(lightGray).toBe('black')
|
|
214
|
+
expect(darkGray).toBe('white')
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
test('should be consistent with luminance calculations', () => {
|
|
218
|
+
const colors = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff']
|
|
219
|
+
|
|
220
|
+
colors.forEach(hex => {
|
|
221
|
+
const rgb = hexToRGB(hex)
|
|
222
|
+
const luminance = getLuminance(rgb)
|
|
223
|
+
const textColor = getTextColor(hex)
|
|
224
|
+
|
|
225
|
+
if (luminance > 0.5) {
|
|
226
|
+
expect(textColor).toBe('black')
|
|
227
|
+
} else {
|
|
228
|
+
expect(textColor).toBe('white')
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
})
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { test, expect, describe } from 'bun:test'
|
|
2
|
+
import { deepClone } from '../deepClone'
|
|
3
|
+
|
|
4
|
+
describe('deepClone', () => {
|
|
5
|
+
test('should clone primitive values', () => {
|
|
6
|
+
expect(deepClone(42)).toBe(42)
|
|
7
|
+
expect(deepClone('hello')).toBe('hello')
|
|
8
|
+
expect(deepClone(true)).toBe(true)
|
|
9
|
+
expect(deepClone(null)).toBe(null)
|
|
10
|
+
expect(deepClone(undefined)).toBe(undefined)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
test('should create independent copy of objects', () => {
|
|
14
|
+
const original = { name: 'John', age: 30 }
|
|
15
|
+
const cloned = deepClone(original)
|
|
16
|
+
|
|
17
|
+
expect(cloned).toEqual(original)
|
|
18
|
+
expect(cloned).not.toBe(original)
|
|
19
|
+
|
|
20
|
+
cloned.name = 'Jane'
|
|
21
|
+
expect(original.name).toBe('John')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('should clone nested objects', () => {
|
|
25
|
+
const original = {
|
|
26
|
+
user: { name: 'John', details: { age: 30, city: 'NY' } },
|
|
27
|
+
settings: { theme: 'dark' }
|
|
28
|
+
}
|
|
29
|
+
const cloned = deepClone(original)
|
|
30
|
+
|
|
31
|
+
expect(cloned).toEqual(original)
|
|
32
|
+
expect(cloned.user).not.toBe(original.user)
|
|
33
|
+
expect(cloned.user.details).not.toBe(original.user.details)
|
|
34
|
+
|
|
35
|
+
cloned.user.details.age = 31
|
|
36
|
+
expect(original.user.details.age).toBe(30)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('should clone arrays', () => {
|
|
40
|
+
const original = [1, 2, [3, 4]]
|
|
41
|
+
const cloned = deepClone(original)
|
|
42
|
+
|
|
43
|
+
expect(cloned).toEqual(original)
|
|
44
|
+
expect(cloned).not.toBe(original)
|
|
45
|
+
expect(cloned[2]).not.toBe(original[2])
|
|
46
|
+
|
|
47
|
+
cloned[2][0] = 5
|
|
48
|
+
expect(original[2][0]).toBe(3)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('should handle complex nested structures', () => {
|
|
52
|
+
const original = {
|
|
53
|
+
items: [
|
|
54
|
+
{ id: 1, tags: ['red', 'blue'] },
|
|
55
|
+
{ id: 2, tags: ['green'] }
|
|
56
|
+
],
|
|
57
|
+
meta: { count: 2, filters: { active: true } }
|
|
58
|
+
}
|
|
59
|
+
const cloned = deepClone(original)
|
|
60
|
+
|
|
61
|
+
expect(cloned).toEqual(original)
|
|
62
|
+
|
|
63
|
+
cloned.items[0].tags.push('yellow')
|
|
64
|
+
cloned.meta.filters.active = false
|
|
65
|
+
|
|
66
|
+
expect(original.items[0].tags).toHaveLength(2)
|
|
67
|
+
expect(original.meta.filters.active).toBe(true)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('should handle dates', () => {
|
|
71
|
+
const original = new Date('2023-01-01')
|
|
72
|
+
const cloned = deepClone(original)
|
|
73
|
+
|
|
74
|
+
expect(cloned).toEqual(original)
|
|
75
|
+
expect(cloned).not.toBe(original)
|
|
76
|
+
expect(cloned instanceof Date).toBe(true)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('should handle empty objects and arrays', () => {
|
|
80
|
+
const emptyObj = deepClone({})
|
|
81
|
+
const emptyArr = deepClone([])
|
|
82
|
+
|
|
83
|
+
expect(emptyObj).toEqual({})
|
|
84
|
+
expect(emptyArr).toEqual([])
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('should clone class instances as plain objects', () => {
|
|
88
|
+
class Person {
|
|
89
|
+
constructor(public name: string) {}
|
|
90
|
+
greet() { return `Hello, ${this.name}` }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const original = new Person('John')
|
|
94
|
+
const cloned = deepClone(original) as any
|
|
95
|
+
|
|
96
|
+
expect(cloned).toEqual({ name: 'John' })
|
|
97
|
+
expect(cloned).not.toBe(original)
|
|
98
|
+
expect((cloned as any).name).toBe('John')
|
|
99
|
+
// Methods are not cloned, only data properties
|
|
100
|
+
expect((cloned as any).greet).toBeUndefined()
|
|
101
|
+
})
|
|
102
|
+
})
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { test, expect, describe } from 'bun:test'
|
|
2
|
+
import { deepmerge } from '../deepmerge'
|
|
3
|
+
|
|
4
|
+
describe('deepmerge', () => {
|
|
5
|
+
const merge = deepmerge()
|
|
6
|
+
|
|
7
|
+
test('should merge simple objects', () => {
|
|
8
|
+
const target = { a: 1, b: 2 }
|
|
9
|
+
const source = { b: 3, c: 4 }
|
|
10
|
+
const result = merge(target, source)
|
|
11
|
+
|
|
12
|
+
expect(result).toEqual({ a: 1, b: 3, c: 4 })
|
|
13
|
+
expect(result).not.toBe(target)
|
|
14
|
+
expect(result).not.toBe(source)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('should merge nested objects', () => {
|
|
18
|
+
const target = {
|
|
19
|
+
user: { name: 'John', age: 30 },
|
|
20
|
+
settings: { theme: 'light' }
|
|
21
|
+
}
|
|
22
|
+
const source = {
|
|
23
|
+
user: { age: 31, city: 'NY' },
|
|
24
|
+
settings: { language: 'en' }
|
|
25
|
+
}
|
|
26
|
+
const result = merge(target, source)
|
|
27
|
+
|
|
28
|
+
expect(result).toEqual({
|
|
29
|
+
user: { name: 'John', age: 31, city: 'NY' },
|
|
30
|
+
settings: { theme: 'light', language: 'en' }
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('should concatenate arrays by default', () => {
|
|
35
|
+
const target = { items: [1, 2, 3] }
|
|
36
|
+
const source = { items: [4, 5] }
|
|
37
|
+
const result = merge(target, source)
|
|
38
|
+
|
|
39
|
+
expect(result).toEqual({ items: [1, 2, 3, 4, 5] })
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('should handle mixed data types', () => {
|
|
43
|
+
const target = {
|
|
44
|
+
string: 'hello',
|
|
45
|
+
number: 42,
|
|
46
|
+
boolean: true,
|
|
47
|
+
nested: { a: 1 }
|
|
48
|
+
}
|
|
49
|
+
const source = {
|
|
50
|
+
string: 'world',
|
|
51
|
+
array: [1, 2, 3],
|
|
52
|
+
nested: { b: 2 }
|
|
53
|
+
}
|
|
54
|
+
const result = merge(target, source)
|
|
55
|
+
|
|
56
|
+
expect(result).toEqual({
|
|
57
|
+
string: 'world',
|
|
58
|
+
number: 42,
|
|
59
|
+
boolean: true,
|
|
60
|
+
array: [1, 2, 3],
|
|
61
|
+
nested: { a: 1, b: 2 }
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('should not mutate original objects', () => {
|
|
66
|
+
const target = { a: { x: 1 }, arr: [1] }
|
|
67
|
+
const source = { a: { y: 2 }, arr: [2] }
|
|
68
|
+
|
|
69
|
+
const result = merge(target, source)
|
|
70
|
+
|
|
71
|
+
expect(target.a).toEqual({ x: 1 })
|
|
72
|
+
expect(target.arr).toEqual([1])
|
|
73
|
+
expect(source.a).toEqual({ y: 2 })
|
|
74
|
+
expect(source.arr).toEqual([2])
|
|
75
|
+
expect(result.a).toEqual({ x: 1, y: 2 })
|
|
76
|
+
expect(result.arr).toEqual([1, 2])
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('should handle primitive values', () => {
|
|
80
|
+
const result1 = merge({ a: 1 }, { a: 'string' })
|
|
81
|
+
const result2 = merge({ a: null }, { a: 'value' })
|
|
82
|
+
const result3 = merge({ a: undefined }, { a: 42 })
|
|
83
|
+
|
|
84
|
+
expect(result1).toEqual({ a: 'string' })
|
|
85
|
+
expect(result2).toEqual({ a: 'value' })
|
|
86
|
+
expect(result3).toEqual({ a: 42 })
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('should handle RegExp and Date objects', () => {
|
|
90
|
+
const target = { regex: /old/, date: new Date('2023-01-01') }
|
|
91
|
+
const source = { regex: /new/, date: new Date('2024-01-01') }
|
|
92
|
+
const result = merge(target, source)
|
|
93
|
+
|
|
94
|
+
expect(result.regex).toEqual(/new/)
|
|
95
|
+
expect(result.date).toEqual(new Date('2024-01-01'))
|
|
96
|
+
expect(result.regex).not.toBe(target.regex)
|
|
97
|
+
expect(result.date).not.toBe(target.date)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('should merge multiple levels deep', () => {
|
|
101
|
+
const target = {
|
|
102
|
+
level1: {
|
|
103
|
+
level2: {
|
|
104
|
+
level3: { a: 1, b: 2 }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const source = {
|
|
109
|
+
level1: {
|
|
110
|
+
level2: {
|
|
111
|
+
level3: { b: 3, c: 4 },
|
|
112
|
+
other: 'value'
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const result = merge(target, source)
|
|
117
|
+
|
|
118
|
+
expect(result).toEqual({
|
|
119
|
+
level1: {
|
|
120
|
+
level2: {
|
|
121
|
+
level3: { a: 1, b: 3, c: 4 },
|
|
122
|
+
other: 'value'
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test('should handle empty objects', () => {
|
|
129
|
+
const result1 = merge({}, { a: 1 })
|
|
130
|
+
const result2 = merge({ a: 1 }, {})
|
|
131
|
+
|
|
132
|
+
expect(result1).toEqual({ a: 1 })
|
|
133
|
+
expect(result2).toEqual({ a: 1 })
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test('should handle array vs object type mismatch', () => {
|
|
137
|
+
const result1 = merge({ a: [1, 2] }, { a: { x: 1 } })
|
|
138
|
+
const result2 = merge({ a: { x: 1 } }, { a: [1, 2] })
|
|
139
|
+
|
|
140
|
+
expect(result1).toEqual({ a: { x: 1 } })
|
|
141
|
+
expect(result2).toEqual({ a: [1, 2] })
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test('should support mergeAll option', () => {
|
|
145
|
+
const mergeAll = deepmerge({ all: true })
|
|
146
|
+
const result = mergeAll(
|
|
147
|
+
{ a: 1 },
|
|
148
|
+
{ b: 2 },
|
|
149
|
+
{ c: 3 },
|
|
150
|
+
{ a: 10 }
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
expect(result).toEqual({ a: 10, b: 2, c: 3 })
|
|
154
|
+
})
|
|
155
|
+
})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { test, expect, describe } from 'bun:test'
|
|
2
|
+
import { hashKey } from '../hashKey'
|
|
3
|
+
|
|
4
|
+
describe('hashKey', () => {
|
|
5
|
+
test('should generate consistent hash for same input', () => {
|
|
6
|
+
const input = ['style1', 'style2']
|
|
7
|
+
const hash1 = hashKey([...input])
|
|
8
|
+
const hash2 = hashKey([...input])
|
|
9
|
+
|
|
10
|
+
expect(hash1).toBe(hash2)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
test('should generate different hashes for different inputs', () => {
|
|
14
|
+
const input1 = ['style1', 'style2']
|
|
15
|
+
const input2 = ['style3', 'style4']
|
|
16
|
+
|
|
17
|
+
const hash1 = hashKey([...input1])
|
|
18
|
+
const hash2 = hashKey([...input2])
|
|
19
|
+
|
|
20
|
+
expect(hash1).not.toBe(hash2)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('should return a valid SHA-256 hash string', () => {
|
|
24
|
+
const input = ['test']
|
|
25
|
+
const hash = hashKey([...input])
|
|
26
|
+
|
|
27
|
+
// SHA-256 hash should be 64 characters long (hex)
|
|
28
|
+
expect(hash).toHaveLength(64)
|
|
29
|
+
expect(hash).toMatch(/^[a-f0-9]{64}$/)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('should handle empty array', () => {
|
|
33
|
+
const hash = hashKey([])
|
|
34
|
+
|
|
35
|
+
expect(hash).toHaveLength(64)
|
|
36
|
+
expect(hash).toMatch(/^[a-f0-9]{64}$/)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('should handle arrays with objects', () => {
|
|
40
|
+
const input = [{ color: 'red' }, { fontSize: '14px' }]
|
|
41
|
+
const hash = hashKey([...input])
|
|
42
|
+
|
|
43
|
+
expect(hash).toHaveLength(64)
|
|
44
|
+
expect(hash).toMatch(/^[a-f0-9]{64}$/)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('should mutate original array by adding version', () => {
|
|
48
|
+
const input = ['style1']
|
|
49
|
+
const originalLength = input.length
|
|
50
|
+
|
|
51
|
+
hashKey(input)
|
|
52
|
+
|
|
53
|
+
expect(input).toHaveLength(originalLength + 1)
|
|
54
|
+
expect(input[input.length - 1]).toHaveProperty('@styles-version')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('should include version in hash calculation', () => {
|
|
58
|
+
// Mock different versions to test version impact
|
|
59
|
+
const input = ['same-style']
|
|
60
|
+
|
|
61
|
+
const hash1 = hashKey([...input])
|
|
62
|
+
|
|
63
|
+
// Simulate version change by adding version manually
|
|
64
|
+
const inputWithDifferentVersion = [...input, { '@styles-version': 'different-version' }]
|
|
65
|
+
const hash2 = hashKey([...inputWithDifferentVersion])
|
|
66
|
+
|
|
67
|
+
expect(hash1).not.toBe(hash2)
|
|
68
|
+
})
|
|
69
|
+
})
|