onejs-react 0.1.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/package.json +43 -0
- package/src/__tests__/components.test.tsx +388 -0
- package/src/__tests__/host-config.test.ts +674 -0
- package/src/__tests__/mocks.ts +311 -0
- package/src/__tests__/renderer.test.tsx +387 -0
- package/src/__tests__/setup.ts +52 -0
- package/src/__tests__/style-parser.test.ts +321 -0
- package/src/components.tsx +87 -0
- package/src/host-config.ts +749 -0
- package/src/index.ts +54 -0
- package/src/renderer.ts +73 -0
- package/src/screen.tsx +242 -0
- package/src/style-parser.ts +288 -0
- package/src/types.ts +295 -0
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for host-config.ts - the React reconciler implementation
|
|
3
|
+
*
|
|
4
|
+
* Tests cover:
|
|
5
|
+
* - Instance creation (createInstance)
|
|
6
|
+
* - Style property application and cleanup
|
|
7
|
+
* - ClassName management (add/remove/update)
|
|
8
|
+
* - Event handler registration
|
|
9
|
+
* - Component-specific props (text, value, label)
|
|
10
|
+
* - Child management (appendChild, insertBefore, removeChild)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
14
|
+
import { hostConfig, type Instance } from '../host-config';
|
|
15
|
+
import { MockVisualElement, MockLength, MockColor, getEventAPI, flushMicrotasks, getCreatedElements } from './mocks';
|
|
16
|
+
|
|
17
|
+
// Helper to extract value from style (handles both raw values and MockLength/MockColor)
|
|
18
|
+
function getStyleValue(style: unknown): unknown {
|
|
19
|
+
if (style instanceof MockLength) return style.value;
|
|
20
|
+
if (style instanceof MockColor) return style;
|
|
21
|
+
return style;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('host-config', () => {
|
|
25
|
+
describe('createInstance', () => {
|
|
26
|
+
it('creates a VisualElement for ojs-view', () => {
|
|
27
|
+
const instance = hostConfig.createInstance('ojs-view', {}, null as any, null, null);
|
|
28
|
+
|
|
29
|
+
expect(instance).toBeDefined();
|
|
30
|
+
expect(instance.type).toBe('ojs-view');
|
|
31
|
+
expect(instance.element).toBeInstanceOf(MockVisualElement);
|
|
32
|
+
expect(instance.eventHandlers).toBeInstanceOf(Map);
|
|
33
|
+
expect(instance.appliedStyleKeys).toBeInstanceOf(Set);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('creates a Label for ojs-label', () => {
|
|
37
|
+
const instance = hostConfig.createInstance('ojs-label', { text: 'Hello' }, null as any, null, null);
|
|
38
|
+
|
|
39
|
+
expect(instance.type).toBe('ojs-label');
|
|
40
|
+
expect(instance.element.text).toBe('Hello');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('creates a Button for ojs-button', () => {
|
|
44
|
+
const instance = hostConfig.createInstance('ojs-button', { text: 'Click me' }, null as any, null, null);
|
|
45
|
+
|
|
46
|
+
expect(instance.type).toBe('ojs-button');
|
|
47
|
+
expect(instance.element.text).toBe('Click me');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('creates a TextField for ojs-textfield', () => {
|
|
51
|
+
const instance = hostConfig.createInstance('ojs-textfield', { value: 'test' }, null as any, null, null);
|
|
52
|
+
|
|
53
|
+
expect(instance.type).toBe('ojs-textfield');
|
|
54
|
+
expect(instance.element.value).toBe('test');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('creates a Toggle for ojs-toggle', () => {
|
|
58
|
+
const instance = hostConfig.createInstance('ojs-toggle', { value: true, label: 'Enable' }, null as any, null, null);
|
|
59
|
+
|
|
60
|
+
expect(instance.type).toBe('ojs-toggle');
|
|
61
|
+
expect(instance.element.value).toBe(true);
|
|
62
|
+
expect(instance.element.label).toBe('Enable');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('throws for unknown element type', () => {
|
|
66
|
+
expect(() => {
|
|
67
|
+
hostConfig.createInstance('unknown-type', {}, null as any, null, null);
|
|
68
|
+
}).toThrow('Unknown element type: unknown-type');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('style application', () => {
|
|
73
|
+
it('applies style properties on creation', () => {
|
|
74
|
+
const instance = hostConfig.createInstance(
|
|
75
|
+
'ojs-view',
|
|
76
|
+
{ style: { width: 100, height: 50, backgroundColor: 'red' } },
|
|
77
|
+
null as any,
|
|
78
|
+
null,
|
|
79
|
+
null
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Length properties are now wrapped in MockLength
|
|
83
|
+
expect(getStyleValue(instance.element.style.width)).toBe(100);
|
|
84
|
+
expect(getStyleValue(instance.element.style.height)).toBe(50);
|
|
85
|
+
// Color properties are now wrapped in MockColor
|
|
86
|
+
expect(instance.element.style.backgroundColor).toBeInstanceOf(MockColor);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('tracks applied style keys', () => {
|
|
90
|
+
const instance = hostConfig.createInstance(
|
|
91
|
+
'ojs-view',
|
|
92
|
+
{ style: { width: 100, height: 50 } },
|
|
93
|
+
null as any,
|
|
94
|
+
null,
|
|
95
|
+
null
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
expect(instance.appliedStyleKeys.has('width')).toBe(true);
|
|
99
|
+
expect(instance.appliedStyleKeys.has('height')).toBe(true);
|
|
100
|
+
expect(instance.appliedStyleKeys.has('backgroundColor')).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('expands shorthand padding to individual properties', () => {
|
|
104
|
+
const instance = hostConfig.createInstance(
|
|
105
|
+
'ojs-view',
|
|
106
|
+
{ style: { padding: 10 } },
|
|
107
|
+
null as any,
|
|
108
|
+
null,
|
|
109
|
+
null
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
expect(getStyleValue(instance.element.style.paddingTop)).toBe(10);
|
|
113
|
+
expect(getStyleValue(instance.element.style.paddingRight)).toBe(10);
|
|
114
|
+
expect(getStyleValue(instance.element.style.paddingBottom)).toBe(10);
|
|
115
|
+
expect(getStyleValue(instance.element.style.paddingLeft)).toBe(10);
|
|
116
|
+
expect(instance.appliedStyleKeys.has('paddingTop')).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('expands shorthand margin to individual properties', () => {
|
|
120
|
+
const instance = hostConfig.createInstance(
|
|
121
|
+
'ojs-view',
|
|
122
|
+
{ style: { margin: 20 } },
|
|
123
|
+
null as any,
|
|
124
|
+
null,
|
|
125
|
+
null
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
expect(getStyleValue(instance.element.style.marginTop)).toBe(20);
|
|
129
|
+
expect(getStyleValue(instance.element.style.marginRight)).toBe(20);
|
|
130
|
+
expect(getStyleValue(instance.element.style.marginBottom)).toBe(20);
|
|
131
|
+
expect(getStyleValue(instance.element.style.marginLeft)).toBe(20);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('expands shorthand borderRadius', () => {
|
|
135
|
+
const instance = hostConfig.createInstance(
|
|
136
|
+
'ojs-view',
|
|
137
|
+
{ style: { borderRadius: 8 } },
|
|
138
|
+
null as any,
|
|
139
|
+
null,
|
|
140
|
+
null
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
expect(getStyleValue(instance.element.style.borderTopLeftRadius)).toBe(8);
|
|
144
|
+
expect(getStyleValue(instance.element.style.borderTopRightRadius)).toBe(8);
|
|
145
|
+
expect(getStyleValue(instance.element.style.borderBottomRightRadius)).toBe(8);
|
|
146
|
+
expect(getStyleValue(instance.element.style.borderBottomLeftRadius)).toBe(8);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('style updates (commitUpdate)', () => {
|
|
151
|
+
it('updates style properties', () => {
|
|
152
|
+
const instance = hostConfig.createInstance(
|
|
153
|
+
'ojs-view',
|
|
154
|
+
{ style: { width: 100 } },
|
|
155
|
+
null as any,
|
|
156
|
+
null,
|
|
157
|
+
null
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// Simulate React calling commitUpdate
|
|
161
|
+
hostConfig.commitUpdate(
|
|
162
|
+
instance,
|
|
163
|
+
'ojs-view',
|
|
164
|
+
{ style: { width: 100 } },
|
|
165
|
+
{ style: { width: 200 } },
|
|
166
|
+
null as any
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
expect(getStyleValue(instance.element.style.width)).toBe(200);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('clears removed style properties', () => {
|
|
173
|
+
const instance = hostConfig.createInstance(
|
|
174
|
+
'ojs-view',
|
|
175
|
+
{ style: { width: 100, height: 50, backgroundColor: 'red' } },
|
|
176
|
+
null as any,
|
|
177
|
+
null,
|
|
178
|
+
null
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// Update: remove width and backgroundColor, keep height
|
|
182
|
+
hostConfig.commitUpdate(
|
|
183
|
+
instance,
|
|
184
|
+
'ojs-view',
|
|
185
|
+
{ style: { width: 100, height: 50, backgroundColor: 'red' } },
|
|
186
|
+
{ style: { height: 75 } },
|
|
187
|
+
null as any
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
expect(instance.element.style.width).toBeUndefined();
|
|
191
|
+
expect(instance.element.style.backgroundColor).toBeUndefined();
|
|
192
|
+
expect(getStyleValue(instance.element.style.height)).toBe(75);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('clears expanded shorthand properties when shorthand is removed', () => {
|
|
196
|
+
const instance = hostConfig.createInstance(
|
|
197
|
+
'ojs-view',
|
|
198
|
+
{ style: { padding: 10, width: 100 } },
|
|
199
|
+
null as any,
|
|
200
|
+
null,
|
|
201
|
+
null
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// Remove padding shorthand
|
|
205
|
+
hostConfig.commitUpdate(
|
|
206
|
+
instance,
|
|
207
|
+
'ojs-view',
|
|
208
|
+
{ style: { padding: 10, width: 100 } },
|
|
209
|
+
{ style: { width: 100 } },
|
|
210
|
+
null as any
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
expect(instance.element.style.paddingTop).toBeUndefined();
|
|
214
|
+
expect(instance.element.style.paddingRight).toBeUndefined();
|
|
215
|
+
expect(instance.element.style.paddingBottom).toBeUndefined();
|
|
216
|
+
expect(instance.element.style.paddingLeft).toBeUndefined();
|
|
217
|
+
expect(getStyleValue(instance.element.style.width)).toBe(100);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('clears all styles when style prop becomes undefined', () => {
|
|
221
|
+
const instance = hostConfig.createInstance(
|
|
222
|
+
'ojs-view',
|
|
223
|
+
{ style: { width: 100, height: 50 } },
|
|
224
|
+
null as any,
|
|
225
|
+
null,
|
|
226
|
+
null
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
hostConfig.commitUpdate(
|
|
230
|
+
instance,
|
|
231
|
+
'ojs-view',
|
|
232
|
+
{ style: { width: 100, height: 50 } },
|
|
233
|
+
{},
|
|
234
|
+
null as any
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
expect(instance.element.style.width).toBeUndefined();
|
|
238
|
+
expect(instance.element.style.height).toBeUndefined();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('updates appliedStyleKeys after style change', () => {
|
|
242
|
+
const instance = hostConfig.createInstance(
|
|
243
|
+
'ojs-view',
|
|
244
|
+
{ style: { width: 100 } },
|
|
245
|
+
null as any,
|
|
246
|
+
null,
|
|
247
|
+
null
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
expect(instance.appliedStyleKeys.has('width')).toBe(true);
|
|
251
|
+
expect(instance.appliedStyleKeys.has('height')).toBe(false);
|
|
252
|
+
|
|
253
|
+
hostConfig.commitUpdate(
|
|
254
|
+
instance,
|
|
255
|
+
'ojs-view',
|
|
256
|
+
{ style: { width: 100 } },
|
|
257
|
+
{ style: { height: 50 } },
|
|
258
|
+
null as any
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
expect(instance.appliedStyleKeys.has('width')).toBe(false);
|
|
262
|
+
expect(instance.appliedStyleKeys.has('height')).toBe(true);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe('className management', () => {
|
|
267
|
+
it('applies className on creation', () => {
|
|
268
|
+
const instance = hostConfig.createInstance(
|
|
269
|
+
'ojs-view',
|
|
270
|
+
{ className: 'foo bar' },
|
|
271
|
+
null as any,
|
|
272
|
+
null,
|
|
273
|
+
null
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
const el = instance.element as MockVisualElement;
|
|
277
|
+
expect(el.hasClass('foo')).toBe(true);
|
|
278
|
+
expect(el.hasClass('bar')).toBe(true);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('handles multiple spaces in className', () => {
|
|
282
|
+
const instance = hostConfig.createInstance(
|
|
283
|
+
'ojs-view',
|
|
284
|
+
{ className: 'foo bar baz' },
|
|
285
|
+
null as any,
|
|
286
|
+
null,
|
|
287
|
+
null
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
const el = instance.element as MockVisualElement;
|
|
291
|
+
expect(el.hasClass('foo')).toBe(true);
|
|
292
|
+
expect(el.hasClass('bar')).toBe(true);
|
|
293
|
+
expect(el.hasClass('baz')).toBe(true);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('selectively adds new classes on update', () => {
|
|
297
|
+
const instance = hostConfig.createInstance(
|
|
298
|
+
'ojs-view',
|
|
299
|
+
{ className: 'foo bar' },
|
|
300
|
+
null as any,
|
|
301
|
+
null,
|
|
302
|
+
null
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
hostConfig.commitUpdate(
|
|
306
|
+
instance,
|
|
307
|
+
'ojs-view',
|
|
308
|
+
{ className: 'foo bar' },
|
|
309
|
+
{ className: 'foo bar baz' },
|
|
310
|
+
null as any
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
const el = instance.element as MockVisualElement;
|
|
314
|
+
expect(el.hasClass('foo')).toBe(true);
|
|
315
|
+
expect(el.hasClass('bar')).toBe(true);
|
|
316
|
+
expect(el.hasClass('baz')).toBe(true);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('selectively removes old classes on update', () => {
|
|
320
|
+
const instance = hostConfig.createInstance(
|
|
321
|
+
'ojs-view',
|
|
322
|
+
{ className: 'foo bar baz' },
|
|
323
|
+
null as any,
|
|
324
|
+
null,
|
|
325
|
+
null
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
hostConfig.commitUpdate(
|
|
329
|
+
instance,
|
|
330
|
+
'ojs-view',
|
|
331
|
+
{ className: 'foo bar baz' },
|
|
332
|
+
{ className: 'foo' },
|
|
333
|
+
null as any
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
const el = instance.element as MockVisualElement;
|
|
337
|
+
expect(el.hasClass('foo')).toBe(true);
|
|
338
|
+
expect(el.hasClass('bar')).toBe(false);
|
|
339
|
+
expect(el.hasClass('baz')).toBe(false);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('handles complete className replacement', () => {
|
|
343
|
+
const instance = hostConfig.createInstance(
|
|
344
|
+
'ojs-view',
|
|
345
|
+
{ className: 'old-class' },
|
|
346
|
+
null as any,
|
|
347
|
+
null,
|
|
348
|
+
null
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
hostConfig.commitUpdate(
|
|
352
|
+
instance,
|
|
353
|
+
'ojs-view',
|
|
354
|
+
{ className: 'old-class' },
|
|
355
|
+
{ className: 'new-class' },
|
|
356
|
+
null as any
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
const el = instance.element as MockVisualElement;
|
|
360
|
+
expect(el.hasClass('old-class')).toBe(false);
|
|
361
|
+
expect(el.hasClass('new-class')).toBe(true);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('removes all classes when className becomes undefined', () => {
|
|
365
|
+
const instance = hostConfig.createInstance(
|
|
366
|
+
'ojs-view',
|
|
367
|
+
{ className: 'foo bar' },
|
|
368
|
+
null as any,
|
|
369
|
+
null,
|
|
370
|
+
null
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
hostConfig.commitUpdate(
|
|
374
|
+
instance,
|
|
375
|
+
'ojs-view',
|
|
376
|
+
{ className: 'foo bar' },
|
|
377
|
+
{},
|
|
378
|
+
null as any
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
const el = instance.element as MockVisualElement;
|
|
382
|
+
expect(el.hasClass('foo')).toBe(false);
|
|
383
|
+
expect(el.hasClass('bar')).toBe(false);
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
describe('event handlers', () => {
|
|
388
|
+
it('registers onClick handler on creation', () => {
|
|
389
|
+
const handler = vi.fn();
|
|
390
|
+
const instance = hostConfig.createInstance(
|
|
391
|
+
'ojs-button',
|
|
392
|
+
{ onClick: handler },
|
|
393
|
+
null as any,
|
|
394
|
+
null,
|
|
395
|
+
null
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
const eventAPI = getEventAPI();
|
|
399
|
+
expect(eventAPI.addEventListener).toHaveBeenCalledWith(
|
|
400
|
+
instance.element,
|
|
401
|
+
'click',
|
|
402
|
+
handler
|
|
403
|
+
);
|
|
404
|
+
expect(instance.eventHandlers.get('click')).toBe(handler);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('registers multiple event handlers', () => {
|
|
408
|
+
const onClick = vi.fn();
|
|
409
|
+
const onPointerDown = vi.fn();
|
|
410
|
+
const instance = hostConfig.createInstance(
|
|
411
|
+
'ojs-view',
|
|
412
|
+
{ onClick, onPointerDown },
|
|
413
|
+
null as any,
|
|
414
|
+
null,
|
|
415
|
+
null
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
const eventAPI = getEventAPI();
|
|
419
|
+
expect(eventAPI.addEventListener).toHaveBeenCalledWith(
|
|
420
|
+
instance.element,
|
|
421
|
+
'click',
|
|
422
|
+
onClick
|
|
423
|
+
);
|
|
424
|
+
expect(eventAPI.addEventListener).toHaveBeenCalledWith(
|
|
425
|
+
instance.element,
|
|
426
|
+
'pointerdown',
|
|
427
|
+
onPointerDown
|
|
428
|
+
);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it('removes event handler on update', () => {
|
|
432
|
+
const handler = vi.fn();
|
|
433
|
+
const instance = hostConfig.createInstance(
|
|
434
|
+
'ojs-button',
|
|
435
|
+
{ onClick: handler },
|
|
436
|
+
null as any,
|
|
437
|
+
null,
|
|
438
|
+
null
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
// Clear mock to track only update calls
|
|
442
|
+
const eventAPI = getEventAPI();
|
|
443
|
+
eventAPI.addEventListener.mockClear();
|
|
444
|
+
eventAPI.removeEventListener.mockClear();
|
|
445
|
+
|
|
446
|
+
hostConfig.commitUpdate(
|
|
447
|
+
instance,
|
|
448
|
+
'ojs-button',
|
|
449
|
+
{ onClick: handler },
|
|
450
|
+
{},
|
|
451
|
+
null as any
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
expect(eventAPI.removeEventListener).toHaveBeenCalledWith(
|
|
455
|
+
instance.element,
|
|
456
|
+
'click',
|
|
457
|
+
handler
|
|
458
|
+
);
|
|
459
|
+
expect(instance.eventHandlers.has('click')).toBe(false);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it('replaces event handler on update', () => {
|
|
463
|
+
const oldHandler = vi.fn();
|
|
464
|
+
const newHandler = vi.fn();
|
|
465
|
+
const instance = hostConfig.createInstance(
|
|
466
|
+
'ojs-button',
|
|
467
|
+
{ onClick: oldHandler },
|
|
468
|
+
null as any,
|
|
469
|
+
null,
|
|
470
|
+
null
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
const eventAPI = getEventAPI();
|
|
474
|
+
eventAPI.addEventListener.mockClear();
|
|
475
|
+
eventAPI.removeEventListener.mockClear();
|
|
476
|
+
|
|
477
|
+
hostConfig.commitUpdate(
|
|
478
|
+
instance,
|
|
479
|
+
'ojs-button',
|
|
480
|
+
{ onClick: oldHandler },
|
|
481
|
+
{ onClick: newHandler },
|
|
482
|
+
null as any
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
expect(eventAPI.removeEventListener).toHaveBeenCalledWith(
|
|
486
|
+
instance.element,
|
|
487
|
+
'click',
|
|
488
|
+
oldHandler
|
|
489
|
+
);
|
|
490
|
+
expect(eventAPI.addEventListener).toHaveBeenCalledWith(
|
|
491
|
+
instance.element,
|
|
492
|
+
'click',
|
|
493
|
+
newHandler
|
|
494
|
+
);
|
|
495
|
+
expect(instance.eventHandlers.get('click')).toBe(newHandler);
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
describe('child management', () => {
|
|
500
|
+
it('appendChild adds child to parent', () => {
|
|
501
|
+
const parent = hostConfig.createInstance('ojs-view', {}, null as any, null, null);
|
|
502
|
+
const child = hostConfig.createInstance('ojs-label', { text: 'Hello' }, null as any, null, null);
|
|
503
|
+
|
|
504
|
+
hostConfig.appendChild(parent, child);
|
|
505
|
+
|
|
506
|
+
const parentEl = parent.element as MockVisualElement;
|
|
507
|
+
expect(parentEl.children).toContain(child.element);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it('appendInitialChild adds child during initial render', () => {
|
|
511
|
+
const parent = hostConfig.createInstance('ojs-view', {}, null as any, null, null);
|
|
512
|
+
const child = hostConfig.createInstance('ojs-label', {}, null as any, null, null);
|
|
513
|
+
|
|
514
|
+
hostConfig.appendInitialChild(parent, child);
|
|
515
|
+
|
|
516
|
+
const parentEl = parent.element as MockVisualElement;
|
|
517
|
+
expect(parentEl.children).toContain(child.element);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it('insertBefore inserts child at correct position', () => {
|
|
521
|
+
const parent = hostConfig.createInstance('ojs-view', {}, null as any, null, null);
|
|
522
|
+
const child1 = hostConfig.createInstance('ojs-label', { text: '1' }, null as any, null, null);
|
|
523
|
+
const child2 = hostConfig.createInstance('ojs-label', { text: '2' }, null as any, null, null);
|
|
524
|
+
const child3 = hostConfig.createInstance('ojs-label', { text: '3' }, null as any, null, null);
|
|
525
|
+
|
|
526
|
+
hostConfig.appendChild(parent, child1);
|
|
527
|
+
hostConfig.appendChild(parent, child3);
|
|
528
|
+
hostConfig.insertBefore(parent, child2, child3);
|
|
529
|
+
|
|
530
|
+
const parentEl = parent.element as MockVisualElement;
|
|
531
|
+
expect(parentEl.children[0]).toBe(child1.element);
|
|
532
|
+
expect(parentEl.children[1]).toBe(child2.element);
|
|
533
|
+
expect(parentEl.children[2]).toBe(child3.element);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
it('removeChild removes child from parent', () => {
|
|
537
|
+
const parent = hostConfig.createInstance('ojs-view', {}, null as any, null, null);
|
|
538
|
+
const child = hostConfig.createInstance('ojs-label', {}, null as any, null, null);
|
|
539
|
+
|
|
540
|
+
hostConfig.appendChild(parent, child);
|
|
541
|
+
hostConfig.removeChild(parent, child);
|
|
542
|
+
|
|
543
|
+
const parentEl = parent.element as MockVisualElement;
|
|
544
|
+
expect(parentEl.children).not.toContain(child.element);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it('removeChild cleans up event listeners', () => {
|
|
548
|
+
const handler = vi.fn();
|
|
549
|
+
const parent = hostConfig.createInstance('ojs-view', {}, null as any, null, null);
|
|
550
|
+
const child = hostConfig.createInstance('ojs-button', { onClick: handler }, null as any, null, null);
|
|
551
|
+
|
|
552
|
+
hostConfig.appendChild(parent, child);
|
|
553
|
+
|
|
554
|
+
const eventAPI = getEventAPI();
|
|
555
|
+
eventAPI.removeAllEventListeners.mockClear();
|
|
556
|
+
|
|
557
|
+
hostConfig.removeChild(parent, child);
|
|
558
|
+
|
|
559
|
+
expect(eventAPI.removeAllEventListeners).toHaveBeenCalledWith(child.element);
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
describe('container operations', () => {
|
|
564
|
+
it('appendChildToContainer adds child to container', () => {
|
|
565
|
+
const container = new MockVisualElement('Container');
|
|
566
|
+
const child = hostConfig.createInstance('ojs-view', {}, null as any, null, null);
|
|
567
|
+
|
|
568
|
+
hostConfig.appendChildToContainer(container as any, child);
|
|
569
|
+
|
|
570
|
+
expect(container.children).toContain(child.element);
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it('removeChildFromContainer removes child and cleans up events', () => {
|
|
574
|
+
const container = new MockVisualElement('Container');
|
|
575
|
+
const handler = vi.fn();
|
|
576
|
+
const child = hostConfig.createInstance('ojs-button', { onClick: handler }, null as any, null, null);
|
|
577
|
+
|
|
578
|
+
hostConfig.appendChildToContainer(container as any, child);
|
|
579
|
+
|
|
580
|
+
const eventAPI = getEventAPI();
|
|
581
|
+
eventAPI.removeAllEventListeners.mockClear();
|
|
582
|
+
|
|
583
|
+
hostConfig.removeChildFromContainer(container as any, child);
|
|
584
|
+
|
|
585
|
+
expect(container.children).not.toContain(child.element);
|
|
586
|
+
expect(eventAPI.removeAllEventListeners).toHaveBeenCalledWith(child.element);
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
it('clearContainer removes all children', () => {
|
|
590
|
+
const container = new MockVisualElement('Container');
|
|
591
|
+
const child1 = hostConfig.createInstance('ojs-view', {}, null as any, null, null);
|
|
592
|
+
const child2 = hostConfig.createInstance('ojs-view', {}, null as any, null, null);
|
|
593
|
+
|
|
594
|
+
hostConfig.appendChildToContainer(container as any, child1);
|
|
595
|
+
hostConfig.appendChildToContainer(container as any, child2);
|
|
596
|
+
|
|
597
|
+
expect(container.childCount).toBe(2);
|
|
598
|
+
|
|
599
|
+
hostConfig.clearContainer(container as any);
|
|
600
|
+
|
|
601
|
+
expect(container.childCount).toBe(0);
|
|
602
|
+
});
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
describe('text instances', () => {
|
|
606
|
+
it('createTextInstance creates a Label with text', () => {
|
|
607
|
+
const textInstance = hostConfig.createTextInstance('Hello World', null as any, null, null);
|
|
608
|
+
|
|
609
|
+
expect(textInstance.type).toBe('text');
|
|
610
|
+
expect(textInstance.element.text).toBe('Hello World');
|
|
611
|
+
expect(textInstance.appliedStyleKeys).toBeInstanceOf(Set);
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
it('commitTextUpdate updates the text', () => {
|
|
615
|
+
const textInstance = hostConfig.createTextInstance('Old text', null as any, null, null);
|
|
616
|
+
|
|
617
|
+
hostConfig.commitTextUpdate(textInstance, 'Old text', 'New text');
|
|
618
|
+
|
|
619
|
+
expect(textInstance.element.text).toBe('New text');
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
describe('visibility', () => {
|
|
624
|
+
it('hideInstance sets display to none', () => {
|
|
625
|
+
const instance = hostConfig.createInstance('ojs-view', {}, null as any, null, null);
|
|
626
|
+
|
|
627
|
+
hostConfig.hideInstance(instance);
|
|
628
|
+
|
|
629
|
+
expect(instance.element.style.display).toBe('none');
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
it('unhideInstance clears display', () => {
|
|
633
|
+
const instance = hostConfig.createInstance('ojs-view', {}, null as any, null, null);
|
|
634
|
+
instance.element.style.display = 'none';
|
|
635
|
+
|
|
636
|
+
hostConfig.unhideInstance(instance, {});
|
|
637
|
+
|
|
638
|
+
expect(instance.element.style.display).toBe('');
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
describe('prepareUpdate', () => {
|
|
643
|
+
it('returns true when props differ', () => {
|
|
644
|
+
const instance = hostConfig.createInstance('ojs-view', { style: { width: 100 } }, null as any, null, null);
|
|
645
|
+
|
|
646
|
+
const result = hostConfig.prepareUpdate(
|
|
647
|
+
instance,
|
|
648
|
+
'ojs-view',
|
|
649
|
+
{ style: { width: 100 } },
|
|
650
|
+
{ style: { width: 200 } },
|
|
651
|
+
null as any,
|
|
652
|
+
null
|
|
653
|
+
);
|
|
654
|
+
|
|
655
|
+
expect(result).toBe(true);
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
it('returns null when props are same reference', () => {
|
|
659
|
+
const props = { style: { width: 100 } };
|
|
660
|
+
const instance = hostConfig.createInstance('ojs-view', props, null as any, null, null);
|
|
661
|
+
|
|
662
|
+
const result = hostConfig.prepareUpdate(
|
|
663
|
+
instance,
|
|
664
|
+
'ojs-view',
|
|
665
|
+
props,
|
|
666
|
+
props,
|
|
667
|
+
null as any,
|
|
668
|
+
null
|
|
669
|
+
);
|
|
670
|
+
|
|
671
|
+
expect(result).toBeNull();
|
|
672
|
+
});
|
|
673
|
+
});
|
|
674
|
+
});
|