@csszyx/dynamic 0.4.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/LICENSE +21 -0
- package/package.json +46 -0
- package/src/css-generator.ts +1003 -0
- package/src/index.ts +112 -0
- package/src/injector.ts +226 -0
- package/src/manifest.ts +119 -0
- package/src/react.ts +166 -0
- package/src/ssr.ts +5 -0
- package/tests/css-generator.test.ts +339 -0
- package/tests/injector.test.ts +222 -0
- package/tests/manifest.test.ts +177 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for manifest module.
|
|
3
|
+
* Verifies manifest loading, caching, and delta check logic.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
type CSSManifest,
|
|
10
|
+
ensureManifest,
|
|
11
|
+
isManifestLoaded,
|
|
12
|
+
lookupManifest,
|
|
13
|
+
preloadManifest,
|
|
14
|
+
resetManifest,
|
|
15
|
+
} from '../src/manifest.js';
|
|
16
|
+
|
|
17
|
+
// Mock fetch globally
|
|
18
|
+
const mockFetch = vi.fn();
|
|
19
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a complete CSSManifest fixture with sensible defaults for testing.
|
|
23
|
+
*
|
|
24
|
+
* @param partial - optional overrides to merge into the default manifest
|
|
25
|
+
* @returns a fully-formed CSSManifest object suitable for use in tests
|
|
26
|
+
*/
|
|
27
|
+
function makeMockManifest(partial: Partial<CSSManifest> = {}): CSSManifest {
|
|
28
|
+
return {
|
|
29
|
+
version: '0.4.0',
|
|
30
|
+
buildId: 'test-build-id',
|
|
31
|
+
classes: ['p-4', 'bg-blue-500', 'flex', 'text-white'],
|
|
32
|
+
...partial,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Configures the global fetch mock to resolve successfully with the given manifest.
|
|
38
|
+
*
|
|
39
|
+
* @param manifest - the CSSManifest object that the mocked fetch should return
|
|
40
|
+
* @returns void
|
|
41
|
+
*/
|
|
42
|
+
function setupFetchSuccess(manifest: CSSManifest): void {
|
|
43
|
+
mockFetch.mockResolvedValueOnce({
|
|
44
|
+
ok: true,
|
|
45
|
+
json: () => Promise.resolve(manifest),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe('manifest loading', () => {
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
resetManifest();
|
|
52
|
+
mockFetch.mockReset();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
resetManifest();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('isManifestLoaded returns false before loading', () => {
|
|
60
|
+
expect(isManifestLoaded()).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('lookupManifest returns null before manifest loads', () => {
|
|
64
|
+
expect(lookupManifest('p-4')).toBeNull();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('preloadManifest fetches and caches manifest', async () => {
|
|
68
|
+
setupFetchSuccess(makeMockManifest());
|
|
69
|
+
await preloadManifest('/csszyx-manifest.json');
|
|
70
|
+
|
|
71
|
+
expect(isManifestLoaded()).toBe(true);
|
|
72
|
+
expect(mockFetch).toHaveBeenCalledWith('/csszyx-manifest.json');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('uses custom URL passed to preloadManifest', async () => {
|
|
76
|
+
setupFetchSuccess(makeMockManifest());
|
|
77
|
+
await preloadManifest('/custom/path/manifest.json');
|
|
78
|
+
|
|
79
|
+
expect(mockFetch).toHaveBeenCalledWith('/custom/path/manifest.json');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('coalesces concurrent ensureManifest calls', async () => {
|
|
83
|
+
setupFetchSuccess(makeMockManifest());
|
|
84
|
+
|
|
85
|
+
// Trigger two concurrent loads
|
|
86
|
+
const p1 = ensureManifest();
|
|
87
|
+
const p2 = ensureManifest();
|
|
88
|
+
await Promise.all([p1, p2]);
|
|
89
|
+
|
|
90
|
+
// Only one fetch should have been made
|
|
91
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('gracefully handles fetch failure — treats all classes as new', async () => {
|
|
95
|
+
mockFetch.mockRejectedValueOnce(new Error('network error'));
|
|
96
|
+
await ensureManifest();
|
|
97
|
+
|
|
98
|
+
expect(isManifestLoaded()).toBe(true); // loaded (empty fallback)
|
|
99
|
+
expect(lookupManifest('p-4')).toBeNull(); // not in empty set
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('gracefully handles non-ok response', async () => {
|
|
103
|
+
mockFetch.mockResolvedValueOnce({ ok: false, status: 404 });
|
|
104
|
+
await ensureManifest();
|
|
105
|
+
|
|
106
|
+
expect(isManifestLoaded()).toBe(true);
|
|
107
|
+
expect(lookupManifest('p-4')).toBeNull();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('manifest delta check', () => {
|
|
112
|
+
beforeEach(async () => {
|
|
113
|
+
resetManifest();
|
|
114
|
+
mockFetch.mockReset();
|
|
115
|
+
setupFetchSuccess(makeMockManifest());
|
|
116
|
+
await preloadManifest();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
afterEach(() => {
|
|
120
|
+
resetManifest();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('returns original class name for class in manifest (no mangle map)', () => {
|
|
124
|
+
expect(lookupManifest('p-4')).toBe('p-4');
|
|
125
|
+
expect(lookupManifest('bg-blue-500')).toBe('bg-blue-500');
|
|
126
|
+
expect(lookupManifest('flex')).toBe('flex');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('returns null for class not in manifest', () => {
|
|
130
|
+
expect(lookupManifest('p-7')).toBeNull(); // not in test manifest
|
|
131
|
+
expect(lookupManifest('bg-red-300')).toBeNull();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('returns mangled name when mangle map is present', async () => {
|
|
135
|
+
resetManifest();
|
|
136
|
+
mockFetch.mockReset();
|
|
137
|
+
setupFetchSuccess(makeMockManifest({
|
|
138
|
+
classes: ['p-4', 'bg-blue-500'],
|
|
139
|
+
mangleMap: { 'p-4': 'z', 'bg-blue-500': 'y' },
|
|
140
|
+
}));
|
|
141
|
+
await preloadManifest();
|
|
142
|
+
|
|
143
|
+
expect(lookupManifest('p-4')).toBe('z');
|
|
144
|
+
expect(lookupManifest('bg-blue-500')).toBe('y');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('returns original name for class in manifest but not in mangle map', async () => {
|
|
148
|
+
resetManifest();
|
|
149
|
+
mockFetch.mockReset();
|
|
150
|
+
setupFetchSuccess(makeMockManifest({
|
|
151
|
+
classes: ['p-4', 'flex'],
|
|
152
|
+
mangleMap: { 'p-4': 'z' }, // 'flex' not in mangle map
|
|
153
|
+
}));
|
|
154
|
+
await preloadManifest();
|
|
155
|
+
|
|
156
|
+
expect(lookupManifest('p-4')).toBe('z');
|
|
157
|
+
expect(lookupManifest('flex')).toBe('flex'); // original name
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('resetManifest', () => {
|
|
162
|
+
it('clears loaded state and allows re-fetch', async () => {
|
|
163
|
+
setupFetchSuccess(makeMockManifest());
|
|
164
|
+
await preloadManifest();
|
|
165
|
+
expect(isManifestLoaded()).toBe(true);
|
|
166
|
+
|
|
167
|
+
resetManifest();
|
|
168
|
+
expect(isManifestLoaded()).toBe(false);
|
|
169
|
+
expect(lookupManifest('p-4')).toBeNull();
|
|
170
|
+
|
|
171
|
+
// Can reload after reset
|
|
172
|
+
setupFetchSuccess(makeMockManifest({ classes: ['new-class'] }));
|
|
173
|
+
await preloadManifest();
|
|
174
|
+
expect(lookupManifest('new-class')).toBe('new-class');
|
|
175
|
+
expect(lookupManifest('p-4')).toBeNull(); // old class gone
|
|
176
|
+
});
|
|
177
|
+
});
|
package/tsconfig.json
ADDED