lwc-convert 1.0.0 → 1.0.1
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/LICENSE +21 -21
- package/README.md +719 -719
- package/dist/cli/commands/aura.d.ts.map +1 -1
- package/dist/cli/commands/aura.js +10 -0
- package/dist/cli/commands/aura.js.map +1 -1
- package/dist/cli/commands/vf.d.ts.map +1 -1
- package/dist/cli/commands/vf.js +10 -0
- package/dist/cli/commands/vf.js.map +1 -1
- package/dist/cli/interactive.d.ts +1 -0
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +10 -0
- package/dist/cli/interactive.js.map +1 -1
- package/dist/cli/options.d.ts +2 -1
- package/dist/cli/options.d.ts.map +1 -1
- package/dist/cli/options.js +14 -13
- package/dist/cli/options.js.map +1 -1
- package/dist/generators/full-conversion.js +6 -6
- package/dist/generators/scaffolding.js +90 -90
- package/dist/generators/test-comparison.js +149 -149
- package/dist/generators/test-generator.js +231 -231
- package/dist/index.js +49 -35
- package/dist/index.js.map +1 -1
- package/dist/transformers/aura-to-lwc/events.js +130 -130
- package/dist/transformers/vf-to-lwc/components.js +79 -79
- package/dist/transformers/vf-to-lwc/data-binding.js +165 -165
- package/dist/utils/file-io.js +15 -15
- package/dist/utils/preview-generator.d.ts +20 -0
- package/dist/utils/preview-generator.d.ts.map +1 -0
- package/dist/utils/preview-generator.js +833 -0
- package/dist/utils/preview-generator.js.map +1 -0
- package/dist/utils/session-store.js +32 -32
- package/package.json +85 -81
|
@@ -29,23 +29,23 @@ function generateAuraToLwcTests(markup, transformedMarkup, controller) {
|
|
|
29
29
|
imports.push(`import ${channelVar} from '@salesforce/messageChannel/${lms.channelName}';`);
|
|
30
30
|
}
|
|
31
31
|
// Add LMS mocks
|
|
32
|
-
mocks.push(`// Mock LMS
|
|
33
|
-
jest.mock(
|
|
34
|
-
'lightning/messageService',
|
|
35
|
-
() => ({
|
|
36
|
-
publish: jest.fn(),
|
|
37
|
-
subscribe: jest.fn(() => ({ unsubscribe: jest.fn() })),
|
|
38
|
-
unsubscribe: jest.fn(),
|
|
39
|
-
MessageContext: jest.fn()
|
|
40
|
-
}),
|
|
41
|
-
{ virtual: true }
|
|
32
|
+
mocks.push(`// Mock LMS
|
|
33
|
+
jest.mock(
|
|
34
|
+
'lightning/messageService',
|
|
35
|
+
() => ({
|
|
36
|
+
publish: jest.fn(),
|
|
37
|
+
subscribe: jest.fn(() => ({ unsubscribe: jest.fn() })),
|
|
38
|
+
unsubscribe: jest.fn(),
|
|
39
|
+
MessageContext: jest.fn()
|
|
40
|
+
}),
|
|
41
|
+
{ virtual: true }
|
|
42
42
|
);`);
|
|
43
43
|
}
|
|
44
44
|
// Analyze wire/data patterns
|
|
45
45
|
if (transformedMarkup.recordDataServices.length > 0) {
|
|
46
46
|
imports.push("import { getRecord, getFieldValue } from 'lightning/uiRecordApi';");
|
|
47
47
|
// Mock wire adapter
|
|
48
|
-
mocks.push(`// Mock getRecord wire adapter
|
|
48
|
+
mocks.push(`// Mock getRecord wire adapter
|
|
49
49
|
const mockGetRecord = require('lightning/uiRecordApi').getRecord;`);
|
|
50
50
|
for (const rds of transformedMarkup.recordDataServices) {
|
|
51
51
|
behaviorSpecs.push({
|
|
@@ -66,11 +66,11 @@ const mockGetRecord = require('lightning/uiRecordApi').getRecord;`);
|
|
|
66
66
|
initApexMethod = initFunc.serverCalls[0].controllerMethod || null;
|
|
67
67
|
if (initApexMethod) {
|
|
68
68
|
imports.push(`import ${initApexMethod} from '@salesforce/apex/${markup.controller}.${initApexMethod}';`);
|
|
69
|
-
mocks.push(`// Mock Apex method
|
|
70
|
-
jest.mock(
|
|
71
|
-
'@salesforce/apex/${markup.controller}.${initApexMethod}',
|
|
72
|
-
() => ({ default: jest.fn() }),
|
|
73
|
-
{ virtual: true }
|
|
69
|
+
mocks.push(`// Mock Apex method
|
|
70
|
+
jest.mock(
|
|
71
|
+
'@salesforce/apex/${markup.controller}.${initApexMethod}',
|
|
72
|
+
() => ({ default: jest.fn() }),
|
|
73
|
+
{ virtual: true }
|
|
74
74
|
);`);
|
|
75
75
|
behaviorSpecs.push({
|
|
76
76
|
category: 'data',
|
|
@@ -82,33 +82,33 @@ jest.mock(
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
// Generate test structure
|
|
85
|
-
let testContent = `/**
|
|
86
|
-
* Jest tests for ${lwcName}
|
|
87
|
-
* Converted from Aura component: ${markup.componentName}
|
|
88
|
-
*
|
|
89
|
-
* These tests verify that the converted LWC preserves the original behaviors.
|
|
90
|
-
*/
|
|
91
|
-
|
|
92
|
-
${imports.join('\n')}
|
|
93
|
-
|
|
94
|
-
${mocks.join('\n\n')}
|
|
95
|
-
|
|
96
|
-
describe('${lwcName}', () => {
|
|
97
|
-
let element;
|
|
98
|
-
|
|
99
|
-
beforeEach(() => {
|
|
100
|
-
element = createElement('c-${lwcName}', {
|
|
101
|
-
is: ${className}
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
afterEach(() => {
|
|
106
|
-
while (document.body.firstChild) {
|
|
107
|
-
document.body.removeChild(document.body.firstChild);
|
|
108
|
-
}
|
|
109
|
-
jest.clearAllMocks();
|
|
110
|
-
});
|
|
111
|
-
|
|
85
|
+
let testContent = `/**
|
|
86
|
+
* Jest tests for ${lwcName}
|
|
87
|
+
* Converted from Aura component: ${markup.componentName}
|
|
88
|
+
*
|
|
89
|
+
* These tests verify that the converted LWC preserves the original behaviors.
|
|
90
|
+
*/
|
|
91
|
+
|
|
92
|
+
${imports.join('\n')}
|
|
93
|
+
|
|
94
|
+
${mocks.join('\n\n')}
|
|
95
|
+
|
|
96
|
+
describe('${lwcName}', () => {
|
|
97
|
+
let element;
|
|
98
|
+
|
|
99
|
+
beforeEach(() => {
|
|
100
|
+
element = createElement('c-${lwcName}', {
|
|
101
|
+
is: ${className}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
afterEach(() => {
|
|
106
|
+
while (document.body.firstChild) {
|
|
107
|
+
document.body.removeChild(document.body.firstChild);
|
|
108
|
+
}
|
|
109
|
+
jest.clearAllMocks();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
112
|
`;
|
|
113
113
|
// Generate LMS Subscriber tests
|
|
114
114
|
if (hasLmsSubscriber) {
|
|
@@ -126,67 +126,67 @@ describe('${lwcName}', () => {
|
|
|
126
126
|
auraPattern: 'Automatic cleanup by Aura framework',
|
|
127
127
|
lwcEquivalent: `unsubscribe(subscription) in disconnectedCallback`
|
|
128
128
|
});
|
|
129
|
-
testContent += ` /**
|
|
130
|
-
* LMS Subscription Tests
|
|
131
|
-
* Original Aura: lightning:messageChannel type="${lms.channelName}" onMessage="{!c.${lms.onMessageHandler}}"
|
|
132
|
-
*/
|
|
133
|
-
describe('LMS Subscription - ${lms.channelName}', () => {
|
|
134
|
-
test('should subscribe to message channel on connect', () => {
|
|
135
|
-
// Arrange & Act
|
|
136
|
-
document.body.appendChild(element);
|
|
137
|
-
|
|
138
|
-
// Assert
|
|
139
|
-
expect(subscribe).toHaveBeenCalledWith(
|
|
140
|
-
expect.anything(), // MessageContext
|
|
141
|
-
${channelVar},
|
|
142
|
-
expect.any(Function)
|
|
143
|
-
);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test('should unsubscribe from message channel on disconnect', () => {
|
|
147
|
-
// Arrange
|
|
148
|
-
document.body.appendChild(element);
|
|
149
|
-
|
|
150
|
-
// Act
|
|
151
|
-
document.body.removeChild(element);
|
|
152
|
-
|
|
153
|
-
// Assert
|
|
154
|
-
expect(unsubscribe).toHaveBeenCalled();
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test('should handle incoming message and update state', async () => {
|
|
158
|
-
// Arrange
|
|
159
|
-
document.body.appendChild(element);
|
|
160
|
-
const mockRecordId = '003xx000004TtgAAC';
|
|
161
|
-
|
|
162
|
-
// Get the message handler that was registered
|
|
163
|
-
const messageHandler = subscribe.mock.calls[0][2];
|
|
164
|
-
|
|
165
|
-
// Act - simulate receiving a message
|
|
166
|
-
messageHandler({ recordId: mockRecordId });
|
|
167
|
-
|
|
168
|
-
// Wait for reactivity
|
|
169
|
-
await Promise.resolve();
|
|
170
|
-
|
|
171
|
-
// Assert - verify state was updated
|
|
172
|
-
// The original Aura set v.contactId from message.getParam('recordId')
|
|
173
|
-
expect(element.contactId).toBe(mockRecordId);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
test('should clear state when message has no recordId', async () => {
|
|
177
|
-
// Arrange
|
|
178
|
-
document.body.appendChild(element);
|
|
179
|
-
const messageHandler = subscribe.mock.calls[0][2];
|
|
180
|
-
|
|
181
|
-
// Act - simulate receiving empty message
|
|
182
|
-
messageHandler({});
|
|
183
|
-
await Promise.resolve();
|
|
184
|
-
|
|
185
|
-
// Assert
|
|
186
|
-
expect(element.contactId).toBe('');
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
|
|
129
|
+
testContent += ` /**
|
|
130
|
+
* LMS Subscription Tests
|
|
131
|
+
* Original Aura: lightning:messageChannel type="${lms.channelName}" onMessage="{!c.${lms.onMessageHandler}}"
|
|
132
|
+
*/
|
|
133
|
+
describe('LMS Subscription - ${lms.channelName}', () => {
|
|
134
|
+
test('should subscribe to message channel on connect', () => {
|
|
135
|
+
// Arrange & Act
|
|
136
|
+
document.body.appendChild(element);
|
|
137
|
+
|
|
138
|
+
// Assert
|
|
139
|
+
expect(subscribe).toHaveBeenCalledWith(
|
|
140
|
+
expect.anything(), // MessageContext
|
|
141
|
+
${channelVar},
|
|
142
|
+
expect.any(Function)
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('should unsubscribe from message channel on disconnect', () => {
|
|
147
|
+
// Arrange
|
|
148
|
+
document.body.appendChild(element);
|
|
149
|
+
|
|
150
|
+
// Act
|
|
151
|
+
document.body.removeChild(element);
|
|
152
|
+
|
|
153
|
+
// Assert
|
|
154
|
+
expect(unsubscribe).toHaveBeenCalled();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('should handle incoming message and update state', async () => {
|
|
158
|
+
// Arrange
|
|
159
|
+
document.body.appendChild(element);
|
|
160
|
+
const mockRecordId = '003xx000004TtgAAC';
|
|
161
|
+
|
|
162
|
+
// Get the message handler that was registered
|
|
163
|
+
const messageHandler = subscribe.mock.calls[0][2];
|
|
164
|
+
|
|
165
|
+
// Act - simulate receiving a message
|
|
166
|
+
messageHandler({ recordId: mockRecordId });
|
|
167
|
+
|
|
168
|
+
// Wait for reactivity
|
|
169
|
+
await Promise.resolve();
|
|
170
|
+
|
|
171
|
+
// Assert - verify state was updated
|
|
172
|
+
// The original Aura set v.contactId from message.getParam('recordId')
|
|
173
|
+
expect(element.contactId).toBe(mockRecordId);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('should clear state when message has no recordId', async () => {
|
|
177
|
+
// Arrange
|
|
178
|
+
document.body.appendChild(element);
|
|
179
|
+
const messageHandler = subscribe.mock.calls[0][2];
|
|
180
|
+
|
|
181
|
+
// Act - simulate receiving empty message
|
|
182
|
+
messageHandler({});
|
|
183
|
+
await Promise.resolve();
|
|
184
|
+
|
|
185
|
+
// Assert
|
|
186
|
+
expect(element.contactId).toBe('');
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
190
|
`;
|
|
191
191
|
}
|
|
192
192
|
}
|
|
@@ -200,148 +200,148 @@ describe('${lwcName}', () => {
|
|
|
200
200
|
auraPattern: `component.find('recordSelected').publish(payload)`,
|
|
201
201
|
lwcEquivalent: `publish(messageContext, ${channelVar}, message)`
|
|
202
202
|
});
|
|
203
|
-
testContent += ` /**
|
|
204
|
-
* LMS Publisher Tests
|
|
205
|
-
* Original Aura: component.find('recordSelected').publish(payload)
|
|
206
|
-
*/
|
|
207
|
-
describe('LMS Publishing - ${lms.channelName}', () => {
|
|
208
|
-
test('should not subscribe to message channel (publisher only)', () => {
|
|
209
|
-
// Arrange & Act
|
|
210
|
-
document.body.appendChild(element);
|
|
211
|
-
|
|
212
|
-
// Assert - publisher should NOT subscribe
|
|
213
|
-
expect(subscribe).not.toHaveBeenCalled();
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
test('should publish message when publishMessage is called', async () => {
|
|
217
|
-
// Arrange
|
|
218
|
-
document.body.appendChild(element);
|
|
219
|
-
const mockRecordId = '003xx000004TtgAAC';
|
|
220
|
-
|
|
221
|
-
// Act
|
|
222
|
-
element.publishMessage({ recordId: mockRecordId });
|
|
223
|
-
|
|
224
|
-
// Assert
|
|
225
|
-
expect(publish).toHaveBeenCalledWith(
|
|
226
|
-
expect.anything(), // MessageContext
|
|
227
|
-
${channelVar},
|
|
228
|
-
expect.objectContaining({ recordId: mockRecordId })
|
|
229
|
-
);
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
|
|
203
|
+
testContent += ` /**
|
|
204
|
+
* LMS Publisher Tests
|
|
205
|
+
* Original Aura: component.find('recordSelected').publish(payload)
|
|
206
|
+
*/
|
|
207
|
+
describe('LMS Publishing - ${lms.channelName}', () => {
|
|
208
|
+
test('should not subscribe to message channel (publisher only)', () => {
|
|
209
|
+
// Arrange & Act
|
|
210
|
+
document.body.appendChild(element);
|
|
211
|
+
|
|
212
|
+
// Assert - publisher should NOT subscribe
|
|
213
|
+
expect(subscribe).not.toHaveBeenCalled();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('should publish message when publishMessage is called', async () => {
|
|
217
|
+
// Arrange
|
|
218
|
+
document.body.appendChild(element);
|
|
219
|
+
const mockRecordId = '003xx000004TtgAAC';
|
|
220
|
+
|
|
221
|
+
// Act
|
|
222
|
+
element.publishMessage({ recordId: mockRecordId });
|
|
223
|
+
|
|
224
|
+
// Assert
|
|
225
|
+
expect(publish).toHaveBeenCalledWith(
|
|
226
|
+
expect.anything(), // MessageContext
|
|
227
|
+
${channelVar},
|
|
228
|
+
expect.objectContaining({ recordId: mockRecordId })
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
233
|
`;
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
// Generate Wire/Data tests
|
|
237
237
|
if (transformedMarkup.recordDataServices.length > 0) {
|
|
238
|
-
testContent += ` /**
|
|
239
|
-
* Wire Adapter Tests (force:recordData equivalent)
|
|
240
|
-
* Original Aura: force:recordData with reactive recordId binding
|
|
241
|
-
*/
|
|
242
|
-
describe('Record Data Loading', () => {
|
|
238
|
+
testContent += ` /**
|
|
239
|
+
* Wire Adapter Tests (force:recordData equivalent)
|
|
240
|
+
* Original Aura: force:recordData with reactive recordId binding
|
|
241
|
+
*/
|
|
242
|
+
describe('Record Data Loading', () => {
|
|
243
243
|
`;
|
|
244
244
|
for (const rds of transformedMarkup.recordDataServices) {
|
|
245
245
|
const targetProp = rds.targetFields || 'record';
|
|
246
|
-
testContent += ` test('should wire ${targetProp} with reactive ${rds.recordIdBinding}', async () => {
|
|
247
|
-
// This test verifies the @wire decorator is properly configured
|
|
248
|
-
// In Aura: force:recordData recordId="{!v.${rds.recordIdBinding}}" targetFields="{!v.${targetProp}}"
|
|
249
|
-
// In LWC: @wire(getRecord, { recordId: '$${rds.recordIdBinding}', fields: [...] }) ${targetProp};
|
|
250
|
-
|
|
251
|
-
// Arrange
|
|
252
|
-
const mockRecord = {
|
|
253
|
-
data: {
|
|
254
|
-
fields: {
|
|
255
|
-
${rds.fields.map(f => ` ${f}: { value: 'Test ${f}' }`).join(',\n')}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
// Act
|
|
261
|
-
document.body.appendChild(element);
|
|
262
|
-
|
|
263
|
-
// Simulate wire returning data
|
|
264
|
-
// Note: In actual test, you'd use @salesforce/sfdx-lwc-jest wire adapter mocking
|
|
265
|
-
|
|
266
|
-
// Assert structure exists
|
|
267
|
-
expect(element).toBeDefined();
|
|
268
|
-
});
|
|
269
|
-
|
|
246
|
+
testContent += ` test('should wire ${targetProp} with reactive ${rds.recordIdBinding}', async () => {
|
|
247
|
+
// This test verifies the @wire decorator is properly configured
|
|
248
|
+
// In Aura: force:recordData recordId="{!v.${rds.recordIdBinding}}" targetFields="{!v.${targetProp}}"
|
|
249
|
+
// In LWC: @wire(getRecord, { recordId: '$${rds.recordIdBinding}', fields: [...] }) ${targetProp};
|
|
250
|
+
|
|
251
|
+
// Arrange
|
|
252
|
+
const mockRecord = {
|
|
253
|
+
data: {
|
|
254
|
+
fields: {
|
|
255
|
+
${rds.fields.map(f => ` ${f}: { value: 'Test ${f}' }`).join(',\n')}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// Act
|
|
261
|
+
document.body.appendChild(element);
|
|
262
|
+
|
|
263
|
+
// Simulate wire returning data
|
|
264
|
+
// Note: In actual test, you'd use @salesforce/sfdx-lwc-jest wire adapter mocking
|
|
265
|
+
|
|
266
|
+
// Assert structure exists
|
|
267
|
+
expect(element).toBeDefined();
|
|
268
|
+
});
|
|
269
|
+
|
|
270
270
|
`;
|
|
271
271
|
// Generate getter tests for each field
|
|
272
272
|
for (const field of rds.fields) {
|
|
273
273
|
const getterName = field.charAt(0).toLowerCase() + field.slice(1).replace(/__c$/i, '');
|
|
274
|
-
testContent += ` test('getter ${getterName} should return undefined when no data', () => {
|
|
275
|
-
// Original Aura: {!v.${targetProp}.${field}}
|
|
276
|
-
// Converted LWC: {${getterName}} via getter using getFieldValue
|
|
277
|
-
|
|
278
|
-
document.body.appendChild(element);
|
|
279
|
-
expect(element.${getterName}).toBeUndefined();
|
|
280
|
-
});
|
|
281
|
-
|
|
274
|
+
testContent += ` test('getter ${getterName} should return undefined when no data', () => {
|
|
275
|
+
// Original Aura: {!v.${targetProp}.${field}}
|
|
276
|
+
// Converted LWC: {${getterName}} via getter using getFieldValue
|
|
277
|
+
|
|
278
|
+
document.body.appendChild(element);
|
|
279
|
+
expect(element.${getterName}).toBeUndefined();
|
|
280
|
+
});
|
|
281
|
+
|
|
282
282
|
`;
|
|
283
283
|
}
|
|
284
284
|
}
|
|
285
|
-
testContent += ` });
|
|
286
|
-
|
|
285
|
+
testContent += ` });
|
|
286
|
+
|
|
287
287
|
`;
|
|
288
288
|
}
|
|
289
289
|
// Generate init/Apex wire tests
|
|
290
290
|
if (initApexMethod) {
|
|
291
|
-
testContent += ` /**
|
|
292
|
-
* Init/Data Loading Tests
|
|
293
|
-
* Original Aura: aura:handler name="init" calling ${initApexMethod}
|
|
294
|
-
* Converted LWC: @wire(${initApexMethod})
|
|
295
|
-
*/
|
|
296
|
-
describe('Initial Data Loading', () => {
|
|
297
|
-
test('should wire ${initApexMethod} for automatic data loading', () => {
|
|
298
|
-
// In Aura: doInit called $A.enqueueAction for ${initApexMethod}
|
|
299
|
-
// In LWC: @wire(${initApexMethod}) loads data declaratively
|
|
300
|
-
|
|
301
|
-
document.body.appendChild(element);
|
|
302
|
-
|
|
303
|
-
// With @wire, data loads automatically - no imperative call needed
|
|
304
|
-
// The test verifies the wire adapter is properly configured
|
|
305
|
-
expect(element).toBeDefined();
|
|
306
|
-
});
|
|
307
|
-
});
|
|
308
|
-
|
|
291
|
+
testContent += ` /**
|
|
292
|
+
* Init/Data Loading Tests
|
|
293
|
+
* Original Aura: aura:handler name="init" calling ${initApexMethod}
|
|
294
|
+
* Converted LWC: @wire(${initApexMethod})
|
|
295
|
+
*/
|
|
296
|
+
describe('Initial Data Loading', () => {
|
|
297
|
+
test('should wire ${initApexMethod} for automatic data loading', () => {
|
|
298
|
+
// In Aura: doInit called $A.enqueueAction for ${initApexMethod}
|
|
299
|
+
// In LWC: @wire(${initApexMethod}) loads data declaratively
|
|
300
|
+
|
|
301
|
+
document.body.appendChild(element);
|
|
302
|
+
|
|
303
|
+
// With @wire, data loads automatically - no imperative call needed
|
|
304
|
+
// The test verifies the wire adapter is properly configured
|
|
305
|
+
expect(element).toBeDefined();
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
309
|
`;
|
|
310
310
|
}
|
|
311
311
|
// Generate UI rendering tests
|
|
312
|
-
testContent += ` /**
|
|
313
|
-
* UI Rendering Tests
|
|
314
|
-
* Verify template renders correctly with data
|
|
315
|
-
*/
|
|
316
|
-
describe('UI Rendering', () => {
|
|
317
|
-
test('should render component', () => {
|
|
318
|
-
document.body.appendChild(element);
|
|
319
|
-
expect(element).toBeTruthy();
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
test('should render lightning-card with correct title', () => {
|
|
323
|
-
document.body.appendChild(element);
|
|
324
|
-
const card = element.shadowRoot.querySelector('lightning-card');
|
|
325
|
-
expect(card).toBeTruthy();
|
|
326
|
-
expect(card.title).toBe('${markup.componentName}');
|
|
327
|
-
});
|
|
312
|
+
testContent += ` /**
|
|
313
|
+
* UI Rendering Tests
|
|
314
|
+
* Verify template renders correctly with data
|
|
315
|
+
*/
|
|
316
|
+
describe('UI Rendering', () => {
|
|
317
|
+
test('should render component', () => {
|
|
318
|
+
document.body.appendChild(element);
|
|
319
|
+
expect(element).toBeTruthy();
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test('should render lightning-card with correct title', () => {
|
|
323
|
+
document.body.appendChild(element);
|
|
324
|
+
const card = element.shadowRoot.querySelector('lightning-card');
|
|
325
|
+
expect(card).toBeTruthy();
|
|
326
|
+
expect(card.title).toBe('${markup.componentName}');
|
|
327
|
+
});
|
|
328
328
|
`;
|
|
329
329
|
// Add conditional rendering tests if aura:if was present
|
|
330
330
|
if (markup.body.some(node => node.name === 'aura:if')) {
|
|
331
|
-
testContent += `
|
|
332
|
-
test('should conditionally render content', async () => {
|
|
333
|
-
// Original Aura: <aura:if isTrue="{!v.someCondition}">
|
|
334
|
-
// Converted LWC: <template if:true={someCondition}>
|
|
335
|
-
|
|
336
|
-
document.body.appendChild(element);
|
|
337
|
-
|
|
338
|
-
// Initially should not show conditional content
|
|
339
|
-
// After data loads, should show content
|
|
340
|
-
});
|
|
331
|
+
testContent += `
|
|
332
|
+
test('should conditionally render content', async () => {
|
|
333
|
+
// Original Aura: <aura:if isTrue="{!v.someCondition}">
|
|
334
|
+
// Converted LWC: <template if:true={someCondition}>
|
|
335
|
+
|
|
336
|
+
document.body.appendChild(element);
|
|
337
|
+
|
|
338
|
+
// Initially should not show conditional content
|
|
339
|
+
// After data loads, should show content
|
|
340
|
+
});
|
|
341
341
|
`;
|
|
342
342
|
}
|
|
343
|
-
testContent += ` });
|
|
344
|
-
});
|
|
343
|
+
testContent += ` });
|
|
344
|
+
});
|
|
345
345
|
`;
|
|
346
346
|
return {
|
|
347
347
|
filename: `${lwcName}.test.js`,
|
|
@@ -354,22 +354,22 @@ ${rds.fields.map(f => ` ${f}: { value: 'Test ${f}' }`).jo
|
|
|
354
354
|
*/
|
|
355
355
|
function generateBehaviorSpecDocument(componentName, specs) {
|
|
356
356
|
const lwcName = (0, file_io_1.toLwcName)(componentName);
|
|
357
|
-
let doc = `# Behavior Specification: ${componentName} → ${lwcName}
|
|
358
|
-
|
|
359
|
-
This document maps the expected behaviors from the original Aura component to their LWC equivalents.
|
|
360
|
-
Use this as a checklist to verify the conversion is correct.
|
|
361
|
-
|
|
362
|
-
## Behavior Mapping
|
|
363
|
-
|
|
364
|
-
| Category | Behavior | Aura Pattern | LWC Equivalent |
|
|
365
|
-
|----------|----------|--------------|----------------|
|
|
357
|
+
let doc = `# Behavior Specification: ${componentName} → ${lwcName}
|
|
358
|
+
|
|
359
|
+
This document maps the expected behaviors from the original Aura component to their LWC equivalents.
|
|
360
|
+
Use this as a checklist to verify the conversion is correct.
|
|
361
|
+
|
|
362
|
+
## Behavior Mapping
|
|
363
|
+
|
|
364
|
+
| Category | Behavior | Aura Pattern | LWC Equivalent |
|
|
365
|
+
|----------|----------|--------------|----------------|
|
|
366
366
|
`;
|
|
367
367
|
for (const spec of specs) {
|
|
368
368
|
doc += `| ${spec.category} | ${spec.description} | \`${spec.auraPattern}\` | \`${spec.lwcEquivalent}\` |\n`;
|
|
369
369
|
}
|
|
370
|
-
doc += `
|
|
371
|
-
## Test Checklist
|
|
372
|
-
|
|
370
|
+
doc += `
|
|
371
|
+
## Test Checklist
|
|
372
|
+
|
|
373
373
|
`;
|
|
374
374
|
const categories = [...new Set(specs.map(s => s.category))];
|
|
375
375
|
for (const cat of categories) {
|
package/dist/index.js
CHANGED
|
@@ -55,6 +55,7 @@ program
|
|
|
55
55
|
.option('--full', 'Run full automated conversion (default: scaffolding only)', false)
|
|
56
56
|
.option('-o, --output <dir>', 'Output directory', options_1.DEFAULT_OUTPUT_DIR)
|
|
57
57
|
.option('--open', 'Open output folder in file explorer after conversion', false)
|
|
58
|
+
.option('--preview', 'Generate and open HTML preview of converted component', false)
|
|
58
59
|
.option('--dry-run', 'Preview conversion without writing files', false)
|
|
59
60
|
.option('--verbose', 'Show detailed conversion logs', false)
|
|
60
61
|
.action(async (bundlePath, options) => {
|
|
@@ -65,6 +66,7 @@ program
|
|
|
65
66
|
dryRun: options.dryRun,
|
|
66
67
|
verbose: options.verbose,
|
|
67
68
|
open: options.open,
|
|
69
|
+
preview: options.preview,
|
|
68
70
|
});
|
|
69
71
|
});
|
|
70
72
|
// Visualforce conversion command
|
|
@@ -75,6 +77,7 @@ program
|
|
|
75
77
|
.option('-o, --output <dir>', 'Output directory', options_1.DEFAULT_OUTPUT_DIR)
|
|
76
78
|
.option('--controller <path>', 'Include Apex controller file for analysis')
|
|
77
79
|
.option('--open', 'Open output folder in file explorer after conversion', false)
|
|
80
|
+
.option('--preview', 'Generate and open HTML preview of converted component', false)
|
|
78
81
|
.option('--dry-run', 'Preview conversion without writing files', false)
|
|
79
82
|
.option('--verbose', 'Show detailed conversion logs', false)
|
|
80
83
|
.action(async (pagePath, options) => {
|
|
@@ -86,6 +89,7 @@ program
|
|
|
86
89
|
verbose: options.verbose,
|
|
87
90
|
controller: options.controller,
|
|
88
91
|
open: options.open,
|
|
92
|
+
preview: options.preview,
|
|
89
93
|
});
|
|
90
94
|
});
|
|
91
95
|
// Session management command
|
|
@@ -150,41 +154,49 @@ program
|
|
|
150
154
|
}
|
|
151
155
|
});
|
|
152
156
|
// Examples in help
|
|
153
|
-
program.addHelpText('after', `
|
|
154
|
-
Examples:
|
|
155
|
-
# Convert Aura component (just use the name!)
|
|
156
|
-
$ ${options_1.CLI_NAME} aura AccountCard
|
|
157
|
-
$ ${options_1.CLI_NAME} aura AccountCard --full
|
|
158
|
-
|
|
159
|
-
# Convert Visualforce page (just use the name!)
|
|
160
|
-
$ ${options_1.CLI_NAME} vf ContactList
|
|
161
|
-
$ ${options_1.CLI_NAME} vf ContactList --controller ContactListController
|
|
162
|
-
|
|
163
|
-
# Full paths also work
|
|
164
|
-
$ ${options_1.CLI_NAME} aura ./force-app/main/default/aura/MyComponent
|
|
165
|
-
$ ${options_1.CLI_NAME} vf ./pages/MyPage.page --controller ./classes/MyController.cls
|
|
166
|
-
|
|
167
|
-
# Preview
|
|
168
|
-
$ ${options_1.CLI_NAME} aura MyComponent --
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
$ ${options_1.CLI_NAME}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
157
|
+
program.addHelpText('after', `
|
|
158
|
+
Examples:
|
|
159
|
+
# Convert Aura component (just use the name!)
|
|
160
|
+
$ ${options_1.CLI_NAME} aura AccountCard
|
|
161
|
+
$ ${options_1.CLI_NAME} aura AccountCard --full
|
|
162
|
+
|
|
163
|
+
# Convert Visualforce page (just use the name!)
|
|
164
|
+
$ ${options_1.CLI_NAME} vf ContactList
|
|
165
|
+
$ ${options_1.CLI_NAME} vf ContactList --controller ContactListController
|
|
166
|
+
|
|
167
|
+
# Full paths also work
|
|
168
|
+
$ ${options_1.CLI_NAME} aura ./force-app/main/default/aura/MyComponent
|
|
169
|
+
$ ${options_1.CLI_NAME} vf ./pages/MyPage.page --controller ./classes/MyController.cls
|
|
170
|
+
|
|
171
|
+
# Preview the converted UI in your browser
|
|
172
|
+
$ ${options_1.CLI_NAME} aura MyComponent --preview
|
|
173
|
+
$ ${options_1.CLI_NAME} vf ContactList --preview --full
|
|
174
|
+
|
|
175
|
+
# Preview without writing files
|
|
176
|
+
$ ${options_1.CLI_NAME} aura MyComponent --dry-run --verbose
|
|
177
|
+
|
|
178
|
+
# Specify output directory
|
|
179
|
+
$ ${options_1.CLI_NAME} vf ContactList -o ./src/lwc
|
|
180
|
+
|
|
181
|
+
# View session data and learned patterns
|
|
182
|
+
$ ${options_1.CLI_NAME} session
|
|
183
|
+
$ ${options_1.CLI_NAME} session --report
|
|
184
|
+
$ ${options_1.CLI_NAME} session --patterns
|
|
185
|
+
|
|
186
|
+
Smart Path Resolution:
|
|
187
|
+
The CLI searches common Salesforce project locations automatically:
|
|
188
|
+
- Aura: force-app/main/default/aura/, src/aura/, aura/
|
|
189
|
+
- VF: force-app/main/default/pages/, src/pages/, pages/
|
|
190
|
+
- Apex: force-app/main/default/classes/, src/classes/, classes/
|
|
191
|
+
|
|
192
|
+
UI Preview:
|
|
193
|
+
Use --preview to generate a standalone HTML file that shows how your
|
|
194
|
+
converted LWC will look using SLDS styling. Opens in your default browser.
|
|
195
|
+
|
|
196
|
+
Session Management:
|
|
197
|
+
Conversion data is stored in a temp folder during your session.
|
|
198
|
+
This helps the tool learn from patterns and provide better suggestions.
|
|
199
|
+
Use 'session --report' to see full session statistics.
|
|
188
200
|
`);
|
|
189
201
|
// Parse and execute
|
|
190
202
|
// If no arguments provided, launch interactive TUI
|
|
@@ -202,6 +214,7 @@ if (process.argv.slice(2).length === 0) {
|
|
|
202
214
|
dryRun: false,
|
|
203
215
|
verbose: false,
|
|
204
216
|
open: answers.openFolder,
|
|
217
|
+
preview: answers.preview,
|
|
205
218
|
});
|
|
206
219
|
}
|
|
207
220
|
else {
|
|
@@ -212,6 +225,7 @@ if (process.argv.slice(2).length === 0) {
|
|
|
212
225
|
verbose: false,
|
|
213
226
|
controller: answers.controllerPath,
|
|
214
227
|
open: answers.openFolder,
|
|
228
|
+
preview: answers.preview,
|
|
215
229
|
});
|
|
216
230
|
}
|
|
217
231
|
}
|