@flightdev/ui 2.0.0 → 4.0.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/README.md +285 -70
- package/dist/{chunk-XTDK7ME5.js → chunk-S4DTUQII.js} +246 -19
- package/dist/chunk-S4DTUQII.js.map +1 -0
- package/dist/core/index.d.ts +423 -3
- package/dist/core/index.js +23 -2
- package/dist/core/index.js.map +1 -0
- package/dist/index.d.ts +2 -3
- package/dist/index.js +29 -5
- package/dist/index.js.map +1 -0
- package/package.json +11 -181
- package/.turbo/turbo-build.log +0 -81
- package/.turbo/turbo-lint.log +0 -40
- package/.turbo/turbo-typecheck.log +0 -4
- package/TESTING.md +0 -124
- package/dist/adapter-MMD-iHNx.d.ts +0 -424
- package/dist/adapters/tier-1/angular.d.ts +0 -60
- package/dist/adapters/tier-1/angular.js +0 -2
- package/dist/adapters/tier-1/index.d.ts +0 -7
- package/dist/adapters/tier-1/index.js +0 -7
- package/dist/adapters/tier-1/qwik.d.ts +0 -55
- package/dist/adapters/tier-1/qwik.js +0 -2
- package/dist/adapters/tier-1/react.d.ts +0 -67
- package/dist/adapters/tier-1/react.js +0 -2
- package/dist/adapters/tier-1/solid.d.ts +0 -45
- package/dist/adapters/tier-1/solid.js +0 -2
- package/dist/adapters/tier-1/svelte.d.ts +0 -48
- package/dist/adapters/tier-1/svelte.js +0 -2
- package/dist/adapters/tier-1/vue.d.ts +0 -47
- package/dist/adapters/tier-1/vue.js +0 -2
- package/dist/adapters/tier-2/index.d.ts +0 -7
- package/dist/adapters/tier-2/index.js +0 -7
- package/dist/adapters/tier-2/inferno.d.ts +0 -31
- package/dist/adapters/tier-2/inferno.js +0 -2
- package/dist/adapters/tier-2/lit.d.ts +0 -34
- package/dist/adapters/tier-2/lit.js +0 -2
- package/dist/adapters/tier-2/marko.d.ts +0 -59
- package/dist/adapters/tier-2/marko.js +0 -2
- package/dist/adapters/tier-2/mithril.d.ts +0 -31
- package/dist/adapters/tier-2/mithril.js +0 -2
- package/dist/adapters/tier-2/preact.d.ts +0 -33
- package/dist/adapters/tier-2/preact.js +0 -2
- package/dist/adapters/tier-2/stencil.d.ts +0 -52
- package/dist/adapters/tier-2/stencil.js +0 -2
- package/dist/adapters/tier-3/alpine.d.ts +0 -73
- package/dist/adapters/tier-3/alpine.js +0 -2
- package/dist/adapters/tier-3/hotwire.d.ts +0 -71
- package/dist/adapters/tier-3/hotwire.js +0 -2
- package/dist/adapters/tier-3/htmx.d.ts +0 -88
- package/dist/adapters/tier-3/htmx.js +0 -2
- package/dist/adapters/tier-3/index.d.ts +0 -7
- package/dist/adapters/tier-3/index.js +0 -7
- package/dist/adapters/tier-3/petite-vue.d.ts +0 -56
- package/dist/adapters/tier-3/petite-vue.js +0 -2
- package/dist/adapters/tier-3/stimulus.d.ts +0 -63
- package/dist/adapters/tier-3/stimulus.js +0 -2
- package/dist/adapters/tier-3/vanilla.d.ts +0 -63
- package/dist/adapters/tier-3/vanilla.js +0 -2
- package/dist/chunk-2SNQ6PTM.js +0 -217
- package/dist/chunk-3D4XMIZI.js +0 -136
- package/dist/chunk-3HU6GSQ4.js +0 -125
- package/dist/chunk-4PZDNFL7.js +0 -148
- package/dist/chunk-5IBLFTYL.js +0 -114
- package/dist/chunk-64JZJ7OK.js +0 -142
- package/dist/chunk-7ZJI3QU2.js +0 -132
- package/dist/chunk-CE4FJHQJ.js +0 -133
- package/dist/chunk-DTCAUBH5.js +0 -87
- package/dist/chunk-NTASPOHG.js +0 -106
- package/dist/chunk-OI2AMQLG.js +0 -152
- package/dist/chunk-Q7HUE44H.js +0 -106
- package/dist/chunk-QH3LOWXU.js +0 -155
- package/dist/chunk-QIVAK6BH.js +0 -103
- package/dist/chunk-V34XPVGK.js +0 -103
- package/dist/chunk-VK7ZPMO7.js +0 -221
- package/dist/chunk-X6CNUW6T.js +0 -136
- package/dist/chunk-YFGSHW5S.js +0 -121
- package/dist/chunk-ZAJVSE7J.js +0 -90
- package/docs/ADAPTERS.md +0 -946
- package/docs/PATTERNS.md +0 -836
- package/src/adapters/tier-1/angular.ts +0 -223
- package/src/adapters/tier-1/index.ts +0 -12
- package/src/adapters/tier-1/qwik.ts +0 -177
- package/src/adapters/tier-1/react.ts +0 -330
- package/src/adapters/tier-1/solid.ts +0 -222
- package/src/adapters/tier-1/svelte.ts +0 -211
- package/src/adapters/tier-1/vue.ts +0 -234
- package/src/adapters/tier-2/index.ts +0 -12
- package/src/adapters/tier-2/inferno.ts +0 -149
- package/src/adapters/tier-2/lit.ts +0 -191
- package/src/adapters/tier-2/marko.ts +0 -199
- package/src/adapters/tier-2/mithril.ts +0 -152
- package/src/adapters/tier-2/preact.ts +0 -133
- package/src/adapters/tier-2/stencil.ts +0 -214
- package/src/adapters/tier-3/alpine.ts +0 -218
- package/src/adapters/tier-3/hotwire.ts +0 -254
- package/src/adapters/tier-3/htmx.ts +0 -263
- package/src/adapters/tier-3/index.ts +0 -12
- package/src/adapters/tier-3/petite-vue.ts +0 -163
- package/src/adapters/tier-3/stimulus.ts +0 -233
- package/src/adapters/tier-3/vanilla.ts +0 -252
- package/src/ambient.d.ts +0 -310
- package/src/core/adapter.ts +0 -366
- package/src/core/index.ts +0 -56
- package/src/core/registry.ts +0 -518
- package/src/core/types.ts +0 -461
- package/src/htmx.ts +0 -134
- package/src/index.ts +0 -263
- package/test/__mocks__/stencil-core.ts +0 -19
- package/test/__mocks__/stencil-hydrate.ts +0 -15
- package/test/adapters/tier-1.test.ts +0 -206
- package/test/adapters/tier-2.test.ts +0 -175
- package/test/adapters/tier-3.test.ts +0 -284
- package/test/contracts/adapter.contract.ts +0 -293
- package/test/core/core.test.ts +0 -310
- package/test/errors/error-handling.test.ts +0 -454
- package/test/integration/htmx.integration.test.ts +0 -246
- package/test/integration/react.integration.test.ts +0 -271
- package/test/integration/registry.integration.test.ts +0 -308
- package/tsconfig.json +0 -22
- package/tsup.config.ts +0 -93
- package/vitest.config.ts +0 -101
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @flightdev/ui - Tier 3 Adapter Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests for HTML-first adapters: HTMX, Alpine.js, Hotwire, Stimulus, Petite-vue, Vanilla.
|
|
5
|
-
* These adapters work differently - they render HTML templates, not components.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, it, expect } from 'vitest';
|
|
9
|
-
import { runAdapterContractTests } from '../contracts/adapter.contract.js';
|
|
10
|
-
|
|
11
|
-
// ============================================================================
|
|
12
|
-
// Tier 3 Adapter Imports
|
|
13
|
-
// ============================================================================
|
|
14
|
-
|
|
15
|
-
import { htmx, HTMXAdapter, hxGet, hxPost, hx } from '../../src/adapters/tier-3/htmx.js';
|
|
16
|
-
import { alpine, AlpineAdapter, xData, xFor, xIf } from '../../src/adapters/tier-3/alpine.js';
|
|
17
|
-
import { hotwire, HotwireAdapter, turboFrame, turboStream } from '../../src/adapters/tier-3/hotwire.js';
|
|
18
|
-
import { stimulus, StimulusAdapter } from '../../src/adapters/tier-3/stimulus.js';
|
|
19
|
-
import { petiteVue, PetiteVueAdapter, vScope } from '../../src/adapters/tier-3/petite-vue.js';
|
|
20
|
-
import { vanilla, VanillaAdapter } from '../../src/adapters/tier-3/vanilla.js';
|
|
21
|
-
|
|
22
|
-
// ============================================================================
|
|
23
|
-
// Contract Tests
|
|
24
|
-
// ============================================================================
|
|
25
|
-
|
|
26
|
-
describe('Tier 3 Adapters', () => {
|
|
27
|
-
// Tier 3 adapters can render HTML templates directly
|
|
28
|
-
runAdapterContractTests(() => htmx(), 'HTMX', { useHtmlComponent: true });
|
|
29
|
-
runAdapterContractTests(() => alpine(), 'Alpine', { useHtmlComponent: true });
|
|
30
|
-
runAdapterContractTests(() => hotwire(), 'Hotwire', { useHtmlComponent: true });
|
|
31
|
-
runAdapterContractTests(() => stimulus(), 'Stimulus', { useHtmlComponent: true });
|
|
32
|
-
runAdapterContractTests(() => petiteVue(), 'Petite-vue', { useHtmlComponent: true });
|
|
33
|
-
runAdapterContractTests(() => vanilla(), 'Vanilla', { useHtmlComponent: true });
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
// ============================================================================
|
|
37
|
-
// HTMX Specific Tests
|
|
38
|
-
// ============================================================================
|
|
39
|
-
|
|
40
|
-
describe('HTMX Adapter - Specific', () => {
|
|
41
|
-
it('creates adapter with default options', () => {
|
|
42
|
-
const adapter = htmx();
|
|
43
|
-
expect(adapter).toBeInstanceOf(HTMXAdapter);
|
|
44
|
-
expect(adapter.id).toBe('htmx');
|
|
45
|
-
expect(adapter.tier).toBe('tier-3');
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('does not support CSR (server-driven)', () => {
|
|
49
|
-
const adapter = htmx();
|
|
50
|
-
expect(adapter.capabilities.csr).toBe(false);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('accepts extensions option', () => {
|
|
54
|
-
const adapter = htmx({ extensions: ['json-enc', 'loading-states'] });
|
|
55
|
-
expect(adapter).toBeDefined();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('accepts version option', () => {
|
|
59
|
-
const adapter = htmx({ version: '2.0.0' });
|
|
60
|
-
expect(adapter).toBeDefined();
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe('HTMX Template Helpers', () => {
|
|
65
|
-
it('hxGet generates correct attributes', () => {
|
|
66
|
-
const attrs = hxGet('/api/data');
|
|
67
|
-
expect(attrs['hx-get']).toBe('/api/data');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('hxGet accepts options', () => {
|
|
71
|
-
const attrs = hxGet('/api/data', {
|
|
72
|
-
target: '#results',
|
|
73
|
-
swap: 'innerHTML',
|
|
74
|
-
trigger: 'click',
|
|
75
|
-
});
|
|
76
|
-
expect(attrs['hx-get']).toBe('/api/data');
|
|
77
|
-
expect(attrs['hx-target']).toBe('#results');
|
|
78
|
-
expect(attrs['hx-swap']).toBe('innerHTML');
|
|
79
|
-
expect(attrs['hx-trigger']).toBe('click');
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('hxPost generates correct attributes', () => {
|
|
83
|
-
const attrs = hxPost('/api/submit');
|
|
84
|
-
expect(attrs['hx-post']).toBe('/api/submit');
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('hx creates element string', () => {
|
|
88
|
-
const element = hx('button', { 'hx-get': '/api' }, 'Click me');
|
|
89
|
-
expect(element).toContain('<button');
|
|
90
|
-
expect(element).toContain('hx-get="/api"');
|
|
91
|
-
expect(element).toContain('Click me');
|
|
92
|
-
expect(element).toContain('</button>');
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// ============================================================================
|
|
97
|
-
// Alpine.js Specific Tests
|
|
98
|
-
// ============================================================================
|
|
99
|
-
|
|
100
|
-
describe('Alpine Adapter - Specific', () => {
|
|
101
|
-
it('creates adapter with default options', () => {
|
|
102
|
-
const adapter = alpine();
|
|
103
|
-
expect(adapter).toBeInstanceOf(AlpineAdapter);
|
|
104
|
-
expect(adapter.id).toBe('alpine');
|
|
105
|
-
expect(adapter.tier).toBe('tier-3');
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('accepts plugins option', () => {
|
|
109
|
-
const adapter = alpine({ plugins: ['intersect', 'persist', 'collapse'] });
|
|
110
|
-
expect(adapter).toBeDefined();
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('accepts version option', () => {
|
|
114
|
-
const adapter = alpine({ version: '3.14.0' });
|
|
115
|
-
expect(adapter).toBeDefined();
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('supports CSR (Alpine hydrates on client)', () => {
|
|
119
|
-
const adapter = alpine();
|
|
120
|
-
expect(adapter.capabilities.csr).toBe(true);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
describe('Alpine Template Helpers', () => {
|
|
125
|
-
it('xData generates div with x-data attribute', () => {
|
|
126
|
-
const html = xData({ count: 0 }, '<span x-text="count"></span>');
|
|
127
|
-
expect(html).toContain('x-data=');
|
|
128
|
-
expect(html).toContain('count');
|
|
129
|
-
expect(html).toContain('<span x-text="count"></span>');
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('xFor generates template with x-for', () => {
|
|
133
|
-
const html = xFor('item in items', '<li x-text="item"></li>');
|
|
134
|
-
expect(html).toContain('<template x-for="item in items">');
|
|
135
|
-
expect(html).toContain('</template>');
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('xIf generates template with x-if', () => {
|
|
139
|
-
const html = xIf('show', '<span>Visible</span>');
|
|
140
|
-
expect(html).toContain('<template x-if="show">');
|
|
141
|
-
expect(html).toContain('</template>');
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// ============================================================================
|
|
146
|
-
// Hotwire Specific Tests
|
|
147
|
-
// ============================================================================
|
|
148
|
-
|
|
149
|
-
describe('Hotwire Adapter - Specific', () => {
|
|
150
|
-
it('creates adapter with default options', () => {
|
|
151
|
-
const adapter = hotwire();
|
|
152
|
-
expect(adapter).toBeInstanceOf(HotwireAdapter);
|
|
153
|
-
expect(adapter.id).toBe('hotwire');
|
|
154
|
-
expect(adapter.tier).toBe('tier-3');
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it('accepts turboVersion option', () => {
|
|
158
|
-
const adapter = hotwire({ turboVersion: '8.0.0' });
|
|
159
|
-
expect(adapter).toBeDefined();
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('accepts stimulusVersion option', () => {
|
|
163
|
-
const adapter = hotwire({ stimulusVersion: '3.2.0' });
|
|
164
|
-
expect(adapter).toBeDefined();
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
describe('Hotwire Template Helpers', () => {
|
|
169
|
-
it('turboFrame generates turbo-frame element', () => {
|
|
170
|
-
const html = turboFrame('user-list', '<ul><li>User 1</li></ul>');
|
|
171
|
-
expect(html).toContain('<turbo-frame id="user-list"');
|
|
172
|
-
expect(html).toContain('</turbo-frame>');
|
|
173
|
-
expect(html).toContain('<ul><li>User 1</li></ul>');
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it('turboFrame accepts options', () => {
|
|
177
|
-
const html = turboFrame('comments', '<div>Comments</div>', {
|
|
178
|
-
src: '/comments',
|
|
179
|
-
loading: 'lazy',
|
|
180
|
-
});
|
|
181
|
-
expect(html).toContain('src="/comments"');
|
|
182
|
-
expect(html).toContain('loading="lazy"');
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it('turboStream generates turbo-stream element', () => {
|
|
186
|
-
// turboStream takes: action, target, content
|
|
187
|
-
const html = turboStream('append', 'messages', '<div>New message</div>');
|
|
188
|
-
expect(html).toContain('<turbo-stream');
|
|
189
|
-
expect(html).toContain('action="append"');
|
|
190
|
-
expect(html).toContain('target="messages"');
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// ============================================================================
|
|
195
|
-
// Stimulus Specific Tests
|
|
196
|
-
// ============================================================================
|
|
197
|
-
|
|
198
|
-
describe('Stimulus Adapter - Specific', () => {
|
|
199
|
-
it('creates adapter with default options', () => {
|
|
200
|
-
const adapter = stimulus();
|
|
201
|
-
expect(adapter).toBeInstanceOf(StimulusAdapter);
|
|
202
|
-
expect(adapter.id).toBe('stimulus');
|
|
203
|
-
expect(adapter.tier).toBe('tier-3');
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it('supports CSR (Stimulus controllers run on client)', () => {
|
|
207
|
-
const adapter = stimulus();
|
|
208
|
-
expect(adapter.capabilities.csr).toBe(true);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it('accepts version option', () => {
|
|
212
|
-
const adapter = stimulus({ version: '3.2.0' });
|
|
213
|
-
expect(adapter).toBeDefined();
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
// ============================================================================
|
|
218
|
-
// Petite-vue Specific Tests
|
|
219
|
-
// ============================================================================
|
|
220
|
-
|
|
221
|
-
describe('Petite-vue Adapter - Specific', () => {
|
|
222
|
-
it('creates adapter with default options', () => {
|
|
223
|
-
const adapter = petiteVue();
|
|
224
|
-
expect(adapter).toBeInstanceOf(PetiteVueAdapter);
|
|
225
|
-
expect(adapter.id).toBe('petite-vue');
|
|
226
|
-
expect(adapter.tier).toBe('tier-3');
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it('supports CSR (client-side hydration)', () => {
|
|
230
|
-
const adapter = petiteVue();
|
|
231
|
-
expect(adapter.capabilities.csr).toBe(true);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
it('accepts version option', () => {
|
|
235
|
-
const adapter = petiteVue({ version: '0.4.0' });
|
|
236
|
-
expect(adapter).toBeDefined();
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
describe('Petite-vue Template Helpers', () => {
|
|
241
|
-
it('vScope generates div with v-scope', () => {
|
|
242
|
-
const html = vScope({ count: 0 }, '<button @click="count++">{{ count }}</button>');
|
|
243
|
-
expect(html).toContain('v-scope=');
|
|
244
|
-
expect(html).toContain('count');
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
// ============================================================================
|
|
249
|
-
// Vanilla Web Components Specific Tests
|
|
250
|
-
// ============================================================================
|
|
251
|
-
|
|
252
|
-
describe('Vanilla Adapter - Specific', () => {
|
|
253
|
-
it('creates adapter with default options', () => {
|
|
254
|
-
const adapter = vanilla();
|
|
255
|
-
expect(adapter).toBeInstanceOf(VanillaAdapter);
|
|
256
|
-
expect(adapter.id).toBe('vanilla');
|
|
257
|
-
expect(adapter.tier).toBe('tier-3');
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
it('supports islands (Web Components are natural islands)', () => {
|
|
261
|
-
const adapter = vanilla();
|
|
262
|
-
expect(adapter.capabilities.islands).toBe(true);
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
it('accepts declarativeShadowDom option', () => {
|
|
266
|
-
const adapter = vanilla({ declarativeShadowDom: true });
|
|
267
|
-
expect(adapter).toBeDefined();
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
it('supports createIsland method', () => {
|
|
271
|
-
const adapter = vanilla();
|
|
272
|
-
expect(typeof adapter.createIsland).toBe('function');
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it('createIsland returns valid island', () => {
|
|
276
|
-
const adapter = vanilla();
|
|
277
|
-
const island = adapter.createIsland('my-counter', { start: 5 });
|
|
278
|
-
|
|
279
|
-
expect(island.id).toBeDefined();
|
|
280
|
-
expect(island.component).toBe('my-counter');
|
|
281
|
-
expect(island.props).toEqual({ start: 5 });
|
|
282
|
-
expect(island.placeholder).toContain('<my-counter');
|
|
283
|
-
});
|
|
284
|
-
});
|
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @flightdev/ui - Contract Tests
|
|
3
|
-
*
|
|
4
|
-
* These tests verify that all UI adapters conform to the UIAdapterV2 contract.
|
|
5
|
-
* Contract testing ensures consistent behavior across all 18 adapters.
|
|
6
|
-
*
|
|
7
|
-
* @module @flightdev/ui/tests
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { describe, it, expect, beforeAll } from 'vitest';
|
|
11
|
-
import type { UIAdapterV2, AdapterCapabilities, RenderResult } from '../../src/core/types.js';
|
|
12
|
-
|
|
13
|
-
// ============================================================================
|
|
14
|
-
// Test Utilities
|
|
15
|
-
// ============================================================================
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Mock component for testing adapters.
|
|
19
|
-
* Each adapter handles this differently based on its framework.
|
|
20
|
-
*/
|
|
21
|
-
const createMockComponent = () => ({
|
|
22
|
-
component: () => '<div>Test Component</div>',
|
|
23
|
-
props: { message: 'Hello, Flight!' },
|
|
24
|
-
id: 'test-component-1',
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Mock HTML template function for HTML-first adapters.
|
|
29
|
-
*/
|
|
30
|
-
const createHtmlComponent = () => ({
|
|
31
|
-
component: (props: { message: string }) => `<div>${props.message}</div>`,
|
|
32
|
-
props: { message: 'Hello, Flight!' },
|
|
33
|
-
id: 'test-html-1',
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
// ============================================================================
|
|
37
|
-
// Contract Test Suite
|
|
38
|
-
// ============================================================================
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Runs the standard contract tests for any UIAdapterV2 implementation.
|
|
42
|
-
* This ensures all adapters behave consistently.
|
|
43
|
-
*/
|
|
44
|
-
export function runAdapterContractTests(
|
|
45
|
-
adapterFactory: () => UIAdapterV2,
|
|
46
|
-
adapterName: string,
|
|
47
|
-
options: {
|
|
48
|
-
/** Use HTML component instead of framework component */
|
|
49
|
-
useHtmlComponent?: boolean;
|
|
50
|
-
/** Skip renderToString test (for adapters that need special setup) */
|
|
51
|
-
skipRenderTest?: boolean;
|
|
52
|
-
} = {}
|
|
53
|
-
): void {
|
|
54
|
-
describe(`${adapterName} Adapter - Contract Tests`, () => {
|
|
55
|
-
let adapter: UIAdapterV2;
|
|
56
|
-
|
|
57
|
-
beforeAll(() => {
|
|
58
|
-
adapter = adapterFactory();
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// ====================================================================
|
|
62
|
-
// Identity Tests
|
|
63
|
-
// ====================================================================
|
|
64
|
-
|
|
65
|
-
describe('Identity', () => {
|
|
66
|
-
it('has a valid id (lowercase, no spaces)', () => {
|
|
67
|
-
expect(adapter.id).toBeDefined();
|
|
68
|
-
expect(typeof adapter.id).toBe('string');
|
|
69
|
-
expect(adapter.id).toMatch(/^[a-z][a-z0-9-]*$/);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('has a human-readable name', () => {
|
|
73
|
-
expect(adapter.name).toBeDefined();
|
|
74
|
-
expect(typeof adapter.name).toBe('string');
|
|
75
|
-
expect(adapter.name.length).toBeGreaterThan(0);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('has a framework identifier', () => {
|
|
79
|
-
expect(adapter.framework).toBeDefined();
|
|
80
|
-
expect(typeof adapter.framework).toBe('string');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('has a valid tier', () => {
|
|
84
|
-
expect(adapter.tier).toBeDefined();
|
|
85
|
-
expect(['tier-1', 'tier-2', 'tier-3']).toContain(adapter.tier);
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// ====================================================================
|
|
90
|
-
// Capabilities Tests
|
|
91
|
-
// ====================================================================
|
|
92
|
-
|
|
93
|
-
describe('Capabilities', () => {
|
|
94
|
-
it('has a capabilities object', () => {
|
|
95
|
-
expect(adapter.capabilities).toBeDefined();
|
|
96
|
-
expect(typeof adapter.capabilities).toBe('object');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('has all required capability flags as booleans', () => {
|
|
100
|
-
const requiredCapabilities: (keyof AdapterCapabilities)[] = [
|
|
101
|
-
'streaming',
|
|
102
|
-
'partialHydration',
|
|
103
|
-
'islands',
|
|
104
|
-
'resumable',
|
|
105
|
-
'ssg',
|
|
106
|
-
'csr',
|
|
107
|
-
'serverComponents',
|
|
108
|
-
];
|
|
109
|
-
|
|
110
|
-
for (const cap of requiredCapabilities) {
|
|
111
|
-
expect(
|
|
112
|
-
typeof adapter.capabilities[cap],
|
|
113
|
-
`capability '${String(cap)}' should be a boolean`
|
|
114
|
-
).toBe('boolean');
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('tier-1 adapters should support SSG', () => {
|
|
119
|
-
if (adapter.tier === 'tier-1') {
|
|
120
|
-
expect(adapter.capabilities.ssg).toBe(true);
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('tier-3 adapters typically do not support CSR', () => {
|
|
125
|
-
// This is a soft check - some tier-3 adapters may support CSR
|
|
126
|
-
if (adapter.tier === 'tier-3' && adapter.id !== 'alpine' && adapter.id !== 'petite-vue') {
|
|
127
|
-
// HTMX, Hotwire are server-driven
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
// ====================================================================
|
|
133
|
-
// Render Contract Tests
|
|
134
|
-
// ====================================================================
|
|
135
|
-
|
|
136
|
-
describe('Render Contract', () => {
|
|
137
|
-
it('renderToString is a function', () => {
|
|
138
|
-
expect(typeof adapter.renderToString).toBe('function');
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
if (!options.skipRenderTest) {
|
|
142
|
-
it('renderToString returns a Promise', async () => {
|
|
143
|
-
const component = options.useHtmlComponent
|
|
144
|
-
? createHtmlComponent()
|
|
145
|
-
: createMockComponent();
|
|
146
|
-
|
|
147
|
-
const resultPromise = adapter.renderToString(component);
|
|
148
|
-
expect(resultPromise).toBeInstanceOf(Promise);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('renderToString result has html property', async () => {
|
|
152
|
-
const component = options.useHtmlComponent
|
|
153
|
-
? createHtmlComponent()
|
|
154
|
-
: createMockComponent();
|
|
155
|
-
|
|
156
|
-
const result = await adapter.renderToString(component);
|
|
157
|
-
|
|
158
|
-
expect(result).toBeDefined();
|
|
159
|
-
expect(typeof result.html).toBe('string');
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('renderToString result may have timing info', async () => {
|
|
163
|
-
const component = options.useHtmlComponent
|
|
164
|
-
? createHtmlComponent()
|
|
165
|
-
: createMockComponent();
|
|
166
|
-
|
|
167
|
-
const result = await adapter.renderToString(component);
|
|
168
|
-
|
|
169
|
-
if (result.timing) {
|
|
170
|
-
expect(typeof result.timing.total).toBe('number');
|
|
171
|
-
expect(result.timing.total).toBeGreaterThanOrEqual(0);
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
// ====================================================================
|
|
178
|
-
// Streaming Contract Tests
|
|
179
|
-
// ====================================================================
|
|
180
|
-
|
|
181
|
-
describe('Streaming Contract', () => {
|
|
182
|
-
it('renderToStream is defined if streaming capability is true (advisory)', () => {
|
|
183
|
-
// Note: This is an advisory check. Some adapters may declare
|
|
184
|
-
// streaming capability but delegate to renderToString internally.
|
|
185
|
-
if (adapter.capabilities.streaming && adapter.renderToStream) {
|
|
186
|
-
expect(typeof adapter.renderToStream).toBe('function');
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
// Only run actual streaming test if not skipping render tests
|
|
191
|
-
// (some adapters need real framework components to stream)
|
|
192
|
-
if (!options.skipRenderTest) {
|
|
193
|
-
it('renderToStream returns stream, done, and abort when available', () => {
|
|
194
|
-
if (adapter.renderToStream) {
|
|
195
|
-
const component = options.useHtmlComponent
|
|
196
|
-
? createHtmlComponent()
|
|
197
|
-
: createMockComponent();
|
|
198
|
-
|
|
199
|
-
const result = adapter.renderToStream(component);
|
|
200
|
-
|
|
201
|
-
expect(result.stream).toBeInstanceOf(ReadableStream);
|
|
202
|
-
expect(result.done).toBeInstanceOf(Promise);
|
|
203
|
-
expect(typeof result.abort).toBe('function');
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
// ====================================================================
|
|
210
|
-
// Hydration Contract Tests
|
|
211
|
-
// ====================================================================
|
|
212
|
-
|
|
213
|
-
describe('Hydration Contract', () => {
|
|
214
|
-
it('getHydrationScript is a function', () => {
|
|
215
|
-
expect(typeof adapter.getHydrationScript).toBe('function');
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it('getHydrationScript returns a string', () => {
|
|
219
|
-
const mockResult: RenderResult = {
|
|
220
|
-
html: '<div>Test</div>',
|
|
221
|
-
hydrationData: { props: {} },
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
const script = adapter.getHydrationScript(mockResult);
|
|
225
|
-
|
|
226
|
-
expect(typeof script).toBe('string');
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it('getHydrationScript contains script tag or is valid JS', () => {
|
|
230
|
-
const mockResult: RenderResult = {
|
|
231
|
-
html: '<div>Test</div>',
|
|
232
|
-
hydrationData: { props: {} },
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
const script = adapter.getHydrationScript(mockResult);
|
|
236
|
-
|
|
237
|
-
// Should be either a script tag or module code
|
|
238
|
-
const hasScriptTag = script.includes('<script');
|
|
239
|
-
const hasModuleCode = script.includes('export') || script.includes('window.');
|
|
240
|
-
const isEmpty = script.trim().length === 0;
|
|
241
|
-
|
|
242
|
-
expect(hasScriptTag || hasModuleCode || isEmpty).toBe(true);
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
it('getClientEntry is a function', () => {
|
|
246
|
-
expect(typeof adapter.getClientEntry).toBe('function');
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
it('getClientEntry returns client-side code', () => {
|
|
250
|
-
const entry = adapter.getClientEntry();
|
|
251
|
-
|
|
252
|
-
expect(typeof entry).toBe('string');
|
|
253
|
-
// Should export a hydrate function or be client code
|
|
254
|
-
const isClientCode =
|
|
255
|
-
entry.includes('hydrate') ||
|
|
256
|
-
entry.includes('mount') ||
|
|
257
|
-
entry.includes('render') ||
|
|
258
|
-
entry.includes('console.log');
|
|
259
|
-
|
|
260
|
-
expect(isClientCode).toBe(true);
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
// ====================================================================
|
|
265
|
-
// Islands Contract Tests
|
|
266
|
-
// ====================================================================
|
|
267
|
-
|
|
268
|
-
describe('Islands Contract', () => {
|
|
269
|
-
it('createIsland is available (all adapters have default implementation)', () => {
|
|
270
|
-
// BaseUIAdapter provides a default createIsland implementation
|
|
271
|
-
expect(typeof adapter.createIsland).toBe('function');
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
it('createIsland returns valid island object', () => {
|
|
275
|
-
if (adapter.createIsland) {
|
|
276
|
-
const island = adapter.createIsland('my-component', { prop: 'value' });
|
|
277
|
-
|
|
278
|
-
expect(island.id).toBeDefined();
|
|
279
|
-
expect(typeof island.id).toBe('string');
|
|
280
|
-
expect(island.component).toBe('my-component');
|
|
281
|
-
expect(island.props).toEqual({ prop: 'value' });
|
|
282
|
-
expect(typeof island.placeholder).toBe('string');
|
|
283
|
-
}
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// ============================================================================
|
|
290
|
-
// Export for direct usage
|
|
291
|
-
// ============================================================================
|
|
292
|
-
|
|
293
|
-
export { createMockComponent, createHtmlComponent };
|