@openwebf/webf 0.23.7 → 0.24.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 +28 -0
- package/bin/webf.js +12 -1
- package/dist/commands.js +375 -9
- package/dist/generator.js +39 -37
- package/dist/module.js +487 -0
- package/dist/peerDeps.js +27 -0
- package/dist/react.js +10 -18
- package/dist/vue.js +138 -117
- package/package.json +2 -2
- package/src/commands.ts +441 -11
- package/src/generator.ts +41 -40
- package/src/module.ts +632 -0
- package/src/peerDeps.ts +21 -0
- package/src/react.ts +10 -18
- package/src/vue.ts +158 -128
- 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/react.component.tsx.tpl +2 -2
- package/templates/react.index.ts.tpl +2 -1
- package/templates/react.package.json.tpl +4 -5
- package/templates/react.tsconfig.json.tpl +8 -1
- package/templates/react.tsup.config.ts.tpl +1 -1
- package/templates/vue.component.partial.tpl +4 -4
- package/templates/vue.components.d.ts.tpl +24 -9
- package/templates/vue.package.json.tpl +4 -2
- package/test/commands.test.ts +86 -19
- package/test/generator.test.ts +33 -24
- package/test/peerDeps.test.ts +30 -0
- package/test/react-consts.test.ts +9 -3
- package/test/standard-props.test.ts +14 -14
- package/test/templates.test.ts +17 -0
- package/test/vue.test.ts +36 -11
- package/dist/constants.js +0 -242
package/test/commands.test.ts
CHANGED
|
@@ -2,18 +2,20 @@
|
|
|
2
2
|
jest.mock('fs');
|
|
3
3
|
jest.mock('child_process');
|
|
4
4
|
jest.mock('../src/generator');
|
|
5
|
-
jest.mock('glob')
|
|
5
|
+
jest.mock('glob', () => ({
|
|
6
|
+
globSync: jest.fn(),
|
|
7
|
+
}));
|
|
6
8
|
jest.mock('inquirer');
|
|
7
9
|
jest.mock('yaml');
|
|
8
10
|
|
|
9
11
|
import fs from 'fs';
|
|
10
12
|
import path from 'path';
|
|
11
13
|
import { spawnSync } from 'child_process';
|
|
12
|
-
import {
|
|
14
|
+
import { globSync } from 'glob';
|
|
13
15
|
|
|
14
16
|
const mockFs = fs as jest.Mocked<typeof fs>;
|
|
15
17
|
const mockSpawnSync = spawnSync as jest.MockedFunction<typeof spawnSync>;
|
|
16
|
-
const
|
|
18
|
+
const mockGlobSync = globSync as jest.MockedFunction<typeof globSync>;
|
|
17
19
|
|
|
18
20
|
// Set up default mocks before importing commands
|
|
19
21
|
mockFs.readFileSync = jest.fn().mockImplementation((filePath: any) => {
|
|
@@ -87,7 +89,7 @@ describe('Commands', () => {
|
|
|
87
89
|
});
|
|
88
90
|
// Default mock for readdirSync to avoid undefined
|
|
89
91
|
mockFs.readdirSync.mockReturnValue([] as any);
|
|
90
|
-
|
|
92
|
+
mockGlobSync.mockReturnValue([]);
|
|
91
93
|
});
|
|
92
94
|
|
|
93
95
|
|
|
@@ -224,12 +226,12 @@ describe('Commands', () => {
|
|
|
224
226
|
// Mock that required files don't exist
|
|
225
227
|
mockFs.existsSync.mockReturnValue(false);
|
|
226
228
|
|
|
227
|
-
|
|
229
|
+
await generateCommand(target, options);
|
|
228
230
|
|
|
229
231
|
expect(mockSpawnSync).toHaveBeenCalledWith(
|
|
230
232
|
expect.stringMatching(/npm(\.cmd)?/),
|
|
231
|
-
['install', '--
|
|
232
|
-
{ cwd: target, stdio: 'inherit' }
|
|
233
|
+
['install', '--production=false'],
|
|
234
|
+
expect.objectContaining({ cwd: target, stdio: 'inherit' })
|
|
233
235
|
);
|
|
234
236
|
});
|
|
235
237
|
});
|
|
@@ -281,18 +283,11 @@ describe('Commands', () => {
|
|
|
281
283
|
|
|
282
284
|
await generateCommand(target, options);
|
|
283
285
|
|
|
284
|
-
// Should install
|
|
285
|
-
expect(mockSpawnSync).toHaveBeenCalledWith(
|
|
286
|
-
expect.stringMatching(/npm(\.cmd)?/),
|
|
287
|
-
['install', '@openwebf/webf-enterprise-typings'],
|
|
288
|
-
{ cwd: target, stdio: 'inherit' }
|
|
289
|
-
);
|
|
290
|
-
|
|
291
|
-
// Should install Vue 3 as dev dependency
|
|
286
|
+
// Should install dependencies (including devDependencies) from package.json
|
|
292
287
|
expect(mockSpawnSync).toHaveBeenCalledWith(
|
|
293
288
|
expect.stringMatching(/npm(\.cmd)?/),
|
|
294
|
-
['install', '
|
|
295
|
-
{ cwd: target, stdio: 'inherit' }
|
|
289
|
+
['install', '--production=false'],
|
|
290
|
+
expect.objectContaining({ cwd: target, stdio: 'inherit' })
|
|
296
291
|
);
|
|
297
292
|
});
|
|
298
293
|
});
|
|
@@ -461,6 +456,78 @@ describe('Commands', () => {
|
|
|
461
456
|
}));
|
|
462
457
|
});
|
|
463
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
|
+
|
|
464
531
|
it('should generate an aggregated README in dist from markdown docs', async () => {
|
|
465
532
|
const options = {
|
|
466
533
|
flutterPackageSrc: '/flutter/src',
|
|
@@ -471,8 +538,8 @@ describe('Commands', () => {
|
|
|
471
538
|
// Mock TypeScript validation
|
|
472
539
|
mockTypeScriptValidation('/flutter/src');
|
|
473
540
|
|
|
474
|
-
|
|
475
|
-
|
|
541
|
+
// Mock .d.ts files so copyMarkdownDocsToDist sees at least one entry
|
|
542
|
+
mockGlobSync.mockReturnValue(['lib/src/alert.d.ts'] as any);
|
|
476
543
|
|
|
477
544
|
const originalExistsSync = mockFs.existsSync as jest.Mock;
|
|
478
545
|
mockFs.existsSync = jest.fn().mockImplementation((filePath: any) => {
|
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
|
|
@@ -231,23 +233,17 @@ describe('Generator', () => {
|
|
|
231
233
|
command: 'test command'
|
|
232
234
|
});
|
|
233
235
|
|
|
234
|
-
//
|
|
236
|
+
// Dart codegen does not write a root index.d.ts; it copies .d.ts files alongside generated Dart bindings.
|
|
235
237
|
const writeFileCalls = mockFs.writeFileSync.mock.calls;
|
|
236
238
|
const indexDtsCall = writeFileCalls.find(call =>
|
|
237
239
|
call[0].toString().endsWith('index.d.ts')
|
|
238
240
|
);
|
|
239
241
|
|
|
240
|
-
expect(indexDtsCall).
|
|
241
|
-
expect(indexDtsCall![1]).toContain('/// <reference path="./global.d.ts" />');
|
|
242
|
-
expect(indexDtsCall![1]).toContain('/// <reference path="./components/button.d.ts" />');
|
|
243
|
-
expect(indexDtsCall![1]).toContain('/// <reference path="./widgets/card.d.ts" />');
|
|
244
|
-
expect(indexDtsCall![1]).toContain("export * from './components/button';");
|
|
245
|
-
expect(indexDtsCall![1]).toContain("export * from './widgets/card';");
|
|
246
|
-
expect(indexDtsCall![1]).toContain('TypeScript Definitions');
|
|
242
|
+
expect(indexDtsCall).toBeUndefined();
|
|
247
243
|
});
|
|
248
244
|
|
|
249
245
|
it('should copy original .d.ts files to output directory', async () => {
|
|
250
|
-
|
|
246
|
+
mockGlobSync.mockReturnValue(['test.d.ts']);
|
|
251
247
|
const originalContent = 'interface Original {}';
|
|
252
248
|
mockFs.readFileSync.mockReturnValue(originalContent);
|
|
253
249
|
mockFs.existsSync.mockImplementation((path) => {
|
|
@@ -279,7 +275,7 @@ describe('Generator', () => {
|
|
|
279
275
|
exclude: ['**/test/**', '**/docs/**']
|
|
280
276
|
});
|
|
281
277
|
|
|
282
|
-
expect(
|
|
278
|
+
expect(mockGlobSync).toHaveBeenCalledWith('**/*.d.ts', {
|
|
283
279
|
cwd: '/test/source',
|
|
284
280
|
ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**', '**/test/**', '**/docs/**']
|
|
285
281
|
});
|
|
@@ -299,6 +295,19 @@ describe('Generator', () => {
|
|
|
299
295
|
expect(mockFs.writeFileSync).toHaveBeenCalled();
|
|
300
296
|
});
|
|
301
297
|
|
|
298
|
+
it('should always generate src/types.ts for React output', async () => {
|
|
299
|
+
await reactGen({
|
|
300
|
+
source: '/test/source',
|
|
301
|
+
target: '/test/target',
|
|
302
|
+
command: 'test command'
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const writeCalls = mockFs.writeFileSync.mock.calls;
|
|
306
|
+
const typesCall = writeCalls.find(call => /[\\/]src[\\/]types\.ts$/.test(call[0].toString()));
|
|
307
|
+
expect(typesCall).toBeDefined();
|
|
308
|
+
expect(typesCall![1]).toContain('export {};');
|
|
309
|
+
});
|
|
310
|
+
|
|
302
311
|
it('should use the exact target directory specified', async () => {
|
|
303
312
|
await reactGen({
|
|
304
313
|
source: '/test/source',
|
|
@@ -419,7 +428,7 @@ describe('Generator', () => {
|
|
|
419
428
|
});
|
|
420
429
|
|
|
421
430
|
it('should only generate one index.d.ts file', async () => {
|
|
422
|
-
|
|
431
|
+
mockGlobSync.mockReturnValue(['comp1.d.ts', 'comp2.d.ts', 'comp3.d.ts']);
|
|
423
432
|
|
|
424
433
|
await vueGen({
|
|
425
434
|
source: '/test/source',
|
|
@@ -436,7 +445,7 @@ describe('Generator', () => {
|
|
|
436
445
|
|
|
437
446
|
describe('Error handling', () => {
|
|
438
447
|
it('should handle glob errors', async () => {
|
|
439
|
-
|
|
448
|
+
mockGlobSync.mockImplementation(() => {
|
|
440
449
|
throw new Error('Glob error');
|
|
441
450
|
});
|
|
442
451
|
|
|
@@ -476,8 +485,8 @@ describe('Generator', () => {
|
|
|
476
485
|
// First file fails (no writes), second file succeeds (1 dart + 1 .d.ts), plus index.d.ts = 3 total
|
|
477
486
|
// But since the error happens in dartGen, the .d.ts copy might not happen
|
|
478
487
|
const writeCalls = mockFs.writeFileSync.mock.calls;
|
|
479
|
-
// Should have at least written the successful
|
|
480
|
-
expect(writeCalls.length).toBeGreaterThanOrEqual(
|
|
488
|
+
// Should have at least written the successful Dart bindings file
|
|
489
|
+
expect(writeCalls.length).toBeGreaterThanOrEqual(1);
|
|
481
490
|
});
|
|
482
491
|
});
|
|
483
492
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
import { getPackageTypesFileFromDir, isPackageTypesReady } from '../src/peerDeps';
|
|
5
|
+
|
|
6
|
+
function makeTempDir(prefix: string): string {
|
|
7
|
+
return fs.mkdtempSync(path.join(process.cwd(), 'test', prefix));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe('peerDeps', () => {
|
|
11
|
+
it('treats package as not ready when declared types file is missing', () => {
|
|
12
|
+
const dir = makeTempDir('peerDeps-');
|
|
13
|
+
const distDir = path.join(dir, 'dist');
|
|
14
|
+
try {
|
|
15
|
+
fs.mkdirSync(distDir, { recursive: true });
|
|
16
|
+
fs.writeFileSync(
|
|
17
|
+
path.join(dir, 'package.json'),
|
|
18
|
+
JSON.stringify({ name: '@openwebf/react-core-ui', types: 'dist/index.d.ts' }, null, 2)
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
expect(getPackageTypesFileFromDir(dir)).toBe(path.join(dir, 'dist', 'index.d.ts'));
|
|
22
|
+
expect(isPackageTypesReady(dir)).toBe(false);
|
|
23
|
+
|
|
24
|
+
fs.writeFileSync(path.join(distDir, 'index.d.ts'), 'export {};');
|
|
25
|
+
expect(isPackageTypesReady(dir)).toBe(true);
|
|
26
|
+
} finally {
|
|
27
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { generateReactComponent } from '../src/react';
|
|
2
2
|
import { IDLBlob } from '../src/IDLBlob';
|
|
3
|
-
import { ConstObject, EnumObject, EnumMemberObject } from '../src/declaration';
|
|
3
|
+
import { ClassObject, ClassObjectKind, ConstObject, EnumObject, EnumMemberObject } from '../src/declaration';
|
|
4
4
|
|
|
5
5
|
describe('React generator - declare const support', () => {
|
|
6
6
|
it('emits export declare const for constants from typings', () => {
|
|
7
7
|
const blob = new IDLBlob('/test/source', '/test/target', 'ConstOnly', 'test', '');
|
|
8
|
+
const properties = new ClassObject();
|
|
9
|
+
properties.name = 'TestComponentProperties';
|
|
10
|
+
properties.kind = ClassObjectKind.interface;
|
|
8
11
|
const constObj = new ConstObject();
|
|
9
12
|
constObj.name = 'WEBF_CUPERTINO_SYMBOL';
|
|
10
13
|
constObj.type = 'unique symbol';
|
|
11
14
|
|
|
12
|
-
blob.objects = [constObj as any];
|
|
15
|
+
blob.objects = [properties, constObj as any];
|
|
13
16
|
|
|
14
17
|
const output = generateReactComponent(blob);
|
|
15
18
|
expect(output).toContain('export declare const WEBF_CUPERTINO_SYMBOL: unique symbol;');
|
|
@@ -17,12 +20,15 @@ describe('React generator - declare const support', () => {
|
|
|
17
20
|
|
|
18
21
|
it('emits export declare enum for enums from typings', () => {
|
|
19
22
|
const blob = new IDLBlob('/test/source', '/test/target', 'EnumOnly', 'test', '');
|
|
23
|
+
const properties = new ClassObject();
|
|
24
|
+
properties.name = 'TestComponentProperties';
|
|
25
|
+
properties.kind = ClassObjectKind.interface;
|
|
20
26
|
const eo = new EnumObject();
|
|
21
27
|
eo.name = 'CupertinoColors';
|
|
22
28
|
const m1 = new EnumMemberObject(); m1.name = "'red'"; m1.initializer = '0x0f';
|
|
23
29
|
const m2 = new EnumMemberObject(); m2.name = "'bbb'"; m2.initializer = '0x00';
|
|
24
30
|
eo.members = [m1, m2];
|
|
25
|
-
blob.objects = [eo as any];
|
|
31
|
+
blob.objects = [properties, eo as any];
|
|
26
32
|
|
|
27
33
|
const output = generateReactComponent(blob);
|
|
28
34
|
expect(output).toContain("export enum CupertinoColors { 'red' = 0x0f, 'bbb' = 0x00 }");
|
|
@@ -49,13 +49,13 @@ describe('Standard HTML Props Generation', () => {
|
|
|
49
49
|
const propsContent = result.substring(propsStart, propsEnd);
|
|
50
50
|
|
|
51
51
|
// Verify order: custom props, event handlers, then standard HTML props
|
|
52
|
-
const labelIndex = propsContent.
|
|
53
|
-
const variantIndex = propsContent.
|
|
54
|
-
const onClickIndex = propsContent.
|
|
55
|
-
const idIndex = propsContent.
|
|
56
|
-
const styleIndex = propsContent.
|
|
57
|
-
const childrenIndex = propsContent.
|
|
58
|
-
const classNameIndex = propsContent.
|
|
52
|
+
const labelIndex = propsContent.search(/\n\s*label\??:\s*/);
|
|
53
|
+
const variantIndex = propsContent.search(/\n\s*variant\??:\s*/);
|
|
54
|
+
const onClickIndex = propsContent.search(/\n\s*onClick\??:\s*/);
|
|
55
|
+
const idIndex = propsContent.search(/\n\s*id\??:\s*/);
|
|
56
|
+
const styleIndex = propsContent.search(/\n\s*style\??:\s*/);
|
|
57
|
+
const childrenIndex = propsContent.search(/\n\s*children\??:\s*/);
|
|
58
|
+
const classNameIndex = propsContent.search(/\n\s*className\??:\s*/);
|
|
59
59
|
|
|
60
60
|
// All props should exist
|
|
61
61
|
expect(labelIndex).toBeGreaterThan(-1);
|
|
@@ -131,8 +131,8 @@ describe('Standard HTML Props Generation', () => {
|
|
|
131
131
|
|
|
132
132
|
// Standard HTML props
|
|
133
133
|
expect(propsContent).toContain("'id'?: string;");
|
|
134
|
-
expect(propsContent).toContain("'class'?:
|
|
135
|
-
expect(propsContent).toContain("'style'?:
|
|
134
|
+
expect(propsContent).toContain("'class'?: ClassValue;");
|
|
135
|
+
expect(propsContent).toContain("'style'?: StyleValue;");
|
|
136
136
|
});
|
|
137
137
|
|
|
138
138
|
it('should handle Vue style prop with both string and object types', () => {
|
|
@@ -145,8 +145,8 @@ describe('Standard HTML Props Generation', () => {
|
|
|
145
145
|
|
|
146
146
|
const result = generateVueTypings([blob]);
|
|
147
147
|
|
|
148
|
-
// Vue style prop should accept
|
|
149
|
-
expect(result).toMatch(/'style'\?:
|
|
148
|
+
// Vue style prop should accept Vue's supported style value forms
|
|
149
|
+
expect(result).toMatch(/'style'\?: StyleValue;/);
|
|
150
150
|
});
|
|
151
151
|
});
|
|
152
152
|
|
|
@@ -176,15 +176,15 @@ describe('Standard HTML Props Generation', () => {
|
|
|
176
176
|
|
|
177
177
|
// Both should have style prop (with appropriate types)
|
|
178
178
|
expect(reactResult).toContain('style?: React.CSSProperties;');
|
|
179
|
-
expect(vueResult).toContain("'style'?:
|
|
179
|
+
expect(vueResult).toContain("'style'?: StyleValue;");
|
|
180
180
|
|
|
181
181
|
// React has className, Vue has class
|
|
182
182
|
expect(reactResult).toContain('className?: string;');
|
|
183
|
-
expect(vueResult).toContain("'class'?:
|
|
183
|
+
expect(vueResult).toContain("'class'?: ClassValue;");
|
|
184
184
|
|
|
185
185
|
// React has children, Vue uses slots (not in props)
|
|
186
186
|
expect(reactResult).toContain('children?: React.ReactNode;');
|
|
187
187
|
expect(vueResult).not.toContain('children');
|
|
188
188
|
});
|
|
189
189
|
});
|
|
190
|
-
});
|
|
190
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
describe('Templates', () => {
|
|
5
|
+
it('react tsconfig template pins React type resolution to root @types', () => {
|
|
6
|
+
const templatePath = path.resolve(__dirname, '../templates/react.tsconfig.json.tpl');
|
|
7
|
+
const template = fs.readFileSync(templatePath, 'utf8');
|
|
8
|
+
|
|
9
|
+
expect(template).toContain('"baseUrl": "."');
|
|
10
|
+
expect(template).toContain('"paths"');
|
|
11
|
+
expect(template).toContain('"react": ["./node_modules/@types/react/index.d.ts"]');
|
|
12
|
+
expect(template).toContain('"react-dom": ["./node_modules/@types/react-dom/index.d.ts"]');
|
|
13
|
+
expect(template).toContain('"react/jsx-runtime": ["./node_modules/@types/react/jsx-runtime.d.ts"]');
|
|
14
|
+
expect(template).toContain('"react/jsx-dev-runtime": ["./node_modules/@types/react/jsx-dev-runtime.d.ts"]');
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
package/test/vue.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { generateVueTypings } from '../src/vue';
|
|
2
2
|
import { IDLBlob } from '../src/IDLBlob';
|
|
3
|
-
import { ClassObject, ClassObjectKind, PropsDeclaration, ConstObject } from '../src/declaration';
|
|
3
|
+
import { ClassObject, ClassObjectKind, PropsDeclaration, ConstObject, TypeAliasObject } from '../src/declaration';
|
|
4
4
|
|
|
5
5
|
describe('Vue Generator', () => {
|
|
6
6
|
describe('generateVueTypings', () => {
|
|
@@ -16,13 +16,16 @@ describe('Vue Generator', () => {
|
|
|
16
16
|
|
|
17
17
|
// Should include standard HTML props in Props type
|
|
18
18
|
expect(result).toContain("'id'?: string;");
|
|
19
|
-
expect(result).toContain("'class'?:
|
|
20
|
-
expect(result).toContain("'style'?:
|
|
19
|
+
expect(result).toContain("'class'?: ClassValue;");
|
|
20
|
+
expect(result).toContain("'style'?: StyleValue;");
|
|
21
21
|
|
|
22
22
|
// Should generate proper type exports
|
|
23
23
|
expect(result).toContain('export type TestComponentProps = {');
|
|
24
24
|
expect(result).toContain('export interface TestComponentElement {');
|
|
25
25
|
expect(result).toContain('export type TestComponentEvents = {');
|
|
26
|
+
|
|
27
|
+
// Should not rely on a non-existent internal __webfTypes import
|
|
28
|
+
expect(result).not.toContain('__webfTypes');
|
|
26
29
|
});
|
|
27
30
|
|
|
28
31
|
it('should include standard HTML props along with custom properties', () => {
|
|
@@ -58,8 +61,8 @@ describe('Vue Generator', () => {
|
|
|
58
61
|
|
|
59
62
|
// And still include standard HTML props
|
|
60
63
|
expect(result).toContain("'id'?: string;");
|
|
61
|
-
expect(result).toContain("'class'?:
|
|
62
|
-
expect(result).toContain("'style'?:
|
|
64
|
+
expect(result).toContain("'class'?: ClassValue;");
|
|
65
|
+
expect(result).toContain("'style'?: StyleValue;");
|
|
63
66
|
});
|
|
64
67
|
|
|
65
68
|
it('should generate proper Vue component declarations', () => {
|
|
@@ -73,11 +76,20 @@ describe('Vue Generator', () => {
|
|
|
73
76
|
const result = generateVueTypings([blob]);
|
|
74
77
|
|
|
75
78
|
// Should generate proper component declarations
|
|
76
|
-
expect(result).toContain("declare module 'vue' {");
|
|
79
|
+
expect(result).toContain("declare module '@vue/runtime-core' {");
|
|
80
|
+
expect(result).toContain("interface GlobalDirectives {");
|
|
81
|
+
expect(result).toContain('vFlutterAttached: typeof flutterAttached;');
|
|
77
82
|
expect(result).toContain("interface GlobalComponents {");
|
|
78
|
-
expect(result).toContain("'
|
|
83
|
+
expect(result).toContain("'webf-list-view': DefineCustomElement<");
|
|
84
|
+
expect(result).not.toContain("'web-f-list-view': DefineCustomElement<");
|
|
85
|
+
expect(result).toContain("WebFListViewElement,");
|
|
79
86
|
expect(result).toContain("WebFListViewProps,");
|
|
80
87
|
expect(result).toContain("WebFListViewEvents");
|
|
88
|
+
|
|
89
|
+
// Compatibility module for older tooling
|
|
90
|
+
expect(result).toContain("declare module 'vue' {");
|
|
91
|
+
expect(result).toContain("interface GlobalDirectives extends import('@vue/runtime-core').GlobalDirectives {}");
|
|
92
|
+
expect(result).toContain("interface GlobalComponents extends import('@vue/runtime-core').GlobalComponents {}");
|
|
81
93
|
});
|
|
82
94
|
|
|
83
95
|
it('should handle multiple components', () => {
|
|
@@ -145,13 +157,13 @@ describe('Vue Generator', () => {
|
|
|
145
157
|
|
|
146
158
|
// Should include event types
|
|
147
159
|
expect(result).toContain('export type TestComponentEvents = {');
|
|
148
|
-
expect(result).toContain('close
|
|
149
|
-
expect(result).toContain('refresh
|
|
160
|
+
expect(result).toContain('close: Event;');
|
|
161
|
+
expect(result).toContain('refresh: CustomEvent;');
|
|
150
162
|
|
|
151
163
|
// Props should still have standard HTML props
|
|
152
164
|
expect(result).toContain("'id'?: string;");
|
|
153
|
-
expect(result).toContain("'class'?:
|
|
154
|
-
expect(result).toContain("'style'?:
|
|
165
|
+
expect(result).toContain("'class'?: ClassValue;");
|
|
166
|
+
expect(result).toContain("'style'?: StyleValue;");
|
|
155
167
|
});
|
|
156
168
|
|
|
157
169
|
it('should include declare const variables as exported declarations', () => {
|
|
@@ -168,6 +180,19 @@ describe('Vue Generator', () => {
|
|
|
168
180
|
expect(result).toContain('export declare const WEBF_UNIQUE: unique symbol;');
|
|
169
181
|
});
|
|
170
182
|
|
|
183
|
+
it('should include type aliases as exported declarations', () => {
|
|
184
|
+
const blob = new IDLBlob('/test/source', '/test/target', 'TypeAliasOnly', 'test', '');
|
|
185
|
+
|
|
186
|
+
const typeAlias = new TypeAliasObject();
|
|
187
|
+
typeAlias.name = 'MyAlias';
|
|
188
|
+
typeAlias.type = 'string | number';
|
|
189
|
+
|
|
190
|
+
blob.objects = [typeAlias as any];
|
|
191
|
+
|
|
192
|
+
const result = generateVueTypings([blob]);
|
|
193
|
+
expect(result).toContain('export type MyAlias = string | number;');
|
|
194
|
+
});
|
|
195
|
+
|
|
171
196
|
it('should include declare enum as exported declaration', () => {
|
|
172
197
|
const blob = new IDLBlob('/test/source', '/test/target', 'EnumOnly', 'test', '');
|
|
173
198
|
// Build a minimal faux EnumObject via analyzer by simulating ast is heavy; create a shape
|