@openwebf/webf 0.24.1 → 0.24.2

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.
@@ -366,5 +366,49 @@ describe('Analyzer', () => {
366
366
  expect(classObj.props[1].type.value).toBe(FunctionArgumentType.promise);
367
367
  expect(classObj.props[2].type.value).toBe(FunctionArgumentType.int);
368
368
  });
369
+
370
+ it('should preserve complex CustomEvent generic types', () => {
371
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
372
+
373
+ const blob = new IDLBlob('/test/source.d.ts', '/test/target', 'test', 'test');
374
+ blob.raw = `
375
+ interface VideoError {
376
+ code: int;
377
+ message: string;
378
+ }
379
+
380
+ interface Events {
381
+ error: CustomEvent<VideoError>;
382
+ loadedmetadata: CustomEvent<{ duration: double; videoWidth: int; videoHeight: int }>;
383
+ volumechange: CustomEvent<{ volume: double; muted: boolean }>;
384
+ }
385
+ `;
386
+
387
+ const propertyCollector = {
388
+ properties: new Set<string>(),
389
+ files: new Set<string>(),
390
+ interfaces: new Set<string>(),
391
+ };
392
+ const unionTypeCollector = {
393
+ types: new Set<ParameterType[]>(),
394
+ };
395
+
396
+ analyzer(blob, propertyCollector, unionTypeCollector);
397
+
398
+ const eventsObj = blob.objects.find(o => (o as ClassObject).name === 'Events') as ClassObject;
399
+ expect(eventsObj).toBeDefined();
400
+
401
+ const errorProp = eventsObj.props.find(p => p.name === 'error');
402
+ expect(errorProp?.type.value).toBe('CustomEvent<VideoError>');
403
+
404
+ const loadedProp = eventsObj.props.find(p => p.name === 'loadedmetadata');
405
+ expect(loadedProp?.type.value).toBe('CustomEvent<{ duration: number; videoWidth: number; videoHeight: number }>');
406
+
407
+ const volumeProp = eventsObj.props.find(p => p.name === 'volumechange');
408
+ expect(volumeProp?.type.value).toBe('CustomEvent<{ volume: number; muted: boolean }>');
409
+
410
+ expect(warnSpy).not.toHaveBeenCalled();
411
+ warnSpy.mockRestore();
412
+ });
369
413
  });
370
- });
414
+ });
@@ -24,7 +24,7 @@ mockFs.readFileSync = jest.fn().mockImplementation((filePath: any) => {
24
24
  if (pathStr.includes('gitignore.tpl')) return 'gitignore template';
25
25
  if (pathStr.includes('react.package.json.tpl')) return '<%= packageName %> <%= version %> <%= description %>';
26
26
  if (pathStr.includes('react.tsconfig.json.tpl')) return 'react tsconfig';
27
- if (pathStr.includes('react.tsup.config.ts.tpl')) return 'tsup config';
27
+ if (pathStr.includes('react.tsdown.config.ts.tpl')) return 'tsdown config';
28
28
  if (pathStr.includes('react.index.ts.tpl')) return 'index template';
29
29
  if (pathStr.includes('vue.package.json.tpl')) return '<%= packageName %> <%= version %> <%= description %>';
30
30
  if (pathStr.includes('vue.tsconfig.json.tpl')) return 'vue tsconfig';
@@ -456,6 +456,78 @@ describe('Commands', () => {
456
456
  }));
457
457
  });
458
458
 
