@esportsplus/web-storage 0.3.5 → 0.5.0

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/tests/lz.ts ADDED
@@ -0,0 +1,324 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { compress, decompress } from '~/lz';
4
+
5
+
6
+ describe('LZ Compression', () => {
7
+
8
+ describe('empty/null handling', () => {
9
+ it('empty string round-trips', () => {
10
+ expect(compress('')).toBe('');
11
+ expect(decompress('')).toBe('');
12
+ });
13
+ });
14
+
15
+ describe('single characters', () => {
16
+ it('lowercase a', () => {
17
+ let compressed = compress('a');
18
+
19
+ expect(decompress(compressed)).toBe('a');
20
+ });
21
+
22
+ it('uppercase Z', () => {
23
+ let compressed = compress('Z');
24
+
25
+ expect(decompress(compressed)).toBe('Z');
26
+ });
27
+
28
+ it('null character (U+0000)', () => {
29
+ let compressed = compress('\0');
30
+
31
+ expect(decompress(compressed)).toBe('\0');
32
+ });
33
+ });
34
+
35
+ describe('short ASCII strings', () => {
36
+ it('hello world', () => {
37
+ let input = 'hello world',
38
+ compressed = compress(input);
39
+
40
+ expect(decompress(compressed)).toBe(input);
41
+ });
42
+
43
+ it('the quick brown fox', () => {
44
+ let input = 'The quick brown fox jumps over the lazy dog',
45
+ compressed = compress(input);
46
+
47
+ expect(decompress(compressed)).toBe(input);
48
+ });
49
+
50
+ it('numbers and symbols', () => {
51
+ let input = '0123456789!@#$%^&*()_+-=[]{}|;:,.<>?',
52
+ compressed = compress(input);
53
+
54
+ expect(decompress(compressed)).toBe(input);
55
+ });
56
+ });
57
+
58
+ describe('repetitive strings', () => {
59
+ it('abcabc repeated 500 times', () => {
60
+ let input = 'abc'.repeat(500),
61
+ compressed = compress(input);
62
+
63
+ expect(decompress(compressed)).toBe(input);
64
+ });
65
+
66
+ it('single char repeated 1000 times', () => {
67
+ let input = 'x'.repeat(1000),
68
+ compressed = compress(input);
69
+
70
+ expect(decompress(compressed)).toBe(input);
71
+ });
72
+
73
+ it('aaabbbccc pattern repeated', () => {
74
+ let input = 'aaabbbccc'.repeat(200),
75
+ compressed = compress(input);
76
+
77
+ expect(decompress(compressed)).toBe(input);
78
+ });
79
+ });
80
+
81
+ describe('JSON-like strings', () => {
82
+ it('1KB+ JSON string round-trips', () => {
83
+ let items: Record<string, unknown>[] = [];
84
+
85
+ for (let i = 0; i < 50; i++) {
86
+ items.push({
87
+ age: 20 + (i % 60),
88
+ id: i,
89
+ name: `user_${i}`,
90
+ tags: ['alpha', 'beta', 'gamma']
91
+ });
92
+ }
93
+
94
+ let input = JSON.stringify({ data: items, total: 50, type: 'users' }),
95
+ compressed = compress(input);
96
+
97
+ expect(input.length).toBeGreaterThan(1024);
98
+ expect(decompress(compressed)).toBe(input);
99
+ });
100
+
101
+ it('nested JSON structure', () => {
102
+ let input = JSON.stringify({
103
+ config: {
104
+ database: { host: 'localhost', port: 5432 },
105
+ features: { darkMode: true, notifications: false }
106
+ },
107
+ users: [
108
+ { email: 'alice@test.com', name: 'alice' },
109
+ { email: 'bob@test.com', name: 'bob' }
110
+ ]
111
+ });
112
+
113
+ let compressed = compress(input);
114
+
115
+ expect(decompress(compressed)).toBe(input);
116
+ });
117
+ });
118
+
119
+ describe('unicode', () => {
120
+ it('emoji characters', () => {
121
+ let input = '😀🎉🚀💯🔥✨',
122
+ compressed = compress(input);
123
+
124
+ expect(decompress(compressed)).toBe(input);
125
+ });
126
+
127
+ it('CJK characters', () => {
128
+ let input = '日本語テスト',
129
+ compressed = compress(input);
130
+
131
+ expect(decompress(compressed)).toBe(input);
132
+ });
133
+
134
+ it('mixed scripts', () => {
135
+ let input = 'Hello 世界! Привет мир! 🎉 café résumé naïve',
136
+ compressed = compress(input);
137
+
138
+ expect(decompress(compressed)).toBe(input);
139
+ });
140
+
141
+ it('arabic and hebrew', () => {
142
+ let input = 'مرحبا שלום',
143
+ compressed = compress(input);
144
+
145
+ expect(decompress(compressed)).toBe(input);
146
+ });
147
+ });
148
+
149
+ describe('byte value coverage', () => {
150
+ it('all 256 byte values individually', () => {
151
+ for (let i = 0; i < 256; i++) {
152
+ let input = String.fromCharCode(i),
153
+ compressed = compress(input);
154
+
155
+ expect(decompress(compressed)).toBe(input);
156
+ }
157
+ });
158
+
159
+ it('all 256 byte values concatenated', () => {
160
+ let chars: string[] = [];
161
+
162
+ for (let i = 0; i < 256; i++) {
163
+ chars.push(String.fromCharCode(i));
164
+ }
165
+
166
+ let input = chars.join(''),
167
+ compressed = compress(input);
168
+
169
+ expect(decompress(compressed)).toBe(input);
170
+ });
171
+ });
172
+
173
+ describe('compression ratio', () => {
174
+ it('1KB repetitive JSON compresses to < 50%', () => {
175
+ let items: Record<string, unknown>[] = [];
176
+
177
+ for (let i = 0; i < 50; i++) {
178
+ items.push({
179
+ active: true,
180
+ name: 'test_user',
181
+ role: 'admin',
182
+ score: 100
183
+ });
184
+ }
185
+
186
+ let input = JSON.stringify(items),
187
+ compressed = compress(input);
188
+
189
+ expect(input.length).toBeGreaterThan(1024);
190
+ expect(compressed.length).toBeLessThan(input.length * 0.5);
191
+ expect(decompress(compressed)).toBe(input);
192
+ });
193
+ });
194
+
195
+ describe('edge cases', () => {
196
+ it('two character string', () => {
197
+ let compressed = compress('ab');
198
+
199
+ expect(decompress(compressed)).toBe('ab');
200
+ });
201
+
202
+ it('three character string', () => {
203
+ let compressed = compress('abc');
204
+
205
+ expect(decompress(compressed)).toBe('abc');
206
+ });
207
+
208
+ it('string with only whitespace', () => {
209
+ let input = ' \t\n\r ',
210
+ compressed = compress(input);
211
+
212
+ expect(decompress(compressed)).toBe(input);
213
+ });
214
+
215
+ it('string with newlines', () => {
216
+ let input = 'line1\nline2\nline3\n',
217
+ compressed = compress(input);
218
+
219
+ expect(decompress(compressed)).toBe(input);
220
+ });
221
+
222
+ it('compressed output contains no null bytes', () => {
223
+ let inputs = [
224
+ 'hello world',
225
+ 'abc'.repeat(500),
226
+ JSON.stringify({ key: 'value' }),
227
+ '日本語テスト'
228
+ ];
229
+
230
+ for (let i = 0, n = inputs.length; i < n; i++) {
231
+ let compressed = compress(inputs[i]);
232
+
233
+ for (let j = 0, m = compressed.length; j < m; j++) {
234
+ expect(compressed.charCodeAt(j)).not.toBe(0);
235
+ }
236
+ }
237
+ });
238
+
239
+ it('binary-like string with high char codes', () => {
240
+ let chars: string[] = [];
241
+
242
+ for (let i = 0; i < 100; i++) {
243
+ chars.push(String.fromCharCode(Math.floor(Math.random() * 65535) + 1));
244
+ }
245
+
246
+ let input = chars.join(''),
247
+ compressed = compress(input);
248
+
249
+ expect(decompress(compressed)).toBe(input);
250
+ });
251
+ });
252
+
253
+ describe('boundary cases', () => {
254
+ it('very large string (~100KB) round-trips', () => {
255
+ let items: Record<string, unknown>[] = [];
256
+
257
+ for (let i = 0; i < 2000; i++) {
258
+ items.push({
259
+ data: 'x'.repeat(10),
260
+ id: i,
261
+ name: `entry_${i}`,
262
+ value: i * 3.14
263
+ });
264
+ }
265
+
266
+ let input = JSON.stringify(items);
267
+
268
+ expect(input.length).toBeGreaterThan(100_000);
269
+
270
+ let compressed = compress(input);
271
+
272
+ expect(decompress(compressed)).toBe(input);
273
+ });
274
+
275
+ it('high-entropy string that does not compress well', () => {
276
+ let chars: string[] = [];
277
+
278
+ for (let i = 0; i < 500; i++) {
279
+ chars.push(String.fromCharCode(32 + (((i * 7) + (i * i * 3)) % 95)));
280
+ }
281
+
282
+ let input = chars.join(''),
283
+ compressed = compress(input);
284
+
285
+ expect(decompress(compressed)).toBe(input);
286
+ });
287
+
288
+ it('exact bit-width boundary (2-bit to 3-bit transition)', () => {
289
+ // dictSize starts at 3, numBits at 2. After 2 new dictionary entries
290
+ // dictSize=5 > (1<<2)=4, triggering numBits bump to 3.
291
+ // 'abcd' has 4 unique chars; pattern 'abcdabcd' creates entries:
292
+ // 'ab'->3 (dictSize=4), 'bc'->4 (dictSize=5, triggers 2->3 bit transition)
293
+ let input = 'abcdabcd',
294
+ compressed = compress(input);
295
+
296
+ expect(decompress(compressed)).toBe(input);
297
+ });
298
+
299
+ it('single char repeated 2 times', () => {
300
+ let compressed = compress('aa');
301
+
302
+ expect(decompress(compressed)).toBe('aa');
303
+ });
304
+
305
+ it('single char repeated 3 times', () => {
306
+ let compressed = compress('aaa');
307
+
308
+ expect(decompress(compressed)).toBe('aaa');
309
+ });
310
+
311
+ it('single char repeated 4 times', () => {
312
+ let compressed = compress('aaaa');
313
+
314
+ expect(decompress(compressed)).toBe('aaaa');
315
+ });
316
+
317
+ it('surrogate pairs (mathematical bold fraktur)', () => {
318
+ let input = '𝕳𝖊𝖑𝖑𝖔',
319
+ compressed = compress(input);
320
+
321
+ expect(decompress(compressed)).toBe(input);
322
+ });
323
+ });
324
+ });
@@ -0,0 +1,16 @@
1
+ import { resolve } from 'node:path';
2
+ import { defineConfig } from 'vitest/config';
3
+
4
+
5
+ export default defineConfig({
6
+ resolve: {
7
+ alias: {
8
+ '~': resolve(__dirname, './src')
9
+ }
10
+ },
11
+ test: {
12
+ environment: 'happy-dom',
13
+ exclude: ['build/**', 'node_modules/**', 'storage/**'],
14
+ include: ['tests/**/*.ts']
15
+ }
16
+ });