@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,452 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach } from 'bun:test'
|
|
2
|
+
import { StyleCache } from '../StyleCache'
|
|
3
|
+
|
|
4
|
+
class MockStateStorage {
|
|
5
|
+
private storage = new Map<string, any>()
|
|
6
|
+
|
|
7
|
+
getItem(key: string): any {
|
|
8
|
+
return this.storage.get(key) || null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
setItem(key: string, value: any): void {
|
|
12
|
+
this.storage.set(key, value)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
removeItem(key: string): void {
|
|
16
|
+
this.storage.delete(key)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
clear(): void {
|
|
20
|
+
this.storage.clear()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get size(): number {
|
|
24
|
+
return this.storage.size
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('StyleCache', () => {
|
|
29
|
+
let styleCache: StyleCache
|
|
30
|
+
let mockStorage: MockStateStorage
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
mockStorage = new MockStateStorage()
|
|
34
|
+
styleCache = new StyleCache(mockStorage)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe('Constructor', () => {
|
|
38
|
+
test('should create StyleCache with all cache instances', () => {
|
|
39
|
+
expect(styleCache).toBeDefined()
|
|
40
|
+
expect(styleCache.styles).toBeDefined()
|
|
41
|
+
expect(styleCache.compositions).toBeDefined()
|
|
42
|
+
expect(styleCache.responsive).toBeDefined()
|
|
43
|
+
expect(styleCache.variants).toBeDefined()
|
|
44
|
+
expect(styleCache.common).toBeDefined()
|
|
45
|
+
expect(styleCache.components).toBeDefined()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('should create non-persistent caches for styles, compositions, and responsive', () => {
|
|
49
|
+
expect(styleCache.styles.registryName).toBe('styles')
|
|
50
|
+
expect(styleCache.compositions.registryName).toBe('compositions')
|
|
51
|
+
expect(styleCache.responsive.registryName).toBe('responsive')
|
|
52
|
+
|
|
53
|
+
// Non-persistent caches should not have persistCache enabled
|
|
54
|
+
expect(styleCache.styles.persistCache).toBe(false)
|
|
55
|
+
expect(styleCache.compositions.persistCache).toBe(false)
|
|
56
|
+
expect(styleCache.responsive.persistCache).toBe(false)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('should create persistent caches for variants, common, and components', () => {
|
|
60
|
+
expect(styleCache.variants.registryName).toBe('variants')
|
|
61
|
+
expect(styleCache.common.registryName).toBe('common')
|
|
62
|
+
expect(styleCache.components.registryName).toBe('components')
|
|
63
|
+
|
|
64
|
+
// Persistent caches should have persistCache enabled
|
|
65
|
+
expect(styleCache.variants.persistCache).toBe(true)
|
|
66
|
+
expect(styleCache.common.persistCache).toBe(true)
|
|
67
|
+
expect(styleCache.components.persistCache).toBe(true)
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('registerBaseKey', () => {
|
|
72
|
+
test('should generate base key with provided keys and version', () => {
|
|
73
|
+
const keys = ['key1', 'key2', { prop: 'value' }]
|
|
74
|
+
const originalKeysLength = keys.length
|
|
75
|
+
|
|
76
|
+
const result = styleCache.registerBaseKey(keys)
|
|
77
|
+
|
|
78
|
+
expect(typeof result).toBe('string')
|
|
79
|
+
expect(result).toBeTruthy()
|
|
80
|
+
expect(styleCache.baseKey).toBe(result)
|
|
81
|
+
// Should have added the version to keys array
|
|
82
|
+
expect(keys.length).toBeGreaterThanOrEqual(originalKeysLength + 1)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('should modify original keys array by adding version', () => {
|
|
86
|
+
const keys = ['test']
|
|
87
|
+
const originalKeys = [...keys]
|
|
88
|
+
|
|
89
|
+
styleCache.registerBaseKey(keys)
|
|
90
|
+
|
|
91
|
+
expect(keys.length).toBeGreaterThanOrEqual(originalKeys.length + 1)
|
|
92
|
+
expect(keys.slice(0, 1)).toEqual(originalKeys)
|
|
93
|
+
// Last item should be the version
|
|
94
|
+
expect(typeof keys[keys.length - 1]).toBe('object')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('should handle empty keys array', () => {
|
|
98
|
+
const keys: any[] = []
|
|
99
|
+
|
|
100
|
+
const result = styleCache.registerBaseKey(keys)
|
|
101
|
+
|
|
102
|
+
expect(typeof result).toBe('string')
|
|
103
|
+
expect(result).toBeTruthy()
|
|
104
|
+
expect(keys.length).toBeGreaterThanOrEqual(1)
|
|
105
|
+
expect(styleCache.baseKey).toBe(result)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test('should generate different keys for different inputs', () => {
|
|
109
|
+
const keys1 = ['a', 'b']
|
|
110
|
+
const keys2 = ['c', 'd']
|
|
111
|
+
|
|
112
|
+
const result1 = styleCache.registerBaseKey(keys1)
|
|
113
|
+
const result2 = styleCache.registerBaseKey(keys2)
|
|
114
|
+
|
|
115
|
+
expect(result1).not.toBe(result2)
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
describe('wipeCache', () => {
|
|
120
|
+
test('should clear all cache instances', () => {
|
|
121
|
+
// First, add some data to caches
|
|
122
|
+
styleCache.styles.cache['test1'] = 'value1'
|
|
123
|
+
styleCache.compositions.cache['test2'] = 'value2'
|
|
124
|
+
styleCache.responsive.cache['test3'] = 'value3'
|
|
125
|
+
styleCache.variants.cache['test4'] = 'value4'
|
|
126
|
+
styleCache.common.cache['test5'] = 'value5'
|
|
127
|
+
styleCache.components.cache['test6'] = 'value6'
|
|
128
|
+
|
|
129
|
+
// Verify data exists
|
|
130
|
+
expect(Object.keys(styleCache.styles.cache).length).toBeGreaterThan(0)
|
|
131
|
+
expect(Object.keys(styleCache.compositions.cache).length).toBeGreaterThan(0)
|
|
132
|
+
expect(Object.keys(styleCache.responsive.cache).length).toBeGreaterThan(0)
|
|
133
|
+
expect(Object.keys(styleCache.variants.cache).length).toBeGreaterThan(0)
|
|
134
|
+
expect(Object.keys(styleCache.common.cache).length).toBeGreaterThan(0)
|
|
135
|
+
expect(Object.keys(styleCache.components.cache).length).toBeGreaterThan(0)
|
|
136
|
+
|
|
137
|
+
// Clear all caches
|
|
138
|
+
styleCache.wipeCache()
|
|
139
|
+
|
|
140
|
+
// Verify all caches are empty
|
|
141
|
+
expect(Object.keys(styleCache.styles.cache)).toHaveLength(0)
|
|
142
|
+
expect(Object.keys(styleCache.compositions.cache)).toHaveLength(0)
|
|
143
|
+
expect(Object.keys(styleCache.responsive.cache)).toHaveLength(0)
|
|
144
|
+
expect(Object.keys(styleCache.variants.cache)).toHaveLength(0)
|
|
145
|
+
expect(Object.keys(styleCache.common.cache)).toHaveLength(0)
|
|
146
|
+
expect(Object.keys(styleCache.components.cache)).toHaveLength(0)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('should also clear persistent storage for persistent caches', () => {
|
|
150
|
+
// Add some data that would be persisted
|
|
151
|
+
styleCache.variants.cacheFor('persistent-key', 'persistent-value')
|
|
152
|
+
styleCache.common.cacheFor('common-key', 'common-value')
|
|
153
|
+
styleCache.components.cacheFor('component-key', 'component-value')
|
|
154
|
+
|
|
155
|
+
// Verify storage has data
|
|
156
|
+
expect(mockStorage.size).toBeGreaterThan(0)
|
|
157
|
+
|
|
158
|
+
styleCache.wipeCache()
|
|
159
|
+
|
|
160
|
+
// Storage should be cleared for persistent caches
|
|
161
|
+
expect(mockStorage.size).toBe(0)
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
describe('keyFor', () => {
|
|
166
|
+
beforeEach(() => {
|
|
167
|
+
styleCache.registerBaseKey(['test-app'])
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
test('should generate key and return null for non-existent cache value', () => {
|
|
171
|
+
const keyData = { prop1: 'value1', prop2: 'value2' }
|
|
172
|
+
|
|
173
|
+
const result = styleCache.keyFor('styles', keyData)
|
|
174
|
+
|
|
175
|
+
expect(typeof result.key).toBe('string')
|
|
176
|
+
expect(result.key).toBeTruthy()
|
|
177
|
+
expect(result.value).toBeNull()
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
test('should handle functions in keyData by converting to string', () => {
|
|
181
|
+
const testFunction = () => 'test'
|
|
182
|
+
const keyData = {
|
|
183
|
+
prop1: 'value1',
|
|
184
|
+
func: testFunction,
|
|
185
|
+
prop2: 'value2'
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const result = styleCache.keyFor('components', keyData)
|
|
189
|
+
|
|
190
|
+
expect(typeof result.key).toBe('string')
|
|
191
|
+
expect(result.key).toBeTruthy()
|
|
192
|
+
expect(result.value).toBeNull()
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
test('should return existing cached value after decompression', () => {
|
|
196
|
+
const keyData = { prop: 'value' }
|
|
197
|
+
const testValue = { result: 'data' }
|
|
198
|
+
|
|
199
|
+
// First, get the key that would be generated
|
|
200
|
+
const { key } = styleCache.keyFor('styles', keyData)
|
|
201
|
+
|
|
202
|
+
// Cache the value
|
|
203
|
+
styleCache.cacheFor('styles', key, testValue)
|
|
204
|
+
|
|
205
|
+
// Retrieve the cached value
|
|
206
|
+
const result = styleCache.keyFor('styles', keyData)
|
|
207
|
+
|
|
208
|
+
expect(result.key).toBe(key)
|
|
209
|
+
expect(result.value).toEqual(testValue)
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
test('should work with array keyData', () => {
|
|
213
|
+
const keyData = ['item1', 'item2', 'item3']
|
|
214
|
+
|
|
215
|
+
const result = styleCache.keyFor('responsive', keyData)
|
|
216
|
+
|
|
217
|
+
expect(typeof result.key).toBe('string')
|
|
218
|
+
expect(result.key).toBeTruthy()
|
|
219
|
+
expect(result.value).toBeNull()
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
test('should generate different keys for different keyData', () => {
|
|
223
|
+
const keyData1 = { prop: 'value1' }
|
|
224
|
+
const keyData2 = { prop: 'value2' }
|
|
225
|
+
|
|
226
|
+
const result1 = styleCache.keyFor('styles', keyData1)
|
|
227
|
+
const result2 = styleCache.keyFor('styles', keyData2)
|
|
228
|
+
|
|
229
|
+
expect(result1.key).not.toBe(result2.key)
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
test('should work with different cache types', () => {
|
|
233
|
+
const keyData = { test: 'data' }
|
|
234
|
+
const cacheTypes = ['styles', 'compositions', 'responsive', 'variants', 'common', 'components'] as const
|
|
235
|
+
|
|
236
|
+
const results = cacheTypes.map(cacheType => {
|
|
237
|
+
return styleCache.keyFor(cacheType, keyData)
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
// All should generate keys
|
|
241
|
+
results.forEach(result => {
|
|
242
|
+
expect(typeof result.key).toBe('string')
|
|
243
|
+
expect(result.key).toBeTruthy()
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
// Keys should be the same since baseKey and keyData are the same
|
|
247
|
+
const firstKey = results[0].key
|
|
248
|
+
results.forEach(result => {
|
|
249
|
+
expect(result.key).toBe(firstKey)
|
|
250
|
+
})
|
|
251
|
+
})
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
describe('cacheFor', () => {
|
|
255
|
+
beforeEach(() => {
|
|
256
|
+
styleCache.registerBaseKey(['test-app'])
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
test('should compress and cache value when CACHE_ENABLED is true', () => {
|
|
260
|
+
const testValue = { data: 'test', number: 42 }
|
|
261
|
+
|
|
262
|
+
const result = styleCache.cacheFor('styles', 'test-key', testValue)
|
|
263
|
+
|
|
264
|
+
// Should return compressed value
|
|
265
|
+
expect(typeof result).toBe('string')
|
|
266
|
+
|
|
267
|
+
// Should be stored in cache
|
|
268
|
+
expect(styleCache.styles.cache['test-key']).toBeDefined()
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
test('should work with different cache types', () => {
|
|
272
|
+
const testValue = { test: 'data' }
|
|
273
|
+
const cacheTypes = ['styles', 'compositions', 'responsive', 'variants', 'common', 'components'] as const
|
|
274
|
+
|
|
275
|
+
cacheTypes.forEach(cacheType => {
|
|
276
|
+
const result = styleCache.cacheFor(cacheType, `${cacheType}-key`, testValue)
|
|
277
|
+
|
|
278
|
+
expect(result).toBeDefined()
|
|
279
|
+
expect(styleCache[cacheType].cache[`${cacheType}-key`]).toBeDefined()
|
|
280
|
+
})
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
test('should handle various data types', () => {
|
|
284
|
+
const testCases = [
|
|
285
|
+
{ name: 'object', value: { prop: 'value' } },
|
|
286
|
+
{ name: 'array', value: [1, 2, 3] },
|
|
287
|
+
{ name: 'string', value: 'test string' },
|
|
288
|
+
{ name: 'number', value: 42 },
|
|
289
|
+
{ name: 'boolean', value: true },
|
|
290
|
+
]
|
|
291
|
+
|
|
292
|
+
testCases.forEach(({ name, value }) => {
|
|
293
|
+
const result = styleCache.cacheFor('styles', `key-${name}`, value)
|
|
294
|
+
expect(result).toBeDefined()
|
|
295
|
+
expect(styleCache.styles.cache[`key-${name}`]).toBeDefined()
|
|
296
|
+
})
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
test('should persist cache for persistent cache types', () => {
|
|
300
|
+
const testValue = { persistent: 'data' }
|
|
301
|
+
|
|
302
|
+
styleCache.cacheFor('variants', 'persistent-key', testValue)
|
|
303
|
+
|
|
304
|
+
// Should be stored in memory cache
|
|
305
|
+
expect(styleCache.variants.cache['persistent-key']).toBeDefined()
|
|
306
|
+
|
|
307
|
+
// Should also be persisted to storage
|
|
308
|
+
expect(mockStorage.size).toBeGreaterThan(0)
|
|
309
|
+
})
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
describe('Integration tests', () => {
|
|
313
|
+
test('should handle complete cache workflow', () => {
|
|
314
|
+
// 1. Register base key
|
|
315
|
+
const baseKey = styleCache.registerBaseKey(['my-app', 'v1.0'])
|
|
316
|
+
expect(styleCache.baseKey).toBe(baseKey)
|
|
317
|
+
|
|
318
|
+
// 2. Generate key and check for existing value (should be null)
|
|
319
|
+
const keyData = { component: 'Button', variant: 'primary', size: 'large' }
|
|
320
|
+
let result = styleCache.keyFor('components', keyData)
|
|
321
|
+
expect(result.value).toBeNull()
|
|
322
|
+
|
|
323
|
+
// 3. Cache a value
|
|
324
|
+
const testValue = {
|
|
325
|
+
backgroundColor: 'blue',
|
|
326
|
+
color: 'white',
|
|
327
|
+
padding: '12px 24px',
|
|
328
|
+
borderRadius: '4px'
|
|
329
|
+
}
|
|
330
|
+
const cachedResult = styleCache.cacheFor('components', result.key, testValue)
|
|
331
|
+
expect(cachedResult).toBeDefined()
|
|
332
|
+
|
|
333
|
+
// 4. Retrieve cached value (should now exist)
|
|
334
|
+
result = styleCache.keyFor('components', keyData)
|
|
335
|
+
expect(result.value).toEqual(testValue)
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
test('should maintain separate caches for different types', () => {
|
|
339
|
+
const keyData = { test: 'data' }
|
|
340
|
+
const stylesValue = { color: 'red' }
|
|
341
|
+
const componentsValue = { backgroundColor: 'blue' }
|
|
342
|
+
|
|
343
|
+
// Get keys for both cache types (should be the same)
|
|
344
|
+
const stylesResult = styleCache.keyFor('styles', keyData)
|
|
345
|
+
const componentsResult = styleCache.keyFor('components', keyData)
|
|
346
|
+
expect(stylesResult.key).toBe(componentsResult.key)
|
|
347
|
+
|
|
348
|
+
// Cache different values in each cache type
|
|
349
|
+
styleCache.cacheFor('styles', stylesResult.key, stylesValue)
|
|
350
|
+
styleCache.cacheFor('components', componentsResult.key, componentsValue)
|
|
351
|
+
|
|
352
|
+
// Retrieve values - should be different
|
|
353
|
+
const retrievedStyles = styleCache.keyFor('styles', keyData)
|
|
354
|
+
const retrievedComponents = styleCache.keyFor('components', keyData)
|
|
355
|
+
|
|
356
|
+
expect(retrievedStyles.value).toEqual(stylesValue)
|
|
357
|
+
expect(retrievedComponents.value).toEqual(componentsValue)
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
test('should handle cache persistence and restoration', () => {
|
|
361
|
+
// Cache some values in persistent caches
|
|
362
|
+
const variantsValue = { variant: 'primary' }
|
|
363
|
+
const commonValue = { common: 'styles' }
|
|
364
|
+
|
|
365
|
+
styleCache.cacheFor('variants', 'variants-key', variantsValue)
|
|
366
|
+
styleCache.cacheFor('common', 'common-key', commonValue)
|
|
367
|
+
|
|
368
|
+
// Verify persistence
|
|
369
|
+
expect(mockStorage.size).toBeGreaterThan(0)
|
|
370
|
+
|
|
371
|
+
// Create new StyleCache with same storage
|
|
372
|
+
const newStyleCache = new StyleCache(mockStorage)
|
|
373
|
+
|
|
374
|
+
// Values should be restored from storage
|
|
375
|
+
expect(Object.keys(newStyleCache.variants.cache).length).toBeGreaterThan(0)
|
|
376
|
+
expect(Object.keys(newStyleCache.common.cache).length).toBeGreaterThan(0)
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
test('should handle cache clearing and restoration', () => {
|
|
380
|
+
// Add data to all caches
|
|
381
|
+
const testValue = { data: 'test' }
|
|
382
|
+
|
|
383
|
+
styleCache.cacheFor('styles', 'styles-key', testValue)
|
|
384
|
+
styleCache.cacheFor('components', 'components-key', testValue)
|
|
385
|
+
styleCache.cacheFor('variants', 'variants-key', testValue)
|
|
386
|
+
|
|
387
|
+
// Verify data exists
|
|
388
|
+
expect(Object.keys(styleCache.styles.cache).length).toBeGreaterThan(0)
|
|
389
|
+
expect(Object.keys(styleCache.components.cache).length).toBeGreaterThan(0)
|
|
390
|
+
expect(Object.keys(styleCache.variants.cache).length).toBeGreaterThan(0)
|
|
391
|
+
expect(mockStorage.size).toBeGreaterThan(0)
|
|
392
|
+
|
|
393
|
+
// Clear all caches
|
|
394
|
+
styleCache.wipeCache()
|
|
395
|
+
|
|
396
|
+
// Verify everything is cleared
|
|
397
|
+
expect(Object.keys(styleCache.styles.cache)).toHaveLength(0)
|
|
398
|
+
expect(Object.keys(styleCache.components.cache)).toHaveLength(0)
|
|
399
|
+
expect(Object.keys(styleCache.variants.cache)).toHaveLength(0)
|
|
400
|
+
expect(mockStorage.size).toBe(0)
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
test('should handle complex keyData with nested objects and functions', () => {
|
|
404
|
+
styleCache.registerBaseKey(['complex-test'])
|
|
405
|
+
|
|
406
|
+
const complexKeyData = {
|
|
407
|
+
theme: {
|
|
408
|
+
primary: '#007bff',
|
|
409
|
+
secondary: '#6c757d',
|
|
410
|
+
breakpoints: ['sm', 'md', 'lg']
|
|
411
|
+
},
|
|
412
|
+
transform: (value: string) => value.toUpperCase(),
|
|
413
|
+
isActive: true,
|
|
414
|
+
index: 0
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Should handle complex data without errors
|
|
418
|
+
const result = styleCache.keyFor('styles', complexKeyData)
|
|
419
|
+
expect(typeof result.key).toBe('string')
|
|
420
|
+
expect(result.key).toBeTruthy()
|
|
421
|
+
|
|
422
|
+
// Should be able to cache and retrieve
|
|
423
|
+
const complexValue = {
|
|
424
|
+
computed: 'styles',
|
|
425
|
+
rules: ['rule1', 'rule2']
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
styleCache.cacheFor('styles', result.key, complexValue)
|
|
429
|
+
|
|
430
|
+
const retrieved = styleCache.keyFor('styles', complexKeyData)
|
|
431
|
+
expect(retrieved.value).toEqual(complexValue)
|
|
432
|
+
})
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
describe('Edge cases and error handling', () => {
|
|
436
|
+
test('should handle very large objects', () => {
|
|
437
|
+
styleCache.registerBaseKey(['large-objects'])
|
|
438
|
+
|
|
439
|
+
const largeObject: Record<string, any> = {}
|
|
440
|
+
for (let i = 0; i < 1000; i++) {
|
|
441
|
+
largeObject[`key${i}`] = `value${i}`
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const result = styleCache.keyFor('styles', largeObject)
|
|
445
|
+
expect(result.key).toBeTruthy()
|
|
446
|
+
|
|
447
|
+
styleCache.cacheFor('styles', result.key, largeObject)
|
|
448
|
+
const retrieved = styleCache.keyFor('styles', largeObject)
|
|
449
|
+
expect(retrieved.value).toEqual(largeObject)
|
|
450
|
+
})
|
|
451
|
+
})
|
|
452
|
+
})
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { test, expect, describe, beforeEach, jest, Mock } from 'bun:test'
|
|
2
|
+
import { StylePersistor, StoragePersistor } from '../StylePersistor'
|
|
3
|
+
import { minifier } from '../../tools'
|
|
4
|
+
|
|
5
|
+
interface MockStorage extends StoragePersistor {
|
|
6
|
+
set: Mock<(...args: any[]) => any>
|
|
7
|
+
get: Mock<(...args: any[]) => any>
|
|
8
|
+
del: Mock<(...args: any[]) => any>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe('StylePersistor', () => {
|
|
12
|
+
let mockStorage: MockStorage
|
|
13
|
+
let persistor: StylePersistor
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
mockStorage = {
|
|
17
|
+
set: jest.fn(),
|
|
18
|
+
get: jest.fn(),
|
|
19
|
+
del: jest.fn()
|
|
20
|
+
} as MockStorage
|
|
21
|
+
|
|
22
|
+
persistor = new StylePersistor(mockStorage)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
describe('setItem', () => {
|
|
26
|
+
test('should compress and store value using underlying storage', () => {
|
|
27
|
+
const testValue = 'test-value'
|
|
28
|
+
const compressedValue = minifier.compress(testValue)
|
|
29
|
+
|
|
30
|
+
persistor.setItem('test-key', testValue)
|
|
31
|
+
|
|
32
|
+
expect(mockStorage.set).toHaveBeenCalledWith('test-key', compressedValue)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('should handle empty string values with compression', () => {
|
|
36
|
+
const compressedEmpty = minifier.compress('')
|
|
37
|
+
|
|
38
|
+
persistor.setItem('empty', '')
|
|
39
|
+
|
|
40
|
+
expect(mockStorage.set).toHaveBeenCalledWith('empty', compressedEmpty)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('should handle special characters with compression', () => {
|
|
44
|
+
const value = 'value with spaces & symbols!'
|
|
45
|
+
const compressedValue = minifier.compress(value)
|
|
46
|
+
|
|
47
|
+
persistor.setItem('special/key:test', value)
|
|
48
|
+
|
|
49
|
+
expect(mockStorage.set).toHaveBeenCalledWith('special/key:test', compressedValue)
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe('getItem', () => {
|
|
54
|
+
test('should retrieve and decompress stored value', () => {
|
|
55
|
+
const originalData = 'test data for compression'
|
|
56
|
+
const compressedData = minifier.compress(originalData)
|
|
57
|
+
|
|
58
|
+
mockStorage.get.mockReturnValue(compressedData)
|
|
59
|
+
|
|
60
|
+
const result = persistor.getItem('test-key')
|
|
61
|
+
|
|
62
|
+
expect(mockStorage.get).toHaveBeenCalledWith('test-key')
|
|
63
|
+
expect(result).toBe(originalData)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('should return null when storage returns null', () => {
|
|
67
|
+
mockStorage.get.mockReturnValue(null)
|
|
68
|
+
|
|
69
|
+
const result = persistor.getItem('nonexistent-key')
|
|
70
|
+
|
|
71
|
+
expect(result).toBeNull()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('should return null when storage returns undefined', () => {
|
|
75
|
+
mockStorage.get.mockReturnValue(undefined)
|
|
76
|
+
|
|
77
|
+
const result = persistor.getItem('undefined-key')
|
|
78
|
+
|
|
79
|
+
expect(result).toBeNull()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('should handle various data types with compression/decompression', () => {
|
|
83
|
+
const testCases = [
|
|
84
|
+
'simple string',
|
|
85
|
+
'{"json": "data"}',
|
|
86
|
+
'',
|
|
87
|
+
'special chars: áéíóú!@#$%',
|
|
88
|
+
'12345'
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
testCases.forEach(testData => {
|
|
92
|
+
const compressedData = minifier.compress(testData)
|
|
93
|
+
mockStorage.get.mockReturnValue(compressedData)
|
|
94
|
+
|
|
95
|
+
const result = persistor.getItem('test-key')
|
|
96
|
+
expect(result).toBe(testData)
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('should return null when decompression fails', () => {
|
|
101
|
+
const invalidData = 'invalid compressed data'
|
|
102
|
+
mockStorage.get.mockReturnValue(invalidData)
|
|
103
|
+
|
|
104
|
+
const originalDecompress = minifier.decompress
|
|
105
|
+
minifier.decompress = jest.fn().mockReturnValue(null)
|
|
106
|
+
|
|
107
|
+
const result = persistor.getItem('invalid-key')
|
|
108
|
+
|
|
109
|
+
expect(result).toBeNull()
|
|
110
|
+
|
|
111
|
+
minifier.decompress = originalDecompress
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
describe('removeItem', () => {
|
|
116
|
+
test('should delete item using underlying storage', () => {
|
|
117
|
+
persistor.removeItem('test-key')
|
|
118
|
+
|
|
119
|
+
expect(mockStorage.del).toHaveBeenCalledWith('test-key')
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test('should handle removal of nonexistent keys', () => {
|
|
123
|
+
persistor.removeItem('nonexistent-key')
|
|
124
|
+
|
|
125
|
+
expect(mockStorage.del).toHaveBeenCalledWith('nonexistent-key')
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test('should handle special characters in key', () => {
|
|
129
|
+
persistor.removeItem('special/key:test')
|
|
130
|
+
|
|
131
|
+
expect(mockStorage.del).toHaveBeenCalledWith('special/key:test')
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
describe('integration with real minifier', () => {
|
|
136
|
+
test('should handle complete compression/decompression workflow', () => {
|
|
137
|
+
const testData = 'workflow test data'
|
|
138
|
+
const compressedData = minifier.compress(testData)
|
|
139
|
+
|
|
140
|
+
persistor.setItem('workflow-test', testData)
|
|
141
|
+
expect(mockStorage.set).toHaveBeenCalledWith('workflow-test', compressedData)
|
|
142
|
+
|
|
143
|
+
mockStorage.get.mockReturnValue(compressedData)
|
|
144
|
+
const retrieved = persistor.getItem('workflow-test')
|
|
145
|
+
expect(retrieved).toBe(testData)
|
|
146
|
+
|
|
147
|
+
persistor.removeItem('workflow-test')
|
|
148
|
+
expect(mockStorage.del).toHaveBeenCalledWith('workflow-test')
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test('should verify compression actually reduces size for large data', () => {
|
|
152
|
+
const largeData = 'a'.repeat(1000)
|
|
153
|
+
const compressedData = minifier.compress(largeData)
|
|
154
|
+
|
|
155
|
+
expect(compressedData.length).toBeLessThanOrEqual(largeData.length)
|
|
156
|
+
|
|
157
|
+
mockStorage.get.mockReturnValue(compressedData)
|
|
158
|
+
const result = persistor.getItem('large-data')
|
|
159
|
+
|
|
160
|
+
expect(result).toBe(largeData)
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
describe('error handling', () => {
|
|
165
|
+
test('should handle storage set errors', () => {
|
|
166
|
+
const errorStorage: MockStorage = {
|
|
167
|
+
set: jest.fn().mockImplementation(() => {
|
|
168
|
+
throw new Error('Storage set error')
|
|
169
|
+
}),
|
|
170
|
+
get: jest.fn(),
|
|
171
|
+
del: jest.fn()
|
|
172
|
+
} as MockStorage
|
|
173
|
+
|
|
174
|
+
const errorPersistor = new StylePersistor(errorStorage)
|
|
175
|
+
|
|
176
|
+
expect(() => errorPersistor.setItem('key', 'value')).toThrow('Storage set error')
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
test('should handle storage get errors', () => {
|
|
180
|
+
const errorStorage: MockStorage = {
|
|
181
|
+
set: jest.fn(),
|
|
182
|
+
get: jest.fn().mockImplementation(() => {
|
|
183
|
+
throw new Error('Storage get error')
|
|
184
|
+
}),
|
|
185
|
+
del: jest.fn()
|
|
186
|
+
} as MockStorage
|
|
187
|
+
|
|
188
|
+
const errorPersistor = new StylePersistor(errorStorage)
|
|
189
|
+
|
|
190
|
+
expect(() => errorPersistor.getItem('key')).toThrow('Storage get error')
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
test('should handle storage delete errors', () => {
|
|
194
|
+
const errorStorage: MockStorage = {
|
|
195
|
+
set: jest.fn(),
|
|
196
|
+
get: jest.fn(),
|
|
197
|
+
del: jest.fn().mockImplementation(() => {
|
|
198
|
+
throw new Error('Storage del error')
|
|
199
|
+
})
|
|
200
|
+
} as MockStorage
|
|
201
|
+
|
|
202
|
+
const errorPersistor = new StylePersistor(errorStorage)
|
|
203
|
+
|
|
204
|
+
expect(() => errorPersistor.removeItem('key')).toThrow('Storage del error')
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
test('should handle minifier compression errors', () => {
|
|
208
|
+
const originalCompress = minifier.compress
|
|
209
|
+
minifier.compress = jest.fn().mockImplementation(() => {
|
|
210
|
+
throw new Error('Compression error')
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
expect(() => persistor.setItem('key', 'value')).toThrow('Compression error')
|
|
214
|
+
|
|
215
|
+
minifier.compress = originalCompress
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
test('should handle minifier decompression errors gracefully', () => {
|
|
219
|
+
const originalDecompress = minifier.decompress
|
|
220
|
+
minifier.decompress = jest.fn().mockImplementation(() => {
|
|
221
|
+
throw new Error('Decompression error')
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
mockStorage.get.mockReturnValue('some data')
|
|
225
|
+
|
|
226
|
+
expect(() => persistor.getItem('error-key')).toThrow('Decompression error')
|
|
227
|
+
|
|
228
|
+
minifier.decompress = originalDecompress
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
})
|