459
+ it('should copy Flutter README.md into generated React package root', async () => {
460
+ const options = {
461
+ flutterPackageSrc: '/flutter/src',
462
+ framework: 'react',
463
+ packageName: 'test-package'
464
+ };
465
+
466
+ // Mock TypeScript validation
467
+ mockTypeScriptValidation('/flutter/src');
468
+
469
+ const originalExistsSync = mockFs.existsSync as jest.Mock;
470
+ mockFs.existsSync = jest.fn().mockImplementation((filePath: any) => {
471
+ const pathStr = filePath.toString();
472
+ if (pathStr === '/flutter/src/README.md') return true;
473
+ if (pathStr === path.join(path.resolve('/dist'), 'README.md')) return false;
474
+ return originalExistsSync(filePath);
475
+ });
476
+
477
+ const originalReadFileSync = mockFs.readFileSync as jest.Mock;
478
+ mockFs.readFileSync = jest.fn().mockImplementation((filePath: any, encoding?: any) => {
479
+ const pathStr = filePath.toString();
480
+ if (pathStr === '/flutter/src/README.md') {
481
+ return '# Flutter README\n\nHello';
482
+ }
483
+ return originalReadFileSync(filePath, encoding);
484
+ });
485
+
486
+ await generateCommand('/dist', options);
487
+
488
+ expect(mockFs.writeFileSync).toHaveBeenCalledWith(
489
+ path.join(path.resolve('/dist'), 'README.md'),
490
+ '# Flutter README\n\nHello',
491
+ 'utf-8'
492
+ );
493
+ });
494
+
495
+ it('should copy Flutter README.md into generated Vue package root', async () => {
496
+ const options = {
497
+ flutterPackageSrc: '/flutter/src',
498
+ framework: 'vue',
499
+ packageName: 'test-package'
500
+ };
501
+
502
+ // Mock TypeScript validation
503
+ mockTypeScriptValidation('/flutter/src');
504
+
505
+ const originalExistsSync = mockFs.existsSync as jest.Mock;
506
+ mockFs.existsSync = jest.fn().mockImplementation((filePath: any) => {
507
+ const pathStr = filePath.toString();
508
+ if (pathStr === '/flutter/src/README.md') return true;
509
+ if (pathStr === path.join(path.resolve('/dist'), 'README.md')) return false;
510
+ return originalExistsSync(filePath);
511
+ });
512
+
513
+ const originalReadFileSync = mockFs.readFileSync as jest.Mock;
514
+ mockFs.readFileSync = jest.fn().mockImplementation((filePath: any, encoding?: any) => {
515
+ const pathStr = filePath.toString();
516
+ if (pathStr === '/flutter/src/README.md') {
517
+ return '# Flutter README\n\nHello';
518
+ }
519
+ return originalReadFileSync(filePath, encoding);
520
+ });
521
+
522
+ await generateCommand('/dist', options);
523
+
524
+ expect(mockFs.writeFileSync).toHaveBeenCalledWith(
525
+ path.join(path.resolve('/dist'), 'README.md'),
526
+ '# Flutter README\n\nHello',
527
+ 'utf-8'
528
+ );
529
+ });
530
+
459
531
  it('should generate an aggregated README in dist from markdown docs', async () => {
460
532
  const options = {
461
533
  flutterPackageSrc: '/flutter/src',
@@ -751,7 +823,7 @@ describe('Commands', () => {
751
823
  name: 'test-package',
752
824
  version: '1.0.0',
753
825
  dependencies: { react: '^18.0.0' },
754
- scripts: { build: 'tsup' }
826
+ scripts: { build: 'tsdown' }
755
827
  });
756
828
  }
757
829
  return '';
@@ -1228,7 +1300,7 @@ describe('Commands', () => {
1228
1300
  name: 'test-package',
1229
1301
  version: '1.0.0',
1230
1302
  scripts: {
1231
- build: 'tsup'
1303
+ build: 'tsdown'
1232
1304
  },
1233
1305
  dependencies: { react: '^18.0.0' }
1234
1306
  });
@@ -1277,7 +1349,7 @@ describe('Commands', () => {
1277
1349
  name: 'test-package',
1278
1350
  version: '1.0.0',
1279
1351
  scripts: {
1280
- build: 'tsup'
1352
+ build: 'tsdown'
1281
1353
  },
1282
1354
  dependencies: { react: '^18.0.0' }
1283
1355
  });
@@ -395,6 +395,43 @@ describe('Generator', () => {
395
395
  const indexWrite = mockFs.writeFileSync.mock.calls.find(call => call[0].toString().includes('index.ts'));
396
396
  expect(indexWrite).toBeUndefined();
397
397
  });
398
+
399
+ it('should merge type-only Element exports into existing index.ts', async () => {
400
+ mockAnalyzer.analyzer.mockImplementation((blob: any) => {
401
+ const props = new ClassObject();
402
+ props.name = blob.filename === 'test' ? 'TestProperties' : 'ComponentProperties';
403
+ const events = new ClassObject();
404
+ events.name = blob.filename === 'test' ? 'TestEvents' : 'ComponentEvents';
405
+ blob.objects = [props, events];
406
+ });
407
+
408
+ mockFs.existsSync.mockImplementation((p: any) => {
409
+ const s = p.toString();
410
+ if (s.includes(path.join('/test/target', 'src', 'index.ts'))) return true;
411
+ return true;
412
+ });
413
+ mockFs.readFileSync.mockImplementation((p: any) => {
414
+ const s = p.toString();
415
+ if (s.includes(path.join('/test/target', 'src', 'index.ts'))) {
416
+ return '/* empty index scaffold */\n';
417
+ }
418
+ return 'test content';
419
+ });
420
+
421
+ await reactGen({
422
+ source: '/test/source',
423
+ target: '/test/target',
424
+ command: 'test command'
425
+ });
426
+
427
+ const indexWrite = mockFs.writeFileSync.mock.calls.find(call => call[0].toString().includes('index.ts'));
428
+ expect(indexWrite).toBeDefined();
429
+ const content = String(indexWrite![1]);
430
+ expect(content).toContain('export { Test }');
431
+ expect(content).toContain('export type { TestElement }');
432
+ expect(content).toContain('export { Component }');
433
+ expect(content).toContain('export type { ComponentElement }');
434
+ });
398
435
  });
399
436
 
400
437
  describe('vueGen', () => {
@@ -184,6 +184,11 @@ describe('React Generator', () => {
184
184
  // Should include custom props with generated TS types
185
185
  expect(result).toContain('title: __webfTypes.dom_string;');
186
186
  expect(result).toContain('disabled?: __webfTypes.boolean;');
187
+
188
+ // Element interface should surface properties for refs
189
+ expect(result).toContain('export interface TestComponentElement extends WebFElementWithMethods<{');
190
+ expect(result).toContain('title: __webfTypes.dom_string;');
191
+ expect(result).toContain('disabled?: __webfTypes.boolean;');
187
192
 
188
193
  // And still include standard HTML props
189
194
  expect(result).toContain('id?: string;');