Skip to content

Commit c387e29

Browse files
committed
test: component preview
1 parent b7bd4ac commit c387e29

File tree

2 files changed

+354
-6
lines changed

2 files changed

+354
-6
lines changed

src/shared/previewUtils.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,9 +231,7 @@ export class PreviewUtils {
231231
targetOrg?: string,
232232
performanceMode?: boolean
233233
): string[] {
234-
let appPath = `lwr/application/e/devpreview/ai/${encodeURIComponent(
235-
'localdev%2Fpreview'
236-
)}?ldpServerUrl=${ldpServerUrl}&ldpServerId=${ldpServerId}`;
234+
let appPath = `lwr/application/e/devpreview/ai/localdev%252Fpreview?ldpServerUrl=${ldpServerUrl}&ldpServerId=${ldpServerId}`;
237235
if (componentName) {
238236
// TODO: support other namespaces
239237
appPath += `&specifier=c/${componentName}`;

test/commands/lightning/dev/component.test.ts

Lines changed: 353 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,142 @@
44
* Licensed under the BSD 3-Clause license.
55
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
*/
7-
import { Messages } from '@salesforce/core';
7+
import { Config as OclifConfig } from '@oclif/core';
8+
import { Messages, Connection } from '@salesforce/core';
89
import { MockTestOrgData, TestContext } from '@salesforce/core/testSetup';
910
import { stubUx } from '@salesforce/sf-plugins-core';
1011
import { expect } from 'chai';
12+
import esmock from 'esmock';
1113
import LightningDevComponent from '../../../../src/commands/lightning/dev/component.js';
14+
import { PreviewUtils } from '../../../../src/shared/previewUtils.js';
15+
import { ConfigUtils, LocalWebServerIdentityData } from '../../../../src/shared/configUtils.js';
16+
import { ComponentUtils } from '../../../../src/shared/componentUtils.js';
17+
import { OrgUtils } from '../../../../src/shared/orgUtils.js';
18+
import { PromptUtils } from '../../../../src/shared/promptUtils.js';
1219

1320
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
1421

22+
const messages = Messages.loadMessages('@salesforce/plugin-lightning-dev', 'lightning.dev.component');
23+
const sharedMessages = Messages.loadMessages('@salesforce/plugin-lightning-dev', 'shared.utils');
24+
25+
const testOrgData = new MockTestOrgData();
26+
27+
const testUsername = 'SalesforceDeveloper';
28+
const testLdpServerId = '1I9xx0000004ClkCAE';
29+
const testLdpServerToken = 'PFT1vw8v65aXd2b9HFvZ3Zu4OcKZwjI60bq7BEjj5k4=';
30+
const testLdpServerUrl = 'wss://localhost:1234';
31+
const testIdentityData: LocalWebServerIdentityData = {
32+
identityToken: testLdpServerToken,
33+
usernameToServerEntityIdMap: {},
34+
};
35+
36+
testIdentityData.usernameToServerEntityIdMap[testUsername] = testLdpServerId;
37+
1538
describe('lightning single component preview', () => {
16-
const messages = Messages.loadMessages('@salesforce/plugin-lightning-dev', 'lightning.dev.component');
1739
const $$ = new TestContext();
18-
const testOrgData = new MockTestOrgData();
40+
let MockedLightningDevComponent: typeof LightningDevComponent;
1941

2042
beforeEach(async () => {
2143
stubUx($$.SANDBOX);
2244
await $$.stubAuths(testOrgData);
45+
46+
$$.SANDBOX.stub(Connection.prototype, 'getUsername').returns(testUsername);
47+
$$.SANDBOX.stub(OrgUtils, 'isLocalDevEnabled').resolves(true);
48+
$$.SANDBOX.stub(OrgUtils, 'ensureMatchingAPIVersion').returns();
49+
$$.SANDBOX.stub(PreviewUtils, 'getOrCreateAppServerIdentity').resolves(testIdentityData);
50+
$$.SANDBOX.stub(PreviewUtils, 'initializePreviewConnection').resolves({
51+
ldpServerId: testLdpServerId,
52+
ldpServerToken: testLdpServerToken,
53+
connection: {} as Connection,
54+
});
55+
$$.SANDBOX.stub(PreviewUtils, 'generateWebSocketUrlForLocalDevServer').returns(testLdpServerUrl);
56+
$$.SANDBOX.stub(PreviewUtils, 'getNextAvailablePorts').resolves({ httpPort: 8081, httpsPort: 8082 });
57+
$$.SANDBOX.stub(ConfigUtils, 'getIdentityData').resolves(testIdentityData);
58+
$$.SANDBOX.stub(ComponentUtils, 'getNamespacePaths').resolves(['/mock/path']);
59+
$$.SANDBOX.stub(ComponentUtils, 'getAllComponentPaths').resolves(['/mock/component/testComponent']);
60+
$$.SANDBOX.stub(ComponentUtils, 'getComponentMetadata').resolves({
61+
LightningComponentBundle: {
62+
masterLabel: 'Test Component',
63+
description: 'Test component description',
64+
},
65+
});
66+
$$.SANDBOX.stub(ComponentUtils, 'componentNameToTitleCase').returns('Test Component');
67+
68+
MockedLightningDevComponent = await esmock<typeof LightningDevComponent>(
69+
'../../../../src/commands/lightning/dev/component.js',
70+
{
71+
'../../../../src/lwc-dev-server/index.js': {
72+
startLWCServer: async () => ({ stopServer: () => {} }),
73+
},
74+
}
75+
);
2376
});
2477

2578
afterEach(() => {
2679
$$.restore();
2780
});
2881

82+
it('throws when local dev not enabled', async () => {
83+
try {
84+
$$.SANDBOX.restore();
85+
$$.SANDBOX.stub(OrgUtils, 'isLocalDevEnabled').resolves(false);
86+
await LightningDevComponent.run(['--name', 'testComponent', '--target-org', testOrgData.orgId]);
87+
} catch (err) {
88+
expect(err).to.be.an('error').with.property('message', sharedMessages.getMessage('error.localdev.not.enabled'));
89+
}
90+
});
91+
92+
it('throws when username not found', async () => {
93+
try {
94+
$$.SANDBOX.restore();
95+
$$.SANDBOX.stub(OrgUtils, 'isLocalDevEnabled').resolves(true);
96+
$$.SANDBOX.stub(Connection.prototype, 'getUsername').returns(undefined);
97+
await LightningDevComponent.run(['--name', 'testComponent', '--target-org', testOrgData.orgId]);
98+
} catch (err) {
99+
expect(err).to.be.an('error').with.property('message', sharedMessages.getMessage('error.username'));
100+
}
101+
});
102+
103+
it('should include ldpServerId in preview URL', async () => {
104+
const runCmdStub = $$.SANDBOX.stub(OclifConfig.prototype, 'runCommand').resolves();
105+
106+
await MockedLightningDevComponent.run(['--name', 'testComponent', '--target-org', testOrgData.orgId]);
107+
108+
expect(runCmdStub.calledOnce).to.be.true;
109+
const [command, args] = runCmdStub.getCall(0).args;
110+
expect(command).to.equal('org:open');
111+
expect(args).to.be.an('array');
112+
expect(args).to.have.length.greaterThan(1);
113+
expect(args![1]).to.include(`ldpServerId=${testLdpServerId}`);
114+
});
115+
116+
it('should include ldpServerUrl in preview URL', async () => {
117+
const runCmdStub = $$.SANDBOX.stub(OclifConfig.prototype, 'runCommand').resolves();
118+
119+
await MockedLightningDevComponent.run(['--name', 'testComponent', '--target-org', testOrgData.orgId]);
120+
121+
expect(runCmdStub.calledOnce).to.be.true;
122+
const [command, args] = runCmdStub.getCall(0).args;
123+
expect(command).to.equal('org:open');
124+
expect(args).to.be.an('array');
125+
expect(args).to.have.length.greaterThan(1);
126+
expect(args![1]).to.include(`ldpServerUrl=${testLdpServerUrl}`);
127+
});
128+
129+
it('should include both ldpServerId and ldpServerUrl in preview URL when client-select flag is used', async () => {
130+
const runCmdStub = $$.SANDBOX.stub(OclifConfig.prototype, 'runCommand').resolves();
131+
132+
await MockedLightningDevComponent.run(['--client-select', '--target-org', testOrgData.orgId]);
133+
134+
expect(runCmdStub.calledOnce).to.be.true;
135+
const [command, args] = runCmdStub.getCall(0).args;
136+
expect(command).to.equal('org:open');
137+
expect(args).to.be.an('array');
138+
expect(args).to.have.length.greaterThan(1);
139+
expect(args![1]).to.include(`ldpServerId=${testLdpServerId}`);
140+
expect(args![1]).to.include(`ldpServerUrl=${testLdpServerUrl}`);
141+
});
142+
29143
it('should throw error when both client-select and performance flags are used', async () => {
30144
try {
31145
await LightningDevComponent.run(['--client-select', '--performance', '--target-org', testOrgData.orgId]);
@@ -34,4 +148,240 @@ describe('lightning single component preview', () => {
34148
expect((error as Error).message).to.equal(messages.getMessage('error.performance-client-select-conflict'));
35149
}
36150
});
151+
152+
it('should not include mode=performance in preview URL when no performance flag is provided', async () => {
153+
const runCmdStub = $$.SANDBOX.stub(OclifConfig.prototype, 'runCommand').resolves();
154+
155+
await MockedLightningDevComponent.run(['--name', 'testComponent', '--target-org', testOrgData.orgId]);
156+
157+
expect(runCmdStub.calledOnce).to.be.true;
158+
const [command, args] = runCmdStub.getCall(0).args;
159+
expect(command).to.equal('org:open');
160+
expect(args).to.be.an('array');
161+
expect(args).to.have.length.greaterThan(1);
162+
expect(args![1]).to.not.include('mode=performance');
163+
});
164+
165+
it('should not include mode=performance in preview URL when client-select flag is used', async () => {
166+
const runCmdStub = $$.SANDBOX.stub(OclifConfig.prototype, 'runCommand').resolves();
167+
168+
await MockedLightningDevComponent.run(['--client-select', '--target-org', testOrgData.orgId]);
169+
170+
expect(runCmdStub.calledOnce).to.be.true;
171+
const [command, args] = runCmdStub.getCall(0).args;
172+
expect(command).to.equal('org:open');
173+
expect(args).to.be.an('array');
174+
expect(args).to.have.length.greaterThan(1);
175+
expect(args![1]).to.not.include('mode=performance');
176+
});
177+
178+
it('should include mode=performance in preview URL when performance flag is explicitly enabled', async () => {
179+
const runCmdStub = $$.SANDBOX.stub(OclifConfig.prototype, 'runCommand').resolves();
180+
181+
await MockedLightningDevComponent.run([
182+
'--name',
183+
'testComponent',
184+
'--performance',
185+
'--target-org',
186+
testOrgData.orgId,
187+
]);
188+
189+
expect(runCmdStub.calledOnce).to.be.true;
190+
const [command, args] = runCmdStub.getCall(0).args;
191+
expect(command).to.equal('org:open');
192+
expect(args).to.be.an('array');
193+
expect(args).to.have.length.greaterThan(1);
194+
expect(args![1]).to.include('mode=performance');
195+
});
196+
197+
it('should include both ldpServerId and ldpServerUrl in preview URL when performance flag is enabled', async () => {
198+
const runCmdStub = $$.SANDBOX.stub(OclifConfig.prototype, 'runCommand').resolves();
199+
200+
await MockedLightningDevComponent.run([
201+
'--name',
202+
'testComponent',
203+
'--performance',
204+
'--target-org',
205+
testOrgData.orgId,
206+
]);
207+
208+
expect(runCmdStub.calledOnce).to.be.true;
209+
const [command, args] = runCmdStub.getCall(0).args;
210+
expect(command).to.equal('org:open');
211+
expect(args).to.be.an('array');
212+
expect(args).to.have.length.greaterThan(1);
213+
expect(args![1]).to.include(`ldpServerId=${testLdpServerId}`);
214+
expect(args![1]).to.include(`ldpServerUrl=${testLdpServerUrl}`);
215+
expect(args![1]).to.include('mode=performance');
216+
});
217+
218+
it('bypasses component selection prompt when --client-select flag is provided', async () => {
219+
const runCmdStub = $$.SANDBOX.stub(OclifConfig.prototype, 'runCommand').resolves();
220+
$$.SANDBOX.stub(PreviewUtils, 'getTargetOrgFromArguments').returns('--target-org=test-org');
221+
$$.SANDBOX.stub(PreviewUtils, 'generateComponentPreviewLaunchArguments').returns(['--path', '/test/url']);
222+
const promptStub = $$.SANDBOX.stub(PromptUtils, 'promptUserToSelectComponent');
223+
224+
await MockedLightningDevComponent.run(['--client-select', '--target-org', testOrgData.orgId]);
225+
226+
expect(runCmdStub.calledOnce).to.be.true;
227+
expect(promptStub.called).to.be.false;
228+
});
229+
230+
it('prompts user to select component when no --name flag provided and --client-select not used', async () => {
231+
const runCmdStub = $$.SANDBOX.stub(OclifConfig.prototype, 'runCommand').resolves();
232+
$$.SANDBOX.stub(PreviewUtils, 'getTargetOrgFromArguments').returns('--target-org=test-org');
233+
$$.SANDBOX.stub(PreviewUtils, 'generateComponentPreviewLaunchArguments').returns(['--path', '/test/url']);
234+
const promptStub = $$.SANDBOX.stub(PromptUtils, 'promptUserToSelectComponent').resolves('testComponent');
235+
236+
await MockedLightningDevComponent.run(['--target-org', testOrgData.orgId]);
237+
238+
expect(runCmdStub.calledOnce).to.be.true;
239+
expect(promptStub.calledOnce).to.be.true;
240+
});
241+
242+
it('should include specifier=c/<component-name> in preview URL when a component is selected via name flag', async () => {
243+
const runCmdStub = $$.SANDBOX.stub(OclifConfig.prototype, 'runCommand').resolves();
244+
245+
await MockedLightningDevComponent.run(['--name', 'testComponent', '--target-org', testOrgData.orgId]);
246+
247+
expect(runCmdStub.calledOnce).to.be.true;
248+
const [command, args] = runCmdStub.getCall(0).args;
249+
expect(command).to.equal('org:open');
250+
expect(args).to.be.an('array');
251+
expect(args).to.have.length.greaterThan(1);
252+
expect(args![1]).to.include('specifier=c/testComponent');
253+
});
254+
255+
it('should NOT include specifier query param when client-select flag is used', async () => {
256+
const runCmdStub = $$.SANDBOX.stub(OclifConfig.prototype, 'runCommand').resolves();
257+
258+
await MockedLightningDevComponent.run(['--client-select', '--target-org', testOrgData.orgId]);
259+
260+
expect(runCmdStub.calledOnce).to.be.true;
261+
const [command, args] = runCmdStub.getCall(0).args;
262+
expect(command).to.equal('org:open');
263+
expect(args).to.be.an('array');
264+
expect(args).to.have.length.greaterThan(1);
265+
expect(args![1]).to.not.include('specifier=');
266+
});
267+
});
268+
269+
describe('lightning single component preview - edge cases', () => {
270+
const $$ = new TestContext();
271+
let MockedLightningDevComponent: typeof LightningDevComponent;
272+
beforeEach(async () => {
273+
stubUx($$.SANDBOX);
274+
await $$.stubAuths(testOrgData);
275+
276+
$$.SANDBOX.stub(Connection.prototype, 'getUsername').returns(testUsername);
277+
$$.SANDBOX.stub(OrgUtils, 'isLocalDevEnabled').resolves(true);
278+
$$.SANDBOX.stub(OrgUtils, 'ensureMatchingAPIVersion').returns();
279+
$$.SANDBOX.stub(PreviewUtils, 'getOrCreateAppServerIdentity').resolves(testIdentityData);
280+
$$.SANDBOX.stub(PreviewUtils, 'initializePreviewConnection').resolves({
281+
ldpServerId: testLdpServerId,
282+
ldpServerToken: testLdpServerToken,
283+
connection: {} as Connection,
284+
});
285+
$$.SANDBOX.stub(PreviewUtils, 'generateWebSocketUrlForLocalDevServer').returns(testLdpServerUrl);
286+
$$.SANDBOX.stub(PreviewUtils, 'getNextAvailablePorts').resolves({ httpPort: 8081, httpsPort: 8082 });
287+
$$.SANDBOX.stub(ConfigUtils, 'getIdentityData').resolves(testIdentityData);
288+
$$.SANDBOX.stub(ComponentUtils, 'getNamespacePaths').resolves(['/mock/path']);
289+
290+
MockedLightningDevComponent = await esmock<typeof LightningDevComponent>(
291+
'../../../../src/commands/lightning/dev/component.js',
292+
{
293+
'../../../../src/lwc-dev-server/index.js': {
294+
startLWCServer: async () => ({ stopServer: () => {} }),
295+
},
296+
}
297+
);
298+
});
299+
300+
afterEach(() => {
301+
$$.restore();
302+
});
303+
304+
it('throws error.directory when no LWC components are found in project', async () => {
305+
$$.SANDBOX.stub(ComponentUtils, 'getAllComponentPaths').resolves(undefined);
306+
307+
try {
308+
await MockedLightningDevComponent.run(['--target-org', testOrgData.orgId]);
309+
expect.fail('Expected command to throw an error');
310+
} catch (error) {
311+
expect((error as Error).message).to.equal(messages.getMessage('error.directory'));
312+
}
313+
});
314+
315+
it('throws error.component-not-found when specified component name does not match any discovered components', async () => {
316+
$$.SANDBOX.stub(ComponentUtils, 'getAllComponentPaths').resolves(['/mock/component/differentComponent']);
317+
$$.SANDBOX.stub(ComponentUtils, 'getComponentMetadata').resolves({
318+
LightningComponentBundle: { masterLabel: 'Different Component', description: 'Different component description' },
319+
});
320+
$$.SANDBOX.stub(ComponentUtils, 'componentNameToTitleCase').returns('Different Component');
321+
322+
try {
323+
await MockedLightningDevComponent.run(['--name', 'nonExistentComponent', '--target-org', testOrgData.orgId]);
324+
expect.fail('Expected command to throw an error');
325+
} catch (error) {
326+
expect((error as Error).message).to.equal(
327+
messages.getMessage('error.component-not-found', ['nonExistentComponent'])
328+
);
329+
}
330+
});
331+
332+
it('throws error.component when user cancels component selection prompt', async () => {
333+
$$.SANDBOX.stub(ComponentUtils, 'getAllComponentPaths').resolves(['/mock/component/testComponent']);
334+
$$.SANDBOX.stub(ComponentUtils, 'getComponentMetadata').resolves({
335+
LightningComponentBundle: { masterLabel: 'Test Component', description: 'Test component description' },
336+
});
337+
$$.SANDBOX.stub(ComponentUtils, 'componentNameToTitleCase').returns('Test Component');
338+
$$.SANDBOX.stub(PromptUtils, 'promptUserToSelectComponent').resolves('');
339+
340+
try {
341+
await MockedLightningDevComponent.run(['--target-org', testOrgData.orgId]);
342+
expect.fail('Expected command to throw an error');
343+
} catch (error) {
344+
expect((error as Error).message).to.equal(messages.getMessage('error.component'));
345+
}
346+
});
347+
348+
it('successfully matches component by masterLabel when provided name equals component label', async () => {
349+
const runCmdStub = $$.SANDBOX.stub(OclifConfig.prototype, 'runCommand').resolves();
350+
$$.SANDBOX.stub(PreviewUtils, 'getTargetOrgFromArguments').returns('--target-org=test-org');
351+
$$.SANDBOX.stub(PreviewUtils, 'generateComponentPreviewLaunchArguments').returns(['--path', '/test/url']);
352+
$$.SANDBOX.stub(ComponentUtils, 'getAllComponentPaths').resolves(['/mock/component/testComponent']);
353+
$$.SANDBOX.stub(ComponentUtils, 'getComponentMetadata').resolves({
354+
LightningComponentBundle: {
355+
masterLabel: 'My Test Component',
356+
description: 'Test component description',
357+
},
358+
});
359+
$$.SANDBOX.stub(ComponentUtils, 'componentNameToTitleCase').returns('My Test Component');
360+
361+
await MockedLightningDevComponent.run(['--name', 'My Test Component', '--target-org', testOrgData.orgId]);
362+
363+
expect(runCmdStub.calledOnce).to.be.true;
364+
});
365+
366+
it('should include specifier=c/<component-name> in preview URL when a component is selected via prompt', async () => {
367+
const runCmdStub = $$.SANDBOX.stub(OclifConfig.prototype, 'runCommand').resolves();
368+
$$.SANDBOX.stub(ComponentUtils, 'getAllComponentPaths').resolves(['/mock/component/selectedComponent']);
369+
$$.SANDBOX.stub(ComponentUtils, 'getComponentMetadata').resolves({
370+
LightningComponentBundle: {
371+
masterLabel: 'Selected Component',
372+
description: 'Selected component description',
373+
},
374+
});
375+
$$.SANDBOX.stub(ComponentUtils, 'componentNameToTitleCase').returns('Selected Component');
376+
$$.SANDBOX.stub(PromptUtils, 'promptUserToSelectComponent').resolves('selectedComponent');
377+
378+
await MockedLightningDevComponent.run(['--target-org', testOrgData.orgId]);
379+
380+
expect(runCmdStub.calledOnce).to.be.true;
381+
const [command, args] = runCmdStub.getCall(0).args;
382+
expect(command).to.equal('org:open');
383+
expect(args).to.be.an('array');
384+
expect(args).to.have.length.greaterThan(1);
385+
expect(args![1]).to.include('specifier=c/selectedComponent');
386+
});
37387
});

0 commit comments

Comments
 (0)