@openwebf/webf 0.23.2 → 0.23.10
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 +33 -1
- package/bin/webf.js +13 -1
- package/dist/analyzer.js +65 -1
- package/dist/commands.js +437 -99
- package/dist/dart.js +91 -25
- package/dist/declaration.js +1 -0
- package/dist/generator.js +28 -18
- package/dist/module.js +458 -0
- package/dist/react.js +272 -25
- package/dist/vue.js +89 -11
- package/package.json +2 -2
- package/src/analyzer.ts +58 -2
- package/src/commands.ts +587 -199
- package/src/dart.ts +95 -20
- package/src/declaration.ts +1 -0
- package/src/generator.ts +27 -19
- package/src/module.ts +600 -0
- package/src/react.ts +288 -29
- package/src/vue.ts +100 -13
- package/templates/class.dart.tpl +1 -1
- package/templates/module.package.json.tpl +36 -0
- package/templates/module.tsconfig.json.tpl +25 -0
- package/templates/module.tsup.config.ts.tpl +13 -0
- package/templates/vue.components.d.ts.tpl +2 -0
- package/test/commands.test.ts +86 -4
- package/test/dart-nullable-props.test.ts +58 -0
- package/test/generator.test.ts +16 -14
- package/test/react-consts.test.ts +1 -1
- package/test/react-vue-nullable-props.test.ts +66 -0
- package/test/react.test.ts +46 -4
package/test/commands.test.ts
CHANGED
|
@@ -2,15 +2,20 @@
|
|
|
2
2
|
jest.mock('fs');
|
|
3
3
|
jest.mock('child_process');
|
|
4
4
|
jest.mock('../src/generator');
|
|
5
|
+
jest.mock('glob', () => ({
|
|
6
|
+
globSync: jest.fn(),
|
|
7
|
+
}));
|
|
5
8
|
jest.mock('inquirer');
|
|
6
9
|
jest.mock('yaml');
|
|
7
10
|
|
|
8
11
|
import fs from 'fs';
|
|
9
12
|
import path from 'path';
|
|
10
13
|
import { spawnSync } from 'child_process';
|
|
14
|
+
import { globSync } from 'glob';
|
|
11
15
|
|
|
12
16
|
const mockFs = fs as jest.Mocked<typeof fs>;
|
|
13
17
|
const mockSpawnSync = spawnSync as jest.MockedFunction<typeof spawnSync>;
|
|
18
|
+
const mockGlobSync = globSync as jest.MockedFunction<typeof globSync>;
|
|
14
19
|
|
|
15
20
|
// Set up default mocks before importing commands
|
|
16
21
|
mockFs.readFileSync = jest.fn().mockImplementation((filePath: any) => {
|
|
@@ -84,6 +89,7 @@ describe('Commands', () => {
|
|
|
84
89
|
});
|
|
85
90
|
// Default mock for readdirSync to avoid undefined
|
|
86
91
|
mockFs.readdirSync.mockReturnValue([] as any);
|
|
92
|
+
mockGlobSync.mockReturnValue([]);
|
|
87
93
|
});
|
|
88
94
|
|
|
89
95
|
|
|
@@ -220,11 +226,11 @@ describe('Commands', () => {
|
|
|
220
226
|
// Mock that required files don't exist
|
|
221
227
|
mockFs.existsSync.mockReturnValue(false);
|
|
222
228
|
|
|
223
|
-
|
|
229
|
+
await generateCommand(target, options);
|
|
224
230
|
|
|
225
231
|
expect(mockSpawnSync).toHaveBeenCalledWith(
|
|
226
232
|
expect.stringMatching(/npm(\.cmd)?/),
|
|
227
|
-
['install'
|
|
233
|
+
['install'],
|
|
228
234
|
{ cwd: target, stdio: 'inherit' }
|
|
229
235
|
);
|
|
230
236
|
});
|
|
@@ -356,6 +362,36 @@ describe('Commands', () => {
|
|
|
356
362
|
consoleSpy.mockRestore();
|
|
357
363
|
});
|
|
358
364
|
|
|
365
|
+
it('should generate only Dart bindings when dartOnly is set', async () => {
|
|
366
|
+
const options = {
|
|
367
|
+
flutterPackageSrc: '/flutter/src',
|
|
368
|
+
dartOnly: true,
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// Mock TypeScript validation
|
|
372
|
+
mockTypeScriptValidation('/flutter/src');
|
|
373
|
+
|
|
374
|
+
await generateCommand('/dist', options as any);
|
|
375
|
+
|
|
376
|
+
// Should call dartGen for the Flutter package, but not reactGen/vueGen
|
|
377
|
+
expect(mockGenerator.dartGen).toHaveBeenCalledWith({
|
|
378
|
+
source: '/flutter/src',
|
|
379
|
+
target: '/flutter/src',
|
|
380
|
+
command: expect.stringContaining('webf codegen'),
|
|
381
|
+
exclude: undefined,
|
|
382
|
+
});
|
|
383
|
+
expect(mockGenerator.reactGen).not.toHaveBeenCalled();
|
|
384
|
+
expect(mockGenerator.vueGen).not.toHaveBeenCalled();
|
|
385
|
+
|
|
386
|
+
// Should not attempt to build or run npm scripts
|
|
387
|
+
const spawnCalls = (mockSpawnSync as jest.Mock).mock.calls;
|
|
388
|
+
const buildOrPublishCalls = spawnCalls.filter(call => {
|
|
389
|
+
const args = call[1] as any;
|
|
390
|
+
return Array.isArray(args) && (args.includes('run') || args.includes('publish') || args.includes('install'));
|
|
391
|
+
});
|
|
392
|
+
expect(buildOrPublishCalls).toHaveLength(0);
|
|
393
|
+
});
|
|
394
|
+
|
|
359
395
|
it('should show instructions when --flutter-package-src is missing', async () => {
|
|
360
396
|
const options = { framework: 'react' };
|
|
361
397
|
|
|
@@ -420,11 +456,57 @@ describe('Commands', () => {
|
|
|
420
456
|
|
|
421
457
|
await generateCommand('/dist', options);
|
|
422
458
|
|
|
423
|
-
expect(mockGenerator.reactGen).toHaveBeenCalledWith({
|
|
459
|
+
expect(mockGenerator.reactGen).toHaveBeenCalledWith(expect.objectContaining({
|
|
424
460
|
source: '/flutter/src',
|
|
425
461
|
target: path.resolve('/dist'),
|
|
426
462
|
command: expect.stringContaining('webf codegen')
|
|
463
|
+
}));
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it('should generate an aggregated README in dist from markdown docs', async () => {
|
|
467
|
+
const options = {
|
|
468
|
+
flutterPackageSrc: '/flutter/src',
|
|
469
|
+
framework: 'react',
|
|
470
|
+
packageName: 'test-package'
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
// Mock TypeScript validation
|
|
474
|
+
mockTypeScriptValidation('/flutter/src');
|
|
475
|
+
|
|
476
|
+
// Mock .d.ts files so copyMarkdownDocsToDist sees at least one entry
|
|
477
|
+
mockGlobSync.mockReturnValue(['lib/src/alert.d.ts'] as any);
|
|
478
|
+
|
|
479
|
+
const originalExistsSync = mockFs.existsSync as jest.Mock;
|
|
480
|
+
mockFs.existsSync = jest.fn().mockImplementation((filePath: any) => {
|
|
481
|
+
const pathStr = filePath.toString();
|
|
482
|
+
const distRoot = path.join(path.resolve('/dist'), 'dist');
|
|
483
|
+
if (pathStr === distRoot) return true;
|
|
484
|
+
if (pathStr === path.join(path.resolve('/dist'), 'package.json')) return true;
|
|
485
|
+
if (pathStr === path.join(path.resolve('/dist'), 'global.d.ts')) return true;
|
|
486
|
+
if (pathStr === path.join(path.resolve('/dist'), 'tsconfig.json')) return true;
|
|
487
|
+
if (pathStr.endsWith('.md') && pathStr.includes('/flutter/src')) return true;
|
|
488
|
+
return originalExistsSync(filePath);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
// Ensure that reading a markdown file returns some content
|
|
492
|
+
const originalReadFileSync = mockFs.readFileSync as jest.Mock;
|
|
493
|
+
mockFs.readFileSync = jest.fn().mockImplementation((filePath: any, encoding?: any) => {
|
|
494
|
+
const pathStr = filePath.toString();
|
|
495
|
+
if (pathStr.endsWith('.md')) {
|
|
496
|
+
return '# Test Component\n\nThis is a test component doc.';
|
|
497
|
+
}
|
|
498
|
+
return originalReadFileSync(filePath, encoding);
|
|
427
499
|
});
|
|
500
|
+
|
|
501
|
+
await generateCommand('/dist', options);
|
|
502
|
+
|
|
503
|
+
// README.md should be written into dist directory
|
|
504
|
+
const writeCalls = (mockFs.writeFileSync as jest.Mock).mock.calls;
|
|
505
|
+
const readmeCall = writeCalls.find(call => {
|
|
506
|
+
const p = call[0].toString();
|
|
507
|
+
return p.endsWith(path.join('dist', 'README.md'));
|
|
508
|
+
});
|
|
509
|
+
expect(readmeCall).toBeDefined();
|
|
428
510
|
});
|
|
429
511
|
|
|
430
512
|
it('should create new project if package.json not found', async () => {
|
|
@@ -1245,4 +1327,4 @@ describe('Commands', () => {
|
|
|
1245
1327
|
});
|
|
1246
1328
|
});
|
|
1247
1329
|
});
|
|
1248
|
-
});
|
|
1330
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { analyzer, clearCaches, UnionTypeCollector, ParameterType } from '../src/analyzer';
|
|
2
|
+
import { generateDartClass } from '../src/dart';
|
|
3
|
+
import { IDLBlob } from '../src/IDLBlob';
|
|
4
|
+
|
|
5
|
+
describe('Dart nullable union properties', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
clearCaches();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('handles boolean | null properties and maps "null" attribute to null', () => {
|
|
11
|
+
const tsContent = `
|
|
12
|
+
interface FlutterCupertinoCheckboxProperties {
|
|
13
|
+
/**
|
|
14
|
+
* Whether the checkbox is checked.
|
|
15
|
+
* Default: false.
|
|
16
|
+
*/
|
|
17
|
+
checked?: boolean | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface FlutterCupertinoCheckboxEvents {
|
|
21
|
+
/**
|
|
22
|
+
* Fired when the checkbox value changes.
|
|
23
|
+
*/
|
|
24
|
+
change: CustomEvent<boolean>;
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
const blob = new IDLBlob('checkbox.d.ts', 'dist', 'checkbox', 'implement');
|
|
29
|
+
blob.raw = tsContent;
|
|
30
|
+
|
|
31
|
+
const definedPropertyCollector = {
|
|
32
|
+
properties: new Set<string>(),
|
|
33
|
+
files: new Set<string>(),
|
|
34
|
+
interfaces: new Set<string>(),
|
|
35
|
+
};
|
|
36
|
+
const unionTypeCollector: UnionTypeCollector = { types: new Set<ParameterType[]>() };
|
|
37
|
+
|
|
38
|
+
analyzer(blob, definedPropertyCollector, unionTypeCollector);
|
|
39
|
+
|
|
40
|
+
const dartCode = generateDartClass(blob, 'test');
|
|
41
|
+
|
|
42
|
+
// Should generate bindings class for FlutterCupertinoCheckbox
|
|
43
|
+
expect(dartCode).toContain('abstract class FlutterCupertinoCheckboxBindings extends WidgetElement {');
|
|
44
|
+
|
|
45
|
+
// The checked property should be a nullable bool in Dart bindings.
|
|
46
|
+
expect(dartCode).toContain('bool? get checked;');
|
|
47
|
+
|
|
48
|
+
// Attribute setter should treat the literal "null" as a Dart null.
|
|
49
|
+
expect(dartCode).toContain(
|
|
50
|
+
"setter: (value) => checked = value == 'null' ? null : (value == 'true' || value == ''),"
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Deleting the attribute should reset to the default `false`.
|
|
54
|
+
expect(dartCode).toContain(
|
|
55
|
+
"deleter: () => checked = false"
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
});
|
package/test/generator.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import {
|
|
3
|
+
import { globSync } from 'glob';
|
|
4
4
|
import { dartGen, reactGen, vueGen, clearAllCaches } from '../src/generator';
|
|
5
5
|
import * as analyzer from '../src/analyzer';
|
|
6
6
|
import * as dartGenerator from '../src/dart';
|
|
@@ -10,7 +10,9 @@ import { ClassObject } from '../src/declaration';
|
|
|
10
10
|
|
|
11
11
|
// Mock dependencies
|
|
12
12
|
jest.mock('fs');
|
|
13
|
-
jest.mock('glob')
|
|
13
|
+
jest.mock('glob', () => ({
|
|
14
|
+
globSync: jest.fn(),
|
|
15
|
+
}));
|
|
14
16
|
jest.mock('../src/analyzer');
|
|
15
17
|
jest.mock('../src/dart');
|
|
16
18
|
jest.mock('../src/react');
|
|
@@ -47,7 +49,7 @@ jest.mock('../src/logger', () => ({
|
|
|
47
49
|
}));
|
|
48
50
|
|
|
49
51
|
const mockFs = fs as jest.Mocked<typeof fs>;
|
|
50
|
-
const
|
|
52
|
+
const mockGlobSync = globSync as jest.MockedFunction<typeof globSync>;
|
|
51
53
|
const mockAnalyzer = analyzer as jest.Mocked<typeof analyzer>;
|
|
52
54
|
const mockDartGenerator = dartGenerator as jest.Mocked<typeof dartGenerator>;
|
|
53
55
|
const mockReactGenerator = reactGenerator as jest.Mocked<typeof reactGenerator>;
|
|
@@ -65,7 +67,7 @@ describe('Generator', () => {
|
|
|
65
67
|
mockFs.writeFileSync.mockImplementation(() => undefined);
|
|
66
68
|
mockFs.mkdirSync.mockImplementation(() => undefined);
|
|
67
69
|
|
|
68
|
-
|
|
70
|
+
mockGlobSync.mockReturnValue(['test.d.ts', 'component.d.ts']);
|
|
69
71
|
|
|
70
72
|
mockAnalyzer.analyzer.mockImplementation(() => undefined);
|
|
71
73
|
mockAnalyzer.clearCaches.mockImplementation(() => undefined);
|
|
@@ -84,7 +86,7 @@ describe('Generator', () => {
|
|
|
84
86
|
command: 'test command'
|
|
85
87
|
});
|
|
86
88
|
|
|
87
|
-
expect(
|
|
89
|
+
expect(mockGlobSync).toHaveBeenCalledWith('**/*.d.ts', {
|
|
88
90
|
cwd: '/test/source',
|
|
89
91
|
ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**']
|
|
90
92
|
});
|
|
@@ -101,14 +103,14 @@ describe('Generator', () => {
|
|
|
101
103
|
command: 'test command'
|
|
102
104
|
});
|
|
103
105
|
|
|
104
|
-
expect(
|
|
106
|
+
expect(mockGlobSync).toHaveBeenCalledWith('**/*.d.ts', {
|
|
105
107
|
cwd: expect.stringContaining('relative/source'),
|
|
106
108
|
ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**']
|
|
107
109
|
});
|
|
108
110
|
});
|
|
109
111
|
|
|
110
112
|
it('should filter out global.d.ts files', async () => {
|
|
111
|
-
|
|
113
|
+
mockGlobSync.mockReturnValue(['test.d.ts', 'global.d.ts', 'component.d.ts']);
|
|
112
114
|
|
|
113
115
|
await dartGen({
|
|
114
116
|
source: '/test/source',
|
|
@@ -122,7 +124,7 @@ describe('Generator', () => {
|
|
|
122
124
|
});
|
|
123
125
|
|
|
124
126
|
it('should handle empty type files', async () => {
|
|
125
|
-
|
|
127
|
+
mockGlobSync.mockReturnValue([]);
|
|
126
128
|
|
|
127
129
|
await dartGen({
|
|
128
130
|
source: '/test/source',
|
|
@@ -135,7 +137,7 @@ describe('Generator', () => {
|
|
|
135
137
|
});
|
|
136
138
|
|
|
137
139
|
it('should continue processing if one file fails', async () => {
|
|
138
|
-
|
|
140
|
+
mockGlobSync.mockReturnValue(['test1.d.ts', 'test2.d.ts']);
|
|
139
141
|
mockAnalyzer.analyzer
|
|
140
142
|
.mockImplementationOnce(() => { throw new Error('Parse error'); })
|
|
141
143
|
.mockImplementationOnce(() => undefined);
|
|
@@ -216,7 +218,7 @@ describe('Generator', () => {
|
|
|
216
218
|
});
|
|
217
219
|
|
|
218
220
|
it('should generate index.d.ts with references and exports', async () => {
|
|
219
|
-
|
|
221
|
+
mockGlobSync.mockReturnValue(['components/button.d.ts', 'widgets/card.d.ts']);
|
|
220
222
|
mockFs.readFileSync.mockReturnValue('interface Test {}');
|
|
221
223
|
mockFs.existsSync.mockImplementation((path) => {
|
|
222
224
|
// Source directory exists
|
|
@@ -247,7 +249,7 @@ describe('Generator', () => {
|
|
|
247
249
|
});
|
|
248
250
|
|
|
249
251
|
it('should copy original .d.ts files to output directory', async () => {
|
|
250
|
-
|
|
252
|
+
mockGlobSync.mockReturnValue(['test.d.ts']);
|
|
251
253
|
const originalContent = 'interface Original {}';
|
|
252
254
|
mockFs.readFileSync.mockReturnValue(originalContent);
|
|
253
255
|
mockFs.existsSync.mockImplementation((path) => {
|
|
@@ -279,7 +281,7 @@ describe('Generator', () => {
|
|
|
279
281
|
exclude: ['**/test/**', '**/docs/**']
|
|
280
282
|
});
|
|
281
283
|
|
|
282
|
-
expect(
|
|
284
|
+
expect(mockGlobSync).toHaveBeenCalledWith('**/*.d.ts', {
|
|
283
285
|
cwd: '/test/source',
|
|
284
286
|
ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**', '**/test/**', '**/docs/**']
|
|
285
287
|
});
|
|
@@ -419,7 +421,7 @@ describe('Generator', () => {
|
|
|
419
421
|
});
|
|
420
422
|
|
|
421
423
|
it('should only generate one index.d.ts file', async () => {
|
|
422
|
-
|
|
424
|
+
mockGlobSync.mockReturnValue(['comp1.d.ts', 'comp2.d.ts', 'comp3.d.ts']);
|
|
423
425
|
|
|
424
426
|
await vueGen({
|
|
425
427
|
source: '/test/source',
|
|
@@ -436,7 +438,7 @@ describe('Generator', () => {
|
|
|
436
438
|
|
|
437
439
|
describe('Error handling', () => {
|
|
438
440
|
it('should handle glob errors', async () => {
|
|
439
|
-
|
|
441
|
+
mockGlobSync.mockImplementation(() => {
|
|
440
442
|
throw new Error('Glob error');
|
|
441
443
|
});
|
|
442
444
|
|
|
@@ -25,6 +25,6 @@ describe('React generator - declare const support', () => {
|
|
|
25
25
|
blob.objects = [eo as any];
|
|
26
26
|
|
|
27
27
|
const output = generateReactComponent(blob);
|
|
28
|
-
expect(output).toContain("export
|
|
28
|
+
expect(output).toContain("export enum CupertinoColors { 'red' = 0x0f, 'bbb' = 0x00 }");
|
|
29
29
|
});
|
|
30
30
|
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { analyzer, clearCaches, UnionTypeCollector, ParameterType } from '../src/analyzer';
|
|
2
|
+
import { generateReactComponent } from '../src/react';
|
|
3
|
+
import { generateVueTypings } from '../src/vue';
|
|
4
|
+
import { IDLBlob } from '../src/IDLBlob';
|
|
5
|
+
|
|
6
|
+
describe('React/Vue nullable union props', () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
clearCaches();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const tsContent = `
|
|
12
|
+
interface FlutterCupertinoCheckboxProperties {
|
|
13
|
+
/**
|
|
14
|
+
* Whether the checkbox is checked.
|
|
15
|
+
* Default: false.
|
|
16
|
+
*/
|
|
17
|
+
checked?: boolean | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface FlutterCupertinoCheckboxEvents {
|
|
21
|
+
/**
|
|
22
|
+
* Fired when the checkbox value changes.
|
|
23
|
+
*/
|
|
24
|
+
change: CustomEvent<boolean>;
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
it('emits boolean | null for React props', () => {
|
|
29
|
+
const blob = new IDLBlob('checkbox.d.ts', 'dist', 'checkbox', 'implement');
|
|
30
|
+
blob.raw = tsContent;
|
|
31
|
+
|
|
32
|
+
const definedPropertyCollector = {
|
|
33
|
+
properties: new Set<string>(),
|
|
34
|
+
files: new Set<string>(),
|
|
35
|
+
interfaces: new Set<string>(),
|
|
36
|
+
};
|
|
37
|
+
const unionTypeCollector: UnionTypeCollector = { types: new Set<ParameterType[]>() };
|
|
38
|
+
|
|
39
|
+
analyzer(blob, definedPropertyCollector, unionTypeCollector);
|
|
40
|
+
|
|
41
|
+
const reactCode = generateReactComponent(blob);
|
|
42
|
+
|
|
43
|
+
// Prop should allow both undefined and null
|
|
44
|
+
expect(reactCode).toContain('checked?: boolean | null;');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('emits boolean | null for Vue props', () => {
|
|
48
|
+
const blob = new IDLBlob('checkbox.d.ts', 'dist', 'checkbox', 'implement');
|
|
49
|
+
blob.raw = tsContent;
|
|
50
|
+
|
|
51
|
+
const definedPropertyCollector = {
|
|
52
|
+
properties: new Set<string>(),
|
|
53
|
+
files: new Set<string>(),
|
|
54
|
+
interfaces: new Set<string>(),
|
|
55
|
+
};
|
|
56
|
+
const unionTypeCollector: UnionTypeCollector = { types: new Set<ParameterType[]>() };
|
|
57
|
+
|
|
58
|
+
analyzer(blob, definedPropertyCollector, unionTypeCollector);
|
|
59
|
+
|
|
60
|
+
const vueCode = generateVueTypings([blob]);
|
|
61
|
+
|
|
62
|
+
// Vue prop name is kebab-cased and should allow null explicitly
|
|
63
|
+
expect(vueCode).toContain(`'checked'?: boolean | null;`);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
package/test/react.test.ts
CHANGED
|
@@ -181,9 +181,9 @@ describe('React Generator', () => {
|
|
|
181
181
|
|
|
182
182
|
const result = generateReactComponent(blob);
|
|
183
183
|
|
|
184
|
-
// Should include custom props
|
|
185
|
-
expect(result).toContain('title: dom_string;');
|
|
186
|
-
expect(result).toContain('disabled?: boolean;');
|
|
184
|
+
// Should include custom props with generated TS types
|
|
185
|
+
expect(result).toContain('title: __webfTypes.dom_string;');
|
|
186
|
+
expect(result).toContain('disabled?: __webfTypes.boolean;');
|
|
187
187
|
|
|
188
188
|
// And still include standard HTML props
|
|
189
189
|
expect(result).toContain('id?: string;');
|
|
@@ -191,5 +191,47 @@ describe('React Generator', () => {
|
|
|
191
191
|
expect(result).toContain('children?: React.ReactNode;');
|
|
192
192
|
expect(result).toContain('className?: string;');
|
|
193
193
|
});
|
|
194
|
+
|
|
195
|
+
it('should preserve JSDoc for supporting option interfaces', () => {
|
|
196
|
+
const blob = new IDLBlob('/test/source', '/test/target', 'TestComponent', 'test', '');
|
|
197
|
+
|
|
198
|
+
const properties = new ClassObject();
|
|
199
|
+
properties.name = 'TestComponentProperties';
|
|
200
|
+
properties.kind = ClassObjectKind.interface;
|
|
201
|
+
|
|
202
|
+
const options = new ClassObject();
|
|
203
|
+
options.name = 'TestComponentOptions';
|
|
204
|
+
options.kind = ClassObjectKind.interface;
|
|
205
|
+
|
|
206
|
+
const titleProp = new PropsDeclaration();
|
|
207
|
+
titleProp.name = 'title';
|
|
208
|
+
titleProp.type = { value: 'string' } as any;
|
|
209
|
+
titleProp.optional = true;
|
|
210
|
+
titleProp.documentation = 'Optional override title for this show() call.';
|
|
211
|
+
titleProp.readonly = false;
|
|
212
|
+
titleProp.typeMode = {};
|
|
213
|
+
|
|
214
|
+
const messageProp = new PropsDeclaration();
|
|
215
|
+
messageProp.name = 'message';
|
|
216
|
+
messageProp.type = { value: 'string' } as any;
|
|
217
|
+
messageProp.optional = true;
|
|
218
|
+
messageProp.documentation = 'Optional override message for this show() call.';
|
|
219
|
+
messageProp.readonly = false;
|
|
220
|
+
messageProp.typeMode = {};
|
|
221
|
+
|
|
222
|
+
options.props = [titleProp, messageProp];
|
|
223
|
+
|
|
224
|
+
blob.objects = [properties, options];
|
|
225
|
+
|
|
226
|
+
const result = generateReactComponent(blob);
|
|
227
|
+
|
|
228
|
+
expect(result).toContain('interface TestComponentOptions');
|
|
229
|
+
expect(result).toMatch(
|
|
230
|
+
/interface TestComponentOptions[\s\S]*?\/\*\*[\s\S]*?Optional override title for this show\(\) call\.[\s\S]*?\*\/\s*\n\s*title\?:/
|
|
231
|
+
);
|
|
232
|
+
expect(result).toMatch(
|
|
233
|
+
/interface TestComponentOptions[\s\S]*?\/\*\*[\s\S]*?Optional override message for this show\(\) call\.[\s\S]*?\*\/\s*\n\s*message\?:/
|
|
234
|
+
);
|
|
235
|
+
});
|
|
194
236
|
});
|
|
195
|
-
});
|
|
237
|
+
});
|