@openwebf/webf 0.22.0 → 0.22.3
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/TYPING_GUIDE.md +208 -0
- package/bin/webf.js +1 -0
- package/package.json +1 -1
- package/src/IDLBlob.ts +2 -2
- package/src/analyzer.ts +19 -1
- package/src/commands.ts +201 -22
- package/src/dart.ts +36 -6
- package/src/declaration.ts +5 -0
- package/src/generator.ts +82 -14
- package/src/react.ts +197 -62
- package/templates/class.dart.tpl +3 -2
- package/templates/gitignore.tpl +8 -1
- package/templates/react.component.tsx.tpl +78 -26
- package/templates/react.index.ts.tpl +0 -1
- package/templates/react.package.json.tpl +3 -0
- package/test/commands.test.ts +0 -5
- package/test/generator.test.ts +29 -8
- package/test/packageName.test.ts +231 -0
- package/test/react.test.ts +94 -9
- package/templates/react.createComponent.tpl +0 -286
package/test/commands.test.ts
CHANGED
|
@@ -20,7 +20,6 @@ mockFs.readFileSync = jest.fn().mockImplementation((filePath: any) => {
|
|
|
20
20
|
if (pathStr.includes('react.package.json.tpl')) return '<%= packageName %> <%= version %> <%= description %>';
|
|
21
21
|
if (pathStr.includes('react.tsconfig.json.tpl')) return 'react tsconfig';
|
|
22
22
|
if (pathStr.includes('react.tsup.config.ts.tpl')) return 'tsup config';
|
|
23
|
-
if (pathStr.includes('react.createComponent.tpl')) return 'create component';
|
|
24
23
|
if (pathStr.includes('react.index.ts.tpl')) return 'index template';
|
|
25
24
|
if (pathStr.includes('vue.package.json.tpl')) return '<%= packageName %> <%= version %> <%= description %>';
|
|
26
25
|
if (pathStr.includes('vue.tsconfig.json.tpl')) return 'vue tsconfig';
|
|
@@ -127,10 +126,6 @@ describe('Commands', () => {
|
|
|
127
126
|
path.join(path.resolve(target), 'src'),
|
|
128
127
|
{ recursive: true }
|
|
129
128
|
);
|
|
130
|
-
expect(mockFs.mkdirSync).toHaveBeenCalledWith(
|
|
131
|
-
path.join(path.resolve(target), 'src', 'utils'),
|
|
132
|
-
{ recursive: true }
|
|
133
|
-
);
|
|
134
129
|
});
|
|
135
130
|
|
|
136
131
|
it('should prompt for framework and package name when missing', async () => {
|
package/test/generator.test.ts
CHANGED
|
@@ -72,7 +72,7 @@ describe('Generator', () => {
|
|
|
72
72
|
|
|
73
73
|
mockDartGenerator.generateDartClass.mockReturnValue('dart code');
|
|
74
74
|
mockReactGenerator.generateReactComponent.mockReturnValue('react component');
|
|
75
|
-
mockReactGenerator.generateReactIndex.mockReturnValue('export
|
|
75
|
+
mockReactGenerator.generateReactIndex.mockReturnValue('export { Component, ComponentElement } from "./lib/src/html/component";');
|
|
76
76
|
mockVueGenerator.generateVueTypings.mockReturnValue('vue typings');
|
|
77
77
|
});
|
|
78
78
|
|
|
@@ -86,7 +86,7 @@ describe('Generator', () => {
|
|
|
86
86
|
|
|
87
87
|
expect(mockGlob.globSync).toHaveBeenCalledWith('**/*.d.ts', {
|
|
88
88
|
cwd: '/test/source',
|
|
89
|
-
ignore: ['**/node_modules/**', '**/dist/**', '**/build/**']
|
|
89
|
+
ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**']
|
|
90
90
|
});
|
|
91
91
|
|
|
92
92
|
expect(mockAnalyzer.analyzer).toHaveBeenCalledTimes(2); // For each file
|
|
@@ -103,7 +103,7 @@ describe('Generator', () => {
|
|
|
103
103
|
|
|
104
104
|
expect(mockGlob.globSync).toHaveBeenCalledWith('**/*.d.ts', {
|
|
105
105
|
cwd: expect.stringContaining('relative/source'),
|
|
106
|
-
ignore: ['**/node_modules/**', '**/dist/**', '**/build/**']
|
|
106
|
+
ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**']
|
|
107
107
|
});
|
|
108
108
|
});
|
|
109
109
|
|
|
@@ -270,6 +270,20 @@ describe('Generator', () => {
|
|
|
270
270
|
'utf-8'
|
|
271
271
|
);
|
|
272
272
|
});
|
|
273
|
+
|
|
274
|
+
it('should handle custom exclude patterns', async () => {
|
|
275
|
+
await dartGen({
|
|
276
|
+
source: '/test/source',
|
|
277
|
+
target: '/test/target',
|
|
278
|
+
command: 'test command',
|
|
279
|
+
exclude: ['**/test/**', '**/docs/**']
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
expect(mockGlob.globSync).toHaveBeenCalledWith('**/*.d.ts', {
|
|
283
|
+
cwd: '/test/source',
|
|
284
|
+
ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**', '**/test/**', '**/docs/**']
|
|
285
|
+
});
|
|
286
|
+
});
|
|
273
287
|
});
|
|
274
288
|
|
|
275
289
|
describe('reactGen', () => {
|
|
@@ -317,6 +331,12 @@ describe('Generator', () => {
|
|
|
317
331
|
});
|
|
318
332
|
|
|
319
333
|
it('should generate index file', async () => {
|
|
334
|
+
// Update the mock to return proper format with export statements
|
|
335
|
+
mockReactGenerator.generateReactIndex.mockReturnValue(
|
|
336
|
+
'export { Test, TestElement } from "./lib/src/html/test";\n' +
|
|
337
|
+
'export { Component, ComponentElement } from "./lib/src/html/component";'
|
|
338
|
+
);
|
|
339
|
+
|
|
320
340
|
await reactGen({
|
|
321
341
|
source: '/test/source',
|
|
322
342
|
target: '/test/target',
|
|
@@ -330,11 +350,12 @@ describe('Generator', () => {
|
|
|
330
350
|
])
|
|
331
351
|
);
|
|
332
352
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
);
|
|
353
|
+
// Check that index.ts was written (the content check is done by looking at the mock return value)
|
|
354
|
+
const writeCalls = mockFs.writeFileSync.mock.calls;
|
|
355
|
+
const indexCall = writeCalls.find(call => call[0].toString().includes('index.ts'));
|
|
356
|
+
expect(indexCall).toBeDefined();
|
|
357
|
+
expect(indexCall![1]).toContain('export { Test, TestElement }');
|
|
358
|
+
expect(indexCall![1]).toContain('export { Component, ComponentElement }');
|
|
338
359
|
});
|
|
339
360
|
});
|
|
340
361
|
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// Mock fs module before importing commands
|
|
2
|
+
jest.mock('fs');
|
|
3
|
+
|
|
4
|
+
// Import the functions we want to test (we'll need to export them first)
|
|
5
|
+
// For now, let's just define them here for testing
|
|
6
|
+
function sanitizePackageName(name: string): string {
|
|
7
|
+
// Remove any leading/trailing whitespace
|
|
8
|
+
let sanitized = name.trim();
|
|
9
|
+
|
|
10
|
+
// Check if it's a scoped package
|
|
11
|
+
const isScoped = sanitized.startsWith('@');
|
|
12
|
+
let scope = '';
|
|
13
|
+
let packageName = sanitized;
|
|
14
|
+
|
|
15
|
+
if (isScoped) {
|
|
16
|
+
const parts = sanitized.split('/');
|
|
17
|
+
if (parts.length >= 2) {
|
|
18
|
+
scope = parts[0];
|
|
19
|
+
packageName = parts.slice(1).join('/');
|
|
20
|
+
} else {
|
|
21
|
+
// Invalid scoped package, treat as regular
|
|
22
|
+
packageName = sanitized.substring(1);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Sanitize scope if present
|
|
27
|
+
if (scope) {
|
|
28
|
+
scope = scope.toLowerCase();
|
|
29
|
+
// Remove invalid characters from scope (keep only @ and alphanumeric/hyphen)
|
|
30
|
+
scope = scope.replace(/[^@a-z0-9-]/g, '');
|
|
31
|
+
if (scope === '@') {
|
|
32
|
+
scope = '@pkg'; // Default scope if only @ remains
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Sanitize package name part
|
|
37
|
+
packageName = packageName.toLowerCase();
|
|
38
|
+
packageName = packageName.replace(/\s+/g, '-');
|
|
39
|
+
packageName = packageName.replace(/[^a-z0-9\-_.]/g, '');
|
|
40
|
+
packageName = packageName.replace(/^[._]+/, '');
|
|
41
|
+
packageName = packageName.replace(/[._]+$/, '');
|
|
42
|
+
packageName = packageName.replace(/[-_.]{2,}/g, '-');
|
|
43
|
+
packageName = packageName.replace(/^-+/, '').replace(/-+$/, '');
|
|
44
|
+
|
|
45
|
+
// Ensure package name is not empty
|
|
46
|
+
if (!packageName) {
|
|
47
|
+
packageName = 'package';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Ensure it starts with a letter or number
|
|
51
|
+
if (!/^[a-z0-9]/.test(packageName)) {
|
|
52
|
+
packageName = 'pkg-' + packageName;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Combine scope and package name
|
|
56
|
+
let result = scope ? `${scope}/${packageName}` : packageName;
|
|
57
|
+
|
|
58
|
+
// Truncate to 214 characters (npm limit)
|
|
59
|
+
if (result.length > 214) {
|
|
60
|
+
if (scope) {
|
|
61
|
+
// Try to preserve scope
|
|
62
|
+
const maxPackageLength = 214 - scope.length - 1; // -1 for the /
|
|
63
|
+
packageName = packageName.substring(0, maxPackageLength);
|
|
64
|
+
packageName = packageName.replace(/[._-]+$/, '');
|
|
65
|
+
result = `${scope}/${packageName}`;
|
|
66
|
+
} else {
|
|
67
|
+
result = result.substring(0, 214);
|
|
68
|
+
result = result.replace(/[._-]+$/, '');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isValidNpmPackageName(name: string): boolean {
|
|
76
|
+
// Check basic rules
|
|
77
|
+
if (!name || name.length === 0 || name.length > 214) return false;
|
|
78
|
+
if (name.trim() !== name) return false;
|
|
79
|
+
|
|
80
|
+
// Check if it's a scoped package
|
|
81
|
+
if (name.startsWith('@')) {
|
|
82
|
+
const parts = name.split('/');
|
|
83
|
+
if (parts.length !== 2) return false; // Scoped packages must have exactly one /
|
|
84
|
+
|
|
85
|
+
const scope = parts[0];
|
|
86
|
+
const packageName = parts[1];
|
|
87
|
+
|
|
88
|
+
// Validate scope
|
|
89
|
+
if (!/^@[a-z0-9][a-z0-9-]*$/.test(scope)) return false;
|
|
90
|
+
|
|
91
|
+
// Validate package name part
|
|
92
|
+
return isValidNpmPackageName(packageName);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// For non-scoped packages
|
|
96
|
+
if (name !== name.toLowerCase()) return false;
|
|
97
|
+
if (name.startsWith('.') || name.startsWith('_')) return false;
|
|
98
|
+
|
|
99
|
+
// Check for valid characters (letters, numbers, hyphens, underscores, dots)
|
|
100
|
+
if (!/^[a-z0-9][a-z0-9\-_.]*$/.test(name)) return false;
|
|
101
|
+
|
|
102
|
+
// Check for URL-safe characters
|
|
103
|
+
try {
|
|
104
|
+
if (encodeURIComponent(name) !== name) return false;
|
|
105
|
+
} catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
describe('npm package name sanitization', () => {
|
|
113
|
+
describe('sanitizePackageName', () => {
|
|
114
|
+
it('should convert uppercase to lowercase', () => {
|
|
115
|
+
expect(sanitizePackageName('MyPackage')).toBe('mypackage');
|
|
116
|
+
expect(sanitizePackageName('HELLO-WORLD')).toBe('hello-world');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should replace spaces with hyphens', () => {
|
|
120
|
+
expect(sanitizePackageName('my package')).toBe('my-package');
|
|
121
|
+
expect(sanitizePackageName('hello world app')).toBe('hello-world-app');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should remove invalid characters', () => {
|
|
125
|
+
expect(sanitizePackageName('my@package!')).toBe('mypackage');
|
|
126
|
+
expect(sanitizePackageName('hello#world$')).toBe('helloworld');
|
|
127
|
+
expect(sanitizePackageName('test(1)_package')).toBe('test1_package');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should remove leading dots and underscores', () => {
|
|
131
|
+
expect(sanitizePackageName('.package')).toBe('package');
|
|
132
|
+
expect(sanitizePackageName('_package')).toBe('package');
|
|
133
|
+
expect(sanitizePackageName('...package')).toBe('package');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should remove trailing dots and underscores', () => {
|
|
137
|
+
expect(sanitizePackageName('package.')).toBe('package');
|
|
138
|
+
expect(sanitizePackageName('package_')).toBe('package');
|
|
139
|
+
expect(sanitizePackageName('package...')).toBe('package');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should handle consecutive special characters', () => {
|
|
143
|
+
expect(sanitizePackageName('my--package')).toBe('my-package');
|
|
144
|
+
expect(sanitizePackageName('hello___world')).toBe('hello-world');
|
|
145
|
+
expect(sanitizePackageName('test...app')).toBe('test-app');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should ensure package starts with letter or number', () => {
|
|
149
|
+
expect(sanitizePackageName('-package')).toBe('package');
|
|
150
|
+
expect(sanitizePackageName('--package')).toBe('package');
|
|
151
|
+
expect(sanitizePackageName('_test')).toBe('test');
|
|
152
|
+
expect(sanitizePackageName('.app')).toBe('app');
|
|
153
|
+
expect(sanitizePackageName('---')).toBe('package'); // all special chars removed, falls back to default
|
|
154
|
+
expect(sanitizePackageName('-')).toBe('package'); // single hyphen removed, falls back to default
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should handle empty or invalid input', () => {
|
|
158
|
+
expect(sanitizePackageName('')).toBe('package');
|
|
159
|
+
expect(sanitizePackageName(' ')).toBe('package');
|
|
160
|
+
expect(sanitizePackageName('___')).toBe('package');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should truncate long names', () => {
|
|
164
|
+
const longName = 'a'.repeat(220);
|
|
165
|
+
const result = sanitizePackageName(longName);
|
|
166
|
+
expect(result.length).toBeLessThanOrEqual(214);
|
|
167
|
+
expect(result).toBe('a'.repeat(214));
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should handle Flutter package names', () => {
|
|
171
|
+
expect(sanitizePackageName('webf_cupertino_ui')).toBe('webf_cupertino_ui');
|
|
172
|
+
expect(sanitizePackageName('WebF Cupertino UI')).toBe('webf-cupertino-ui');
|
|
173
|
+
expect(sanitizePackageName('flutter_package_name')).toBe('flutter_package_name');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should handle scoped packages', () => {
|
|
177
|
+
expect(sanitizePackageName('@openwebf/react-cupertino-ui')).toBe('@openwebf/react-cupertino-ui');
|
|
178
|
+
expect(sanitizePackageName('@OpenWebF/React-Cupertino-UI')).toBe('@openwebf/react-cupertino-ui');
|
|
179
|
+
expect(sanitizePackageName('@my-org/my-package')).toBe('@my-org/my-package');
|
|
180
|
+
expect(sanitizePackageName(' @scope/package ')).toBe('@scope/package');
|
|
181
|
+
expect(sanitizePackageName('@SCOPE/PACKAGE')).toBe('@scope/package');
|
|
182
|
+
expect(sanitizePackageName('@/package')).toBe('@pkg/package'); // Invalid scope
|
|
183
|
+
expect(sanitizePackageName('@123/package')).toBe('@123/package'); // Numeric scope is valid
|
|
184
|
+
expect(sanitizePackageName('@scope/my package')).toBe('@scope/my-package');
|
|
185
|
+
expect(sanitizePackageName('@scope/.package')).toBe('@scope/package');
|
|
186
|
+
expect(sanitizePackageName('@scope/_package')).toBe('@scope/package');
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('isValidNpmPackageName', () => {
|
|
191
|
+
it('should accept valid package names', () => {
|
|
192
|
+
expect(isValidNpmPackageName('mypackage')).toBe(true);
|
|
193
|
+
expect(isValidNpmPackageName('my-package')).toBe(true);
|
|
194
|
+
expect(isValidNpmPackageName('my_package')).toBe(true);
|
|
195
|
+
expect(isValidNpmPackageName('my.package')).toBe(true);
|
|
196
|
+
expect(isValidNpmPackageName('package123')).toBe(true);
|
|
197
|
+
expect(isValidNpmPackageName('@openwebf/react-cupertino-ui')).toBe(true);
|
|
198
|
+
expect(isValidNpmPackageName('@scope/package')).toBe(true);
|
|
199
|
+
expect(isValidNpmPackageName('@my-org/my-package')).toBe(true);
|
|
200
|
+
expect(isValidNpmPackageName('@123/package')).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should reject invalid package names', () => {
|
|
204
|
+
expect(isValidNpmPackageName('MyPackage')).toBe(false); // uppercase
|
|
205
|
+
expect(isValidNpmPackageName('my package')).toBe(false); // space
|
|
206
|
+
expect(isValidNpmPackageName('.package')).toBe(false); // starts with dot
|
|
207
|
+
expect(isValidNpmPackageName('_package')).toBe(false); // starts with underscore
|
|
208
|
+
expect(isValidNpmPackageName('my@package')).toBe(false); // special char in wrong position
|
|
209
|
+
expect(isValidNpmPackageName('')).toBe(false); // empty
|
|
210
|
+
expect(isValidNpmPackageName(' package ')).toBe(false); // leading/trailing space
|
|
211
|
+
expect(isValidNpmPackageName('@')).toBe(false); // just @
|
|
212
|
+
expect(isValidNpmPackageName('@/')).toBe(false); // missing both parts
|
|
213
|
+
expect(isValidNpmPackageName('@scope')).toBe(false); // missing package name
|
|
214
|
+
expect(isValidNpmPackageName('@/package')).toBe(false); // missing scope name
|
|
215
|
+
expect(isValidNpmPackageName('@Scope/package')).toBe(false); // uppercase in scope
|
|
216
|
+
expect(isValidNpmPackageName('@scope/Package')).toBe(false); // uppercase in package
|
|
217
|
+
expect(isValidNpmPackageName('@scope/.package')).toBe(false); // package starts with dot
|
|
218
|
+
expect(isValidNpmPackageName('@scope/_package')).toBe(false); // package starts with underscore
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should reject names that are too long', () => {
|
|
222
|
+
const longName = 'a'.repeat(215);
|
|
223
|
+
expect(isValidNpmPackageName(longName)).toBe(false);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should reject non-URL-safe names', () => {
|
|
227
|
+
expect(isValidNpmPackageName('my package')).toBe(false);
|
|
228
|
+
expect(isValidNpmPackageName('package?name')).toBe(false);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
});
|
package/test/react.test.ts
CHANGED
|
@@ -2,9 +2,32 @@ import { generateReactComponent } from '../src/react';
|
|
|
2
2
|
import { IDLBlob } from '../src/IDLBlob';
|
|
3
3
|
import { ClassObject, ClassObjectKind } from '../src/declaration';
|
|
4
4
|
|
|
5
|
+
// Import the toWebFTagName function for testing
|
|
6
|
+
import { toWebFTagName } from '../src/react';
|
|
7
|
+
|
|
5
8
|
describe('React Generator', () => {
|
|
9
|
+
describe('toWebFTagName', () => {
|
|
10
|
+
it('should convert WebF prefixed components correctly', () => {
|
|
11
|
+
expect(toWebFTagName('WebFTable')).toBe('webf-table');
|
|
12
|
+
expect(toWebFTagName('WebFTableCell')).toBe('webf-table-cell');
|
|
13
|
+
expect(toWebFTagName('WebFListView')).toBe('webf-list-view');
|
|
14
|
+
expect(toWebFTagName('WebFTouchArea')).toBe('webf-touch-area');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should convert Flutter prefixed components correctly', () => {
|
|
18
|
+
expect(toWebFTagName('FlutterShimmer')).toBe('flutter-shimmer');
|
|
19
|
+
expect(toWebFTagName('FlutterShimmerText')).toBe('flutter-shimmer-text');
|
|
20
|
+
expect(toWebFTagName('FlutterShimmerAvatar')).toBe('flutter-shimmer-avatar');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should handle components without special prefixes', () => {
|
|
24
|
+
expect(toWebFTagName('CustomComponent')).toBe('custom-component');
|
|
25
|
+
expect(toWebFTagName('MyElement')).toBe('my-element');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
6
29
|
describe('generateReactComponent', () => {
|
|
7
|
-
it('should
|
|
30
|
+
it('should import createWebFComponent from @openwebf/react-core-ui', () => {
|
|
8
31
|
const blob = new IDLBlob('/test/source', '/test/target', 'TestComponent', 'test', 'components/ui');
|
|
9
32
|
|
|
10
33
|
const properties = new ClassObject();
|
|
@@ -14,11 +37,11 @@ describe('React Generator', () => {
|
|
|
14
37
|
|
|
15
38
|
const result = generateReactComponent(blob);
|
|
16
39
|
|
|
17
|
-
//
|
|
18
|
-
expect(result).toContain('import {
|
|
40
|
+
// Should import from npm package by default
|
|
41
|
+
expect(result).toContain('import { createWebFComponent, WebFElementWithMethods } from "@openwebf/react-core-ui"');
|
|
19
42
|
});
|
|
20
43
|
|
|
21
|
-
it('should
|
|
44
|
+
it('should generate component using createWebFComponent', () => {
|
|
22
45
|
const blob = new IDLBlob('/test/source', '/test/target', 'TestComponent', 'test', '');
|
|
23
46
|
|
|
24
47
|
const properties = new ClassObject();
|
|
@@ -28,11 +51,13 @@ describe('React Generator', () => {
|
|
|
28
51
|
|
|
29
52
|
const result = generateReactComponent(blob);
|
|
30
53
|
|
|
31
|
-
//
|
|
32
|
-
expect(result).toContain('
|
|
54
|
+
// Should use createWebFComponent
|
|
55
|
+
expect(result).toContain('export const TestComponent = createWebFComponent<TestComponentElement, TestComponentProps>({');
|
|
56
|
+
expect(result).toContain('tagName: \'test-component\'');
|
|
57
|
+
expect(result).toContain('displayName: \'TestComponent\'');
|
|
33
58
|
});
|
|
34
59
|
|
|
35
|
-
it('should
|
|
60
|
+
it('should generate proper TypeScript interfaces', () => {
|
|
36
61
|
const blob = new IDLBlob('/test/source', '/test/target', 'TestComponent', 'test', 'widgets');
|
|
37
62
|
|
|
38
63
|
const properties = new ClassObject();
|
|
@@ -42,8 +67,68 @@ describe('React Generator', () => {
|
|
|
42
67
|
|
|
43
68
|
const result = generateReactComponent(blob);
|
|
44
69
|
|
|
45
|
-
//
|
|
46
|
-
expect(result).toContain('
|
|
70
|
+
// Should have proper interfaces
|
|
71
|
+
expect(result).toContain('export interface TestComponentProps {');
|
|
72
|
+
expect(result).toContain('export interface TestComponentElement extends WebFElementWithMethods<{');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should generate correct tagName for WebF components', () => {
|
|
76
|
+
const blob = new IDLBlob('/test/source', '/test/target', 'WebFTableCell', 'test', '');
|
|
77
|
+
|
|
78
|
+
const properties = new ClassObject();
|
|
79
|
+
properties.name = 'WebFTableCellProperties';
|
|
80
|
+
properties.kind = ClassObjectKind.interface;
|
|
81
|
+
blob.objects = [properties];
|
|
82
|
+
|
|
83
|
+
const result = generateReactComponent(blob);
|
|
84
|
+
|
|
85
|
+
// Should generate correct tagName
|
|
86
|
+
expect(result).toContain('tagName: \'webf-table-cell\'');
|
|
87
|
+
expect(result).not.toContain('tagName: \'web-f-table-cell\'');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should generate correct tagName for Flutter components', () => {
|
|
91
|
+
const blob = new IDLBlob('/test/source', '/test/target', 'FlutterShimmerText', 'test', '');
|
|
92
|
+
|
|
93
|
+
const properties = new ClassObject();
|
|
94
|
+
properties.name = 'FlutterShimmerTextProperties';
|
|
95
|
+
properties.kind = ClassObjectKind.interface;
|
|
96
|
+
blob.objects = [properties];
|
|
97
|
+
|
|
98
|
+
const result = generateReactComponent(blob);
|
|
99
|
+
|
|
100
|
+
// Should generate correct tagName
|
|
101
|
+
expect(result).toContain('tagName: \'flutter-shimmer-text\'');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should use relative import for @openwebf/react-core-ui package', () => {
|
|
105
|
+
const blob = new IDLBlob('/test/source', '/test/target', 'TestComponent', 'test', 'lib/src/html');
|
|
106
|
+
|
|
107
|
+
const properties = new ClassObject();
|
|
108
|
+
properties.name = 'TestComponentProperties';
|
|
109
|
+
properties.kind = ClassObjectKind.interface;
|
|
110
|
+
blob.objects = [properties];
|
|
111
|
+
|
|
112
|
+
const result = generateReactComponent(blob, '@openwebf/react-core-ui', 'lib/src/html');
|
|
113
|
+
|
|
114
|
+
// Should use relative import for react-core-ui package itself
|
|
115
|
+
// From src/lib/src/html to src/utils: ../../../utils
|
|
116
|
+
expect(result).toContain('import { createWebFComponent, WebFElementWithMethods } from "../../../utils/createWebFComponent"');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should use relative import for nested directories in @openwebf/react-core-ui', () => {
|
|
120
|
+
const blob = new IDLBlob('/test/source', '/test/target', 'TestComponent', 'test', 'lib/src/html/shimmer');
|
|
121
|
+
|
|
122
|
+
const properties = new ClassObject();
|
|
123
|
+
properties.name = 'TestComponentProperties';
|
|
124
|
+
properties.kind = ClassObjectKind.interface;
|
|
125
|
+
blob.objects = [properties];
|
|
126
|
+
|
|
127
|
+
const result = generateReactComponent(blob, '@openwebf/react-core-ui', 'lib/src/html/shimmer');
|
|
128
|
+
|
|
129
|
+
// Should use relative import with correct depth
|
|
130
|
+
// From src/lib/src/html/shimmer to src/utils: ../../../../utils
|
|
131
|
+
expect(result).toContain('import { createWebFComponent, WebFElementWithMethods } from "../../../../utils/createWebFComponent"');
|
|
47
132
|
});
|
|
48
133
|
});
|
|
49
134
|
});
|