@useavalon/avalon 0.1.4 → 0.1.6
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 +31 -58
- package/src/vite-plugin/plugin.ts +22 -4
- package/src/build/README.md +0 -310
- package/src/client/tests/css-hmr-handler.test.ts +0 -360
- package/src/client/tests/framework-adapter.test.ts +0 -519
- package/src/client/tests/hmr-coordinator.test.ts +0 -176
- package/src/client/tests/hydration-option-parsing.test.ts +0 -107
- package/src/client/tests/lit-adapter.test.ts +0 -427
- package/src/client/tests/preact-adapter.test.ts +0 -353
- package/src/client/tests/qwik-adapter.test.ts +0 -343
- package/src/client/tests/react-adapter.test.ts +0 -317
- package/src/client/tests/solid-adapter.test.ts +0 -396
- package/src/client/tests/svelte-adapter.test.ts +0 -387
- package/src/client/tests/vue-adapter.test.ts +0 -407
- package/src/components/tests/component-analyzer.test.ts +0 -96
- package/src/components/tests/component-detection.test.ts +0 -347
- package/src/components/tests/persistent-islands.test.ts +0 -398
- package/src/core/components/tests/enhanced-framework-detector.test.ts +0 -577
- package/src/core/components/tests/framework-registry.test.ts +0 -465
- package/src/core/integrations/README.md +0 -282
- package/src/core/layout/tests/enhanced-layout-resolver.test.ts +0 -477
- package/src/core/layout/tests/layout-cache-optimization.test.ts +0 -149
- package/src/core/layout/tests/layout-composer.test.ts +0 -486
- package/src/core/layout/tests/layout-data-loader.test.ts +0 -443
- package/src/core/layout/tests/layout-discovery.test.ts +0 -253
- package/src/core/layout/tests/layout-matcher.test.ts +0 -480
- package/src/core/modules/tests/framework-module-resolver.test.ts +0 -263
- package/src/core/modules/tests/module-resolution-integration.test.ts +0 -117
- package/src/islands/discovery/tests/island-discovery.test.ts +0 -881
- package/src/middleware/__tests__/discovery.test.ts +0 -107
- package/src/types/tests/layout-types.test.ts +0 -197
- package/src/vite-plugin/tests/image-optimization.test.ts +0 -54
|
@@ -1,353 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for Preact HMR Adapter
|
|
3
|
-
*
|
|
4
|
-
* Verifies Preact-specific HMR functionality including:
|
|
5
|
-
* - Component detection
|
|
6
|
-
* - State preservation
|
|
7
|
-
* - HMR integration
|
|
8
|
-
* - Error handling
|
|
9
|
-
*
|
|
10
|
-
* Requirements: 2.2
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { describe, it, expect } from 'vitest';
|
|
14
|
-
import { PreactHMRAdapter } from '../adapters/preact-adapter.ts';
|
|
15
|
-
import type { StateSnapshot } from '../framework-adapter.ts';
|
|
16
|
-
|
|
17
|
-
// Mock HTMLElement for testing
|
|
18
|
-
class MockHTMLElement {
|
|
19
|
-
private attributes: Map<string, string> = new Map();
|
|
20
|
-
private _children: MockHTMLElement[] = [];
|
|
21
|
-
public scrollTop = 0;
|
|
22
|
-
public scrollLeft = 0;
|
|
23
|
-
public style: Record<string, string> = {};
|
|
24
|
-
|
|
25
|
-
getAttribute(name: string): string | null {
|
|
26
|
-
return this.attributes.get(name) || null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
setAttribute(name: string, value: string): void {
|
|
30
|
-
this.attributes.set(name, value);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
removeAttribute(name: string): void {
|
|
34
|
-
this.attributes.delete(name);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
hasAttribute(name: string): boolean {
|
|
38
|
-
return this.attributes.has(name);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
querySelector(selector: string): MockHTMLElement | null {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
querySelectorAll(selector: string): MockHTMLElement[] {
|
|
46
|
-
return [];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
contains(element: unknown): boolean {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
insertBefore(newNode: unknown, referenceNode: unknown): void {
|
|
54
|
-
// Mock implementation
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
get firstChild(): unknown {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
dispatchEvent(event: any): boolean {
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
get innerHTML(): string {
|
|
66
|
-
return '';
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
set innerHTML(value: string) {
|
|
70
|
-
// Mock implementation
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Mock Preact components for testing
|
|
75
|
-
function MockFunctionComponent(props: Record<string, unknown>) {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
class MockClassComponent {
|
|
80
|
-
isReactComponent = true; // Preact uses same marker as React for compatibility
|
|
81
|
-
|
|
82
|
-
render() {
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const MockPreactVNode = {
|
|
88
|
-
$typeof: Symbol.for('react.element'), // Preact uses React's symbol for compatibility
|
|
89
|
-
type: MockFunctionComponent,
|
|
90
|
-
props: {},
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
describe('PreactHMRAdapter - initialization', () => {
|
|
94
|
-
it('should create adapter with correct name', () => {
|
|
95
|
-
const adapter = new PreactHMRAdapter();
|
|
96
|
-
|
|
97
|
-
expect(adapter).toBeDefined();
|
|
98
|
-
expect(adapter.name).toBe('preact');
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
describe('PreactHMRAdapter - canHandle', () => {
|
|
103
|
-
it('should handle function component', () => {
|
|
104
|
-
const adapter = new PreactHMRAdapter();
|
|
105
|
-
|
|
106
|
-
const result = adapter.canHandle(MockFunctionComponent);
|
|
107
|
-
expect(result).toBe(true);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('should handle class component', () => {
|
|
111
|
-
const adapter = new PreactHMRAdapter();
|
|
112
|
-
|
|
113
|
-
const result = adapter.canHandle(MockClassComponent);
|
|
114
|
-
expect(result).toBe(true);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('should handle Preact VNode', () => {
|
|
118
|
-
const adapter = new PreactHMRAdapter();
|
|
119
|
-
|
|
120
|
-
const result = adapter.canHandle(MockPreactVNode);
|
|
121
|
-
expect(result).toBe(true);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('should handle arrow function', () => {
|
|
125
|
-
const adapter = new PreactHMRAdapter();
|
|
126
|
-
const ArrowComponent = () => null;
|
|
127
|
-
|
|
128
|
-
const result = adapter.canHandle(ArrowComponent);
|
|
129
|
-
expect(result).toBe(true);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('should not handle non-Preact component', () => {
|
|
133
|
-
const adapter = new PreactHMRAdapter();
|
|
134
|
-
|
|
135
|
-
expect(adapter.canHandle(null)).toBe(false);
|
|
136
|
-
expect(adapter.canHandle(undefined)).toBe(false);
|
|
137
|
-
expect(adapter.canHandle('string')).toBe(false);
|
|
138
|
-
expect(adapter.canHandle(123)).toBe(false);
|
|
139
|
-
expect(adapter.canHandle({})).toBe(false);
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
describe('PreactHMRAdapter - preserveState', () => {
|
|
144
|
-
it('should return valid snapshot', () => {
|
|
145
|
-
const adapter = new PreactHMRAdapter();
|
|
146
|
-
const mockIsland = new MockHTMLElement() as unknown as HTMLElement;
|
|
147
|
-
|
|
148
|
-
// Set up mock island attributes
|
|
149
|
-
mockIsland.setAttribute('data-src', '/islands/TestComponent.tsx');
|
|
150
|
-
mockIsland.setAttribute('data-props', JSON.stringify({ count: 5 }));
|
|
151
|
-
|
|
152
|
-
const snapshot = adapter.preserveState(mockIsland);
|
|
153
|
-
|
|
154
|
-
// In test environment without DOM, preserveState returns null
|
|
155
|
-
// This is expected behavior - the adapter gracefully handles missing DOM
|
|
156
|
-
if (snapshot) {
|
|
157
|
-
expect(snapshot.framework).toBe('preact');
|
|
158
|
-
expect(typeof snapshot.timestamp).toBe('number');
|
|
159
|
-
expect(snapshot.data).toBeDefined();
|
|
160
|
-
} else {
|
|
161
|
-
// Graceful degradation when DOM is not available
|
|
162
|
-
expect(snapshot).toBeNull();
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('should capture component name', () => {
|
|
167
|
-
const adapter = new PreactHMRAdapter();
|
|
168
|
-
const mockIsland = new MockHTMLElement() as unknown as HTMLElement;
|
|
169
|
-
|
|
170
|
-
mockIsland.setAttribute('data-src', '/islands/PreactCounter.tsx');
|
|
171
|
-
mockIsland.setAttribute('data-props', '{}');
|
|
172
|
-
|
|
173
|
-
const snapshot = adapter.preserveState(mockIsland);
|
|
174
|
-
|
|
175
|
-
// In test environment without DOM, preserveState returns null
|
|
176
|
-
if (snapshot) {
|
|
177
|
-
expect(snapshot.data.componentName).toBe('PreactCounter');
|
|
178
|
-
} else {
|
|
179
|
-
// Expected in test environment
|
|
180
|
-
expect(snapshot).toBeNull();
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it('should capture props', () => {
|
|
185
|
-
const adapter = new PreactHMRAdapter();
|
|
186
|
-
const mockIsland = new MockHTMLElement() as unknown as HTMLElement;
|
|
187
|
-
|
|
188
|
-
const props = { count: 10, name: 'test' };
|
|
189
|
-
mockIsland.setAttribute('data-src', '/islands/TestComponent.tsx');
|
|
190
|
-
mockIsland.setAttribute('data-props', JSON.stringify(props));
|
|
191
|
-
|
|
192
|
-
const snapshot = adapter.preserveState(mockIsland);
|
|
193
|
-
|
|
194
|
-
// In test environment without DOM, preserveState returns null
|
|
195
|
-
if (snapshot) {
|
|
196
|
-
expect(snapshot.data.capturedProps).toBeDefined();
|
|
197
|
-
expect(snapshot.data.capturedProps).toEqual(props);
|
|
198
|
-
} else {
|
|
199
|
-
// Expected in test environment
|
|
200
|
-
expect(snapshot).toBeNull();
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('should handle missing props', () => {
|
|
205
|
-
const adapter = new PreactHMRAdapter();
|
|
206
|
-
const mockIsland = new MockHTMLElement() as unknown as HTMLElement;
|
|
207
|
-
|
|
208
|
-
mockIsland.setAttribute('data-src', '/islands/TestComponent.tsx');
|
|
209
|
-
// No data-props attribute
|
|
210
|
-
|
|
211
|
-
const snapshot = adapter.preserveState(mockIsland);
|
|
212
|
-
|
|
213
|
-
// In test environment without DOM, preserveState returns null
|
|
214
|
-
if (snapshot) {
|
|
215
|
-
expect(snapshot.data.capturedProps).toBeDefined();
|
|
216
|
-
expect(Object.keys(snapshot.data.capturedProps || {}).length).toBe(0);
|
|
217
|
-
} else {
|
|
218
|
-
// Expected in test environment
|
|
219
|
-
expect(snapshot).toBeNull();
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it('should handle invalid JSON props', () => {
|
|
224
|
-
const adapter = new PreactHMRAdapter();
|
|
225
|
-
const mockIsland = new MockHTMLElement() as unknown as HTMLElement;
|
|
226
|
-
|
|
227
|
-
mockIsland.setAttribute('data-src', '/islands/TestComponent.tsx');
|
|
228
|
-
mockIsland.setAttribute('data-props', 'invalid json');
|
|
229
|
-
|
|
230
|
-
const snapshot = adapter.preserveState(mockIsland);
|
|
231
|
-
|
|
232
|
-
// Should return null on error
|
|
233
|
-
expect(snapshot).toBeNull();
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
describe('PreactHMRAdapter - restoreState', () => {
|
|
238
|
-
it('should call base implementation', () => {
|
|
239
|
-
const adapter = new PreactHMRAdapter();
|
|
240
|
-
const mockIsland = new MockHTMLElement() as unknown as HTMLElement;
|
|
241
|
-
|
|
242
|
-
const snapshot: StateSnapshot = {
|
|
243
|
-
framework: 'preact',
|
|
244
|
-
timestamp: Date.now(),
|
|
245
|
-
data: {},
|
|
246
|
-
dom: {
|
|
247
|
-
scrollPosition: { x: 50, y: 100 },
|
|
248
|
-
},
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
// Should not throw
|
|
252
|
-
adapter.restoreState(mockIsland, snapshot);
|
|
253
|
-
|
|
254
|
-
// Verify DOM state was restored
|
|
255
|
-
expect(mockIsland.scrollLeft).toBe(50);
|
|
256
|
-
expect(mockIsland.scrollTop).toBe(100);
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
describe('PreactHMRAdapter - handleError', () => {
|
|
261
|
-
it('should add Preact-specific error info', () => {
|
|
262
|
-
const adapter = new PreactHMRAdapter();
|
|
263
|
-
const mockIsland = new MockHTMLElement() as unknown as HTMLElement;
|
|
264
|
-
|
|
265
|
-
const error = new Error('Invalid hook call');
|
|
266
|
-
|
|
267
|
-
// In test environment without DOM, handleError may fail
|
|
268
|
-
// This is expected - the adapter requires DOM APIs
|
|
269
|
-
try {
|
|
270
|
-
adapter.handleError(mockIsland, error);
|
|
271
|
-
|
|
272
|
-
// If it succeeds, verify error attributes were set
|
|
273
|
-
expect(mockIsland.getAttribute('data-hmr-error')).toBe('true');
|
|
274
|
-
expect(mockIsland.getAttribute('data-hmr-error-message')).toBe('Invalid hook call');
|
|
275
|
-
} catch (e) {
|
|
276
|
-
// Expected in test environment without DOM
|
|
277
|
-
// The adapter gracefully handles missing DOM APIs
|
|
278
|
-
// Accept any error - the important thing is it doesn't crash silently
|
|
279
|
-
expect(e).toBeDefined();
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it('should provide hooks hint', () => {
|
|
284
|
-
const adapter = new PreactHMRAdapter();
|
|
285
|
-
const mockIsland = new MockHTMLElement() as unknown as HTMLElement;
|
|
286
|
-
|
|
287
|
-
const error = new Error('Hooks can only be called inside the body of a function component');
|
|
288
|
-
|
|
289
|
-
// The adapter should recognize hooks-related errors and provide helpful hints
|
|
290
|
-
// This is tested indirectly through the error message
|
|
291
|
-
try {
|
|
292
|
-
adapter.handleError(mockIsland, error);
|
|
293
|
-
} catch (e) {
|
|
294
|
-
// Expected in test environment
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// The actual hint is added to the error indicator element
|
|
298
|
-
// which requires full DOM support to test properly
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
describe('PreactHMRAdapter - extractComponentName', () => {
|
|
303
|
-
it('should extract from various paths', () => {
|
|
304
|
-
const adapter = new PreactHMRAdapter();
|
|
305
|
-
|
|
306
|
-
// Access private method through type assertion for testing
|
|
307
|
-
const extractName = (adapter as any).extractComponentName.bind(adapter);
|
|
308
|
-
|
|
309
|
-
expect(extractName('/islands/PreactCounter.tsx')).toBe('PreactCounter');
|
|
310
|
-
expect(extractName('/islands/Button.jsx')).toBe('Button');
|
|
311
|
-
expect(extractName('/src/components/Card.ts')).toBe('Card');
|
|
312
|
-
expect(extractName('/nested/path/Component.js')).toBe('Component');
|
|
313
|
-
expect(extractName('SimpleComponent.tsx')).toBe('SimpleComponent');
|
|
314
|
-
});
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
describe('PreactHMRAdapter - unmount', () => {
|
|
318
|
-
it('should clear instance', () => {
|
|
319
|
-
const adapter = new PreactHMRAdapter();
|
|
320
|
-
const mockIsland = new MockHTMLElement() as unknown as HTMLElement;
|
|
321
|
-
|
|
322
|
-
// Store a mock instance
|
|
323
|
-
(adapter as any).instances.set(mockIsland, MockFunctionComponent);
|
|
324
|
-
|
|
325
|
-
// Unmount should clear the instance
|
|
326
|
-
adapter.unmount(mockIsland);
|
|
327
|
-
|
|
328
|
-
// Verify instance was removed
|
|
329
|
-
const hasInstance = (adapter as any).instances.has(mockIsland);
|
|
330
|
-
expect(hasInstance).toBe(false);
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
it('should handle missing instance gracefully', () => {
|
|
334
|
-
const adapter = new PreactHMRAdapter();
|
|
335
|
-
const mockIsland = new MockHTMLElement() as unknown as HTMLElement;
|
|
336
|
-
|
|
337
|
-
// Should not throw when unmounting island without instance
|
|
338
|
-
adapter.unmount(mockIsland);
|
|
339
|
-
|
|
340
|
-
// No assertion needed - just verify it doesn't throw
|
|
341
|
-
});
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
describe('PreactHMRAdapter - singleton instance', () => {
|
|
345
|
-
it('should export singleton', async () => {
|
|
346
|
-
// Import the singleton
|
|
347
|
-
const { preactAdapter } = await import('../adapters/preact-adapter.ts');
|
|
348
|
-
|
|
349
|
-
expect(preactAdapter).toBeDefined();
|
|
350
|
-
expect(preactAdapter.name).toBe('preact');
|
|
351
|
-
expect(preactAdapter instanceof PreactHMRAdapter).toBe(true);
|
|
352
|
-
});
|
|
353
|
-
});
|
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for Qwik HMR Adapter
|
|
3
|
-
*
|
|
4
|
-
* Verifies Qwik-specific HMR functionality including:
|
|
5
|
-
* - Component detection (component$, QRL markers)
|
|
6
|
-
* - State preservation (container state, q: attributes)
|
|
7
|
-
* - Resumability-aware update flow
|
|
8
|
-
* - Error handling with Qwik-specific hints
|
|
9
|
-
*
|
|
10
|
-
* Requirements: 2.1
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { describe, it, expect } from 'vitest';
|
|
14
|
-
import { QwikHMRAdapter } from '../adapters/qwik-adapter.ts';
|
|
15
|
-
import type { StateSnapshot } from '../framework-adapter.ts';
|
|
16
|
-
|
|
17
|
-
// Mock HTMLElement for testing
|
|
18
|
-
class MockHTMLElement {
|
|
19
|
-
private attributes: Map<string, string> = new Map();
|
|
20
|
-
private _children: MockHTMLElement[] = [];
|
|
21
|
-
public scrollTop = 0;
|
|
22
|
-
public scrollLeft = 0;
|
|
23
|
-
public style: Record<string, string> = {};
|
|
24
|
-
public dataset: Record<string, string> = {};
|
|
25
|
-
|
|
26
|
-
getAttribute(name: string): string | null {
|
|
27
|
-
return this.attributes.get(name) || null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
setAttribute(name: string, value: string): void {
|
|
31
|
-
this.attributes.set(name, value);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
removeAttribute(name: string): void {
|
|
35
|
-
this.attributes.delete(name);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
hasAttribute(name: string): boolean {
|
|
39
|
-
return this.attributes.has(name);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
querySelector(selector: string): MockHTMLElement | null {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
querySelectorAll(selector: string): MockHTMLElement[] {
|
|
47
|
-
return [];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
closest(selector: string): MockHTMLElement | null {
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
contains(element: unknown): boolean {
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
insertBefore(newNode: unknown, referenceNode: unknown): void {
|
|
59
|
-
// Mock implementation
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
get firstChild(): unknown {
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
get children(): MockHTMLElement[] {
|
|
67
|
-
return this._children;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
dispatchEvent(event: any): boolean {
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Mock Qwik components for testing
|
|
76
|
-
function MockQwikComponent(props: Record<string, unknown>) {
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Add Qwik marker
|
|
81
|
-
(MockQwikComponent as any).__brand = 'QwikComponent';
|
|
82
|
-
|
|
83
|
-
function MockQwikComponentWithSignal(props: Record<string, unknown>) {
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
Object.defineProperty(MockQwikComponentWithSignal, 'toString', {
|
|
88
|
-
value: () => 'function() { useSignal(0); }',
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
function MockQwikComponentWithStore(props: Record<string, unknown>) {
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
Object.defineProperty(MockQwikComponentWithStore, 'toString', {
|
|
96
|
-
value: () => 'function() { useStore({ count: 0 }); }',
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
function MockQwikComponentWithTask(props: Record<string, unknown>) {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
Object.defineProperty(MockQwikComponentWithTask, 'toString', {
|
|
104
|
-
value: () => 'function() { useTask$(() => {}); }',
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
// Mock QRL-wrapped component
|
|
108
|
-
function MockQRLComponent(props: Record<string, unknown>) {
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
(MockQRLComponent as any).__qrl = true;
|
|
112
|
-
(MockQRLComponent as any).getSymbol = () => 'MockQRLComponent';
|
|
113
|
-
(MockQRLComponent as any).getHash = () => 'abc123';
|
|
114
|
-
|
|
115
|
-
const MockQwikModule = {
|
|
116
|
-
default: MockQwikComponent,
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
describe('QwikHMRAdapter - initialization', () => {
|
|
120
|
-
it('should create adapter with correct name', () => {
|
|
121
|
-
const adapter = new QwikHMRAdapter();
|
|
122
|
-
|
|
123
|
-
expect(adapter).toBeDefined();
|
|
124
|
-
expect(adapter.name).toBe('qwik');
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
describe('QwikHMRAdapter - canHandle', () => {
|
|
129
|
-
it('should handle Qwik component with __brand marker', () => {
|
|
130
|
-
const adapter = new QwikHMRAdapter();
|
|
131
|
-
|
|
132
|
-
const result = adapter.canHandle(MockQwikComponent);
|
|
133
|
-
expect(result).toBe(true);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('should handle QRL-wrapped component', () => {
|
|
137
|
-
const adapter = new QwikHMRAdapter();
|
|
138
|
-
|
|
139
|
-
const result = adapter.canHandle(MockQRLComponent);
|
|
140
|
-
expect(result).toBe(true);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('should handle component with useSignal', () => {
|
|
144
|
-
const adapter = new QwikHMRAdapter();
|
|
145
|
-
|
|
146
|
-
const result = adapter.canHandle(MockQwikComponentWithSignal);
|
|
147
|
-
expect(result).toBe(true);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('should handle component with useStore', () => {
|
|
151
|
-
const adapter = new QwikHMRAdapter();
|
|
152
|
-
|
|
153
|
-
const result = adapter.canHandle(MockQwikComponentWithStore);
|
|
154
|
-
expect(result).toBe(true);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it('should handle component with useTask$', () => {
|
|
158
|
-
const adapter = new QwikHMRAdapter();
|
|
159
|
-
|
|
160
|
-
const result = adapter.canHandle(MockQwikComponentWithTask);
|
|
161
|
-
expect(result).toBe(true);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it('should handle module with default export', () => {
|
|
165
|
-
const adapter = new QwikHMRAdapter();
|
|
166
|
-
|
|
167
|
-
const result = adapter.canHandle(MockQwikModule);
|
|
168
|
-
expect(result).toBe(true);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('should handle object with __qrl marker', () => {
|
|
172
|
-
const adapter = new QwikHMRAdapter();
|
|
173
|
-
|
|
174
|
-
const result = adapter.canHandle({ __qrl: true });
|
|
175
|
-
expect(result).toBe(true);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('should not handle non-Qwik values', () => {
|
|
179
|
-
const adapter = new QwikHMRAdapter();
|
|
180
|
-
|
|
181
|
-
expect(adapter.canHandle(null)).toBe(false);
|
|
182
|
-
expect(adapter.canHandle(undefined)).toBe(false);
|
|
183
|
-
expect(adapter.canHandle('string')).toBe(false);
|
|
184
|
-
expect(adapter.canHandle(123)).toBe(false);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it('should not handle plain object without default or markers', () => {
|
|
188
|
-
const adapter = new QwikHMRAdapter();
|
|
189
|
-
|
|
190
|
-
const result = adapter.canHandle({ foo: 'bar' });
|
|
191
|
-
expect(result).toBe(false);
|
|
192
|
-
});
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
describe('QwikHMRAdapter - preserveState', () => {
|
|
196
|
-
it('should return valid snapshot with container state', () => {
|
|
197
|
-
const adapter = new QwikHMRAdapter();
|
|
198
|
-
const mockIsland = new MockHTMLElement() as unknown as HTMLElement;
|
|
199
|
-
|
|
200
|
-
mockIsland.setAttribute('data-src', '/islands/Counter.qwik.tsx');
|
|
201
|
-
mockIsland.setAttribute('data-props', JSON.stringify({ count: 5 }));
|
|
202
|
-
|
|
203
|
-
const snapshot = adapter.preserveState(mockIsland);
|
|
204
|
-
|
|
205
|
-
if (snapshot) {
|
|
206
|
-
expect(snapshot.framework).toBe('qwik');
|
|
207
|
-
expect(typeof snapshot.timestamp).toBe('number');
|
|
208
|
-
expect(snapshot.data).toBeDefined();
|
|
209
|
-
} else {
|
|
210
|
-
// Graceful degradation when DOM is not available
|
|
211
|
-
expect(snapshot).toBeNull();
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it('should capture component name from .qwik.tsx path', () => {
|
|
216
|
-
const adapter = new QwikHMRAdapter();
|
|
217
|
-
const mockIsland = new MockHTMLElement() as unknown as HTMLElement;
|
|
218
|
-
|
|
219
|
-
mockIsland.setAttribute('data-src', '/islands/Counter.qwik.tsx');
|
|
220
|
-
mockIsland.setAttribute('data-props', '{}');
|
|
221
|
-
|
|
222
|
-
const snapshot = adapter.preserveState(mockIsland);
|
|
223
|
-
|
|
224
|
-
if (snapshot) {
|
|
225
|
-
expect(snapshot.data.componentName).toBe('Counter');
|
|
226
|
-
} else {
|
|
227
|
-
expect(snapshot).toBeNull();
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it('should handle missing props', () => {
|
|
232
|
-
const adapter = new QwikHMRAdapter();
|
|
233
|
-
const mockIsland = new MockHTMLElement() as unknown as HTMLElement;
|
|
234
|
-
|
|
235
|
-
mockIsland.setAttribute('data-src', '/islands/TestComponent.qwik.tsx');
|
|
236
|
-
|
|
237
|
-
const snapshot = adapter.preserveState(mockIsland);
|
|
238
|
-
|
|
239
|
-
if (snapshot) {
|
|
240
|
-
expect(snapshot.data.capturedProps).toBeDefined();
|
|
241
|
-
expect(Object.keys(snapshot.data.capturedProps || {}).length).toBe(0);
|
|
242
|
-
} else {
|
|
243
|
-
expect(snapshot).toBeNull();
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
it('should handle invalid JSON props', () => {
|
|
248
|
-
const adapter = new QwikHMRAdapter();
|
|
249
|
-
const mockIsland = new MockHTMLElement() as unknown as HTMLElement;
|
|
250
|
-
|
|
251
|
-
mockIsland.setAttribute('data-src', '/islands/TestComponent.qwik.tsx');
|
|
252
|
-
mockIsland.setAttribute('data-props', 'invalid json');
|
|
253
|
-
|
|
254
|
-
const snapshot = adapter.preserveState(mockIsland);
|
|
255
|
-
expect(snapshot).toBeNull();
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
describe('QwikHMRAdapter - restoreState', () => {
|
|
260
|
-
it('should restore DOM state via base implementation', () => {
|
|
261
|
-
const adapter = new QwikHMRAdapter();
|
|
262
|
-
const mockIsland = new MockHTMLElement() as unknown as HTMLElement;
|
|
263
|
-
|
|
264
|
-
const snapshot: StateSnapshot = {
|
|
265
|
-
framework: 'qwik',
|
|
266
|
-
timestamp: Date.now(),
|
|
267
|
-
data: {},
|
|
268
|
-
dom: {
|
|
269
|
-
scrollPosition: { x: 50, y: 100 },
|
|
270
|
-
},
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
adapter.restoreState(mockIsland, snapshot);
|
|
274
|
-
|
|
275
|
-
expect(mockIsland.scrollLeft).toBe(50);
|
|
276
|
-
expect(mockIsland.scrollTop).toBe(100);
|
|
277
|
-
});
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
describe('QwikHMRAdapter - handleError', () => {
|
|
281
|
-
it('should handle component$ errors', () => {
|
|
282
|
-
const adapter = new QwikHMRAdapter();
|
|
283
|
-
const mockIsland = new MockHTMLElement() as unknown as HTMLElement;
|
|
284
|
-
|
|
285
|
-
const error = new Error('component$ is not defined');
|
|
286
|
-
|
|
287
|
-
try {
|
|
288
|
-
adapter.handleError(mockIsland, error);
|
|
289
|
-
expect(mockIsland.getAttribute('data-hmr-error')).toBe('true');
|
|
290
|
-
expect(mockIsland.getAttribute('data-hmr-error-message')).toBe('component$ is not defined');
|
|
291
|
-
} catch (e) {
|
|
292
|
-
// Expected in test environment without DOM
|
|
293
|
-
expect((e as Error).message.includes('document is not defined')).toBe(true);
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
it('should handle serialization errors', () => {
|
|
298
|
-
const adapter = new QwikHMRAdapter();
|
|
299
|
-
const mockIsland = new MockHTMLElement() as unknown as HTMLElement;
|
|
300
|
-
|
|
301
|
-
const error = new Error('Failed to serialize state');
|
|
302
|
-
|
|
303
|
-
try {
|
|
304
|
-
adapter.handleError(mockIsland, error);
|
|
305
|
-
} catch (e) {
|
|
306
|
-
// Expected in test environment
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
describe('QwikHMRAdapter - extractComponentName', () => {
|
|
312
|
-
it('should extract from various paths', () => {
|
|
313
|
-
const adapter = new QwikHMRAdapter();
|
|
314
|
-
|
|
315
|
-
const extractName = (adapter as any).extractComponentName.bind(adapter);
|
|
316
|
-
|
|
317
|
-
expect(extractName('/islands/Counter.qwik.tsx')).toBe('Counter');
|
|
318
|
-
expect(extractName('/islands/Button.qwik.jsx')).toBe('Button');
|
|
319
|
-
expect(extractName('/src/components/Card.tsx')).toBe('Card');
|
|
320
|
-
expect(extractName('/nested/path/Component.jsx')).toBe('Component');
|
|
321
|
-
expect(extractName('SimpleComponent.qwik.tsx')).toBe('SimpleComponent');
|
|
322
|
-
});
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
describe('QwikHMRAdapter - unmount', () => {
|
|
326
|
-
it('should handle unmount of untracked island', () => {
|
|
327
|
-
const adapter = new QwikHMRAdapter();
|
|
328
|
-
const mockIsland = new MockHTMLElement() as unknown as HTMLElement;
|
|
329
|
-
|
|
330
|
-
// Should not throw for untracked islands
|
|
331
|
-
expect(() => adapter.unmount(mockIsland)).not.toThrow();
|
|
332
|
-
});
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
describe('QwikHMRAdapter - singleton instance', () => {
|
|
336
|
-
it('should export singleton', async () => {
|
|
337
|
-
const { qwikAdapter } = await import('../adapters/qwik-adapter.ts');
|
|
338
|
-
|
|
339
|
-
expect(qwikAdapter).toBeDefined();
|
|
340
|
-
expect(qwikAdapter.name).toBe('qwik');
|
|
341
|
-
expect(qwikAdapter instanceof QwikHMRAdapter).toBe(true);
|
|
342
|
-
});
|
|
343
|
-
});
|