scc-universal 1.1.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/.claude-plugin/plugin.json +44 -0
- package/.cursor/agents/deep-researcher.md +142 -0
- package/.cursor/agents/doc-updater.md +219 -0
- package/.cursor/agents/eval-runner.md +335 -0
- package/.cursor/agents/learning-engine.md +210 -0
- package/.cursor/agents/loop-operator.md +245 -0
- package/.cursor/agents/refactor-cleaner.md +119 -0
- package/.cursor/agents/sf-admin-agent.md +127 -0
- package/.cursor/agents/sf-agentforce-agent.md +126 -0
- package/.cursor/agents/sf-apex-agent.md +117 -0
- package/.cursor/agents/sf-architect.md +426 -0
- package/.cursor/agents/sf-aura-reviewer.md +369 -0
- package/.cursor/agents/sf-bugfix-agent.md +101 -0
- package/.cursor/agents/sf-flow-agent.md +155 -0
- package/.cursor/agents/sf-integration-agent.md +141 -0
- package/.cursor/agents/sf-lwc-agent.md +123 -0
- package/.cursor/agents/sf-review-agent.md +357 -0
- package/.cursor/agents/sf-visualforce-reviewer.md +465 -0
- package/.cursor/hooks/adapter.js +81 -0
- package/.cursor/hooks/after-file-edit.js +26 -0
- package/.cursor/hooks/after-mcp-execution.js +12 -0
- package/.cursor/hooks/after-shell-execution.js +30 -0
- package/.cursor/hooks/after-tab-file-edit.js +12 -0
- package/.cursor/hooks/before-mcp-execution.js +11 -0
- package/.cursor/hooks/before-read-file.js +13 -0
- package/.cursor/hooks/before-shell-execution.js +29 -0
- package/.cursor/hooks/before-submit-prompt.js +23 -0
- package/.cursor/hooks/pre-compact.js +7 -0
- package/.cursor/hooks/session-end.js +10 -0
- package/.cursor/hooks/session-start.js +10 -0
- package/.cursor/hooks/stop.js +18 -0
- package/.cursor/hooks/subagent-start.js +10 -0
- package/.cursor/hooks/subagent-stop.js +10 -0
- package/.cursor/hooks.json +107 -0
- package/.cursor/skills/aside/SKILL.md +115 -0
- package/.cursor/skills/checkpoint/SKILL.md +50 -0
- package/.cursor/skills/configure-scc/SKILL.md +160 -0
- package/.cursor/skills/continuous-agent-loop/SKILL.md +260 -0
- package/.cursor/skills/mcp-server-patterns/SKILL.md +142 -0
- package/.cursor/skills/model-route/SKILL.md +81 -0
- package/.cursor/skills/prompt-optimizer/SKILL.md +366 -0
- package/.cursor/skills/refactor-clean/SKILL.md +133 -0
- package/.cursor/skills/resume-session/SKILL.md +111 -0
- package/.cursor/skills/save-session/SKILL.md +183 -0
- package/.cursor/skills/search-first/SKILL.md +140 -0
- package/.cursor/skills/security-scan/SKILL.md +142 -0
- package/.cursor/skills/sessions/SKILL.md +124 -0
- package/.cursor/skills/sf-agentforce-development/SKILL.md +449 -0
- package/.cursor/skills/sf-apex-async-patterns/SKILL.md +324 -0
- package/.cursor/skills/sf-apex-best-practices/SKILL.md +421 -0
- package/.cursor/skills/sf-apex-constraints/SKILL.md +79 -0
- package/.cursor/skills/sf-apex-cursor/SKILL.md +336 -0
- package/.cursor/skills/sf-apex-enterprise-patterns/SKILL.md +344 -0
- package/.cursor/skills/sf-apex-testing/SKILL.md +407 -0
- package/.cursor/skills/sf-api-design/SKILL.md +237 -0
- package/.cursor/skills/sf-approval-processes/SKILL.md +312 -0
- package/.cursor/skills/sf-aura-development/SKILL.md +260 -0
- package/.cursor/skills/sf-build-fix/SKILL.md +120 -0
- package/.cursor/skills/sf-data-modeling/SKILL.md +274 -0
- package/.cursor/skills/sf-debugging/SKILL.md +362 -0
- package/.cursor/skills/sf-deployment/SKILL.md +291 -0
- package/.cursor/skills/sf-deployment-constraints/SKILL.md +153 -0
- package/.cursor/skills/sf-devops-ci-cd/SKILL.md +322 -0
- package/.cursor/skills/sf-docs-lookup/SKILL.md +100 -0
- package/.cursor/skills/sf-e2e-testing/SKILL.md +321 -0
- package/.cursor/skills/sf-experience-cloud/SKILL.md +248 -0
- package/.cursor/skills/sf-flow-development/SKILL.md +376 -0
- package/.cursor/skills/sf-governor-limits/SKILL.md +319 -0
- package/.cursor/skills/sf-harness-audit/SKILL.md +139 -0
- package/.cursor/skills/sf-help/SKILL.md +156 -0
- package/.cursor/skills/sf-integration/SKILL.md +479 -0
- package/.cursor/skills/sf-lwc-constraints/SKILL.md +128 -0
- package/.cursor/skills/sf-lwc-development/SKILL.md +302 -0
- package/.cursor/skills/sf-lwc-testing/SKILL.md +387 -0
- package/.cursor/skills/sf-metadata-management/SKILL.md +285 -0
- package/.cursor/skills/sf-platform-events-cdc/SKILL.md +372 -0
- package/.cursor/skills/sf-quickstart/SKILL.md +170 -0
- package/.cursor/skills/sf-security/SKILL.md +330 -0
- package/.cursor/skills/sf-security-constraints/SKILL.md +125 -0
- package/.cursor/skills/sf-soql-constraints/SKILL.md +129 -0
- package/.cursor/skills/sf-soql-optimization/SKILL.md +353 -0
- package/.cursor/skills/sf-tdd-workflow/SKILL.md +332 -0
- package/.cursor/skills/sf-testing-constraints/SKILL.md +198 -0
- package/.cursor/skills/sf-trigger-constraints/SKILL.md +88 -0
- package/.cursor/skills/sf-trigger-frameworks/SKILL.md +343 -0
- package/.cursor/skills/sf-visualforce-development/SKILL.md +259 -0
- package/.cursor/skills/strategic-compact/SKILL.md +205 -0
- package/.cursor/skills/update-docs/SKILL.md +162 -0
- package/.cursor/skills/update-platform-docs/SKILL.md +86 -0
- package/.cursor-plugin/plugin.json +26 -0
- package/LICENSE +21 -0
- package/README.md +522 -0
- package/agents/deep-researcher.md +145 -0
- package/agents/doc-updater.md +222 -0
- package/agents/eval-runner.md +340 -0
- package/agents/learning-engine.md +211 -0
- package/agents/loop-operator.md +247 -0
- package/agents/refactor-cleaner.md +122 -0
- package/agents/sf-admin-agent.md +131 -0
- package/agents/sf-agentforce-agent.md +132 -0
- package/agents/sf-apex-agent.md +124 -0
- package/agents/sf-architect.md +435 -0
- package/agents/sf-aura-reviewer.md +372 -0
- package/agents/sf-bugfix-agent.md +105 -0
- package/agents/sf-flow-agent.md +159 -0
- package/agents/sf-integration-agent.md +146 -0
- package/agents/sf-lwc-agent.md +127 -0
- package/agents/sf-review-agent.md +366 -0
- package/agents/sf-visualforce-reviewer.md +468 -0
- package/assets/logo.svg +18 -0
- package/docs/ARCHITECTURE.md +133 -0
- package/docs/authoring-guide.md +373 -0
- package/docs/hook-development.md +578 -0
- package/docs/token-optimization.md +139 -0
- package/docs/workflow-examples.md +645 -0
- package/examples/agentforce-action/README.md +227 -0
- package/examples/apex-trigger-handler/README.md +114 -0
- package/examples/devops-pipeline/README.md +325 -0
- package/examples/flow-automation/README.md +188 -0
- package/examples/integration-pattern/README.md +416 -0
- package/examples/lwc-component/README.md +180 -0
- package/examples/platform-events/README.md +492 -0
- package/examples/scratch-org-setup/README.md +138 -0
- package/examples/security-audit/README.md +244 -0
- package/examples/visualforce-migration/README.md +314 -0
- package/hooks/hooks.json +338 -0
- package/hooks/memory-persistence/README.md +73 -0
- package/manifests/install-modules.json +217 -0
- package/manifests/install-profiles.json +17 -0
- package/mcp-configs/mcp-servers.json +19 -0
- package/package.json +89 -0
- package/schemas/hooks.schema.json +123 -0
- package/schemas/install-modules.schema.json +76 -0
- package/schemas/install-profiles.schema.json +28 -0
- package/schemas/install-state.schema.json +73 -0
- package/schemas/package-manager.schema.json +18 -0
- package/schemas/plugin.schema.json +112 -0
- package/schemas/scc-install-config.schema.json +29 -0
- package/schemas/state-store.schema.json +111 -0
- package/scripts/cli/install-apply.js +170 -0
- package/scripts/cli/uninstall.js +193 -0
- package/scripts/hooks/check-console-log.js +101 -0
- package/scripts/hooks/check-hook-enabled.js +17 -0
- package/scripts/hooks/check-platform-docs-age.js +48 -0
- package/scripts/hooks/cost-tracker.js +78 -0
- package/scripts/hooks/doc-file-warning.js +63 -0
- package/scripts/hooks/evaluate-session.js +98 -0
- package/scripts/hooks/governor-check.js +220 -0
- package/scripts/hooks/learning-observe.sh +206 -0
- package/scripts/hooks/mcp-health-check.js +588 -0
- package/scripts/hooks/post-bash-build-complete.js +34 -0
- package/scripts/hooks/post-bash-pr-created.js +43 -0
- package/scripts/hooks/post-edit-console-warn.js +61 -0
- package/scripts/hooks/post-edit-format.js +79 -0
- package/scripts/hooks/post-edit-typecheck.js +98 -0
- package/scripts/hooks/post-write.js +168 -0
- package/scripts/hooks/pre-bash-git-push-reminder.js +35 -0
- package/scripts/hooks/pre-bash-tmux-reminder.js +47 -0
- package/scripts/hooks/pre-compact.js +51 -0
- package/scripts/hooks/pre-tool-use.js +163 -0
- package/scripts/hooks/pre-write-doc-warn.js +9 -0
- package/scripts/hooks/quality-gate.js +251 -0
- package/scripts/hooks/run-with-flags-shell.sh +32 -0
- package/scripts/hooks/run-with-flags.js +135 -0
- package/scripts/hooks/session-end-marker.js +29 -0
- package/scripts/hooks/session-end.js +311 -0
- package/scripts/hooks/session-start.js +202 -0
- package/scripts/hooks/sfdx-scanner-check.js +142 -0
- package/scripts/hooks/sfdx-validate.js +119 -0
- package/scripts/hooks/stop-hook.js +170 -0
- package/scripts/hooks/suggest-compact.js +67 -0
- package/scripts/lib/agent-adapter.js +82 -0
- package/scripts/lib/apex-analysis.js +194 -0
- package/scripts/lib/hook-flags.js +74 -0
- package/scripts/lib/install-config.js +73 -0
- package/scripts/lib/install-executor.js +363 -0
- package/scripts/lib/install-state.js +121 -0
- package/scripts/lib/orchestration-session.js +299 -0
- package/scripts/lib/package-manager.js +124 -0
- package/scripts/lib/project-detect.js +228 -0
- package/scripts/lib/schema-validator.js +190 -0
- package/scripts/lib/skill-adapter.js +100 -0
- package/scripts/lib/state-store.js +376 -0
- package/scripts/lib/tmux-worktree-orchestrator.js +598 -0
- package/scripts/lib/utils.js +313 -0
- package/scripts/scc.js +164 -0
- package/skills/_reference/AGENTFORCE_PATTERNS.md +112 -0
- package/skills/_reference/APEX_CURSOR.md +159 -0
- package/skills/_reference/API_VERSIONS.md +78 -0
- package/skills/_reference/APPROVAL_PROCESSES.md +105 -0
- package/skills/_reference/ASYNC_PATTERNS.md +163 -0
- package/skills/_reference/AURA_COMPONENTS.md +146 -0
- package/skills/_reference/DATA_MIGRATION_PATTERNS.md +151 -0
- package/skills/_reference/DATA_MODELING.md +124 -0
- package/skills/_reference/DEBUGGING_TOOLS.md +140 -0
- package/skills/_reference/DEPLOYMENT_CHECKLIST.md +87 -0
- package/skills/_reference/DEPRECATIONS.md +79 -0
- package/skills/_reference/DOCKER_CI_PATTERNS.md +138 -0
- package/skills/_reference/ENTERPRISE_PATTERNS.md +122 -0
- package/skills/_reference/EXPERIENCE_CLOUD.md +143 -0
- package/skills/_reference/FLOW_PATTERNS.md +113 -0
- package/skills/_reference/GOVERNOR_LIMITS.md +77 -0
- package/skills/_reference/INTEGRATION_PATTERNS.md +105 -0
- package/skills/_reference/LWC_PATTERNS.md +79 -0
- package/skills/_reference/METADATA_TYPES.md +115 -0
- package/skills/_reference/NAMING_CONVENTIONS.md +84 -0
- package/skills/_reference/PACKAGE_DEVELOPMENT.md +150 -0
- package/skills/_reference/PLATFORM_EVENTS.md +121 -0
- package/skills/_reference/REPORTING_API.md +143 -0
- package/skills/_reference/SCRATCH_ORG_PATTERNS.md +126 -0
- package/skills/_reference/SECURITY_PATTERNS.md +127 -0
- package/skills/_reference/SHARING_MODEL.md +120 -0
- package/skills/_reference/SOQL_PATTERNS.md +119 -0
- package/skills/_reference/TESTING_STANDARDS.md +96 -0
- package/skills/_reference/TRIGGER_PATTERNS.md +114 -0
- package/skills/_reference/VISUALFORCE_PATTERNS.md +121 -0
- package/skills/aside/SKILL.md +118 -0
- package/skills/checkpoint/SKILL.md +53 -0
- package/skills/configure-scc/SKILL.md +163 -0
- package/skills/continuous-agent-loop/SKILL.md +264 -0
- package/skills/mcp-server-patterns/SKILL.md +146 -0
- package/skills/model-route/SKILL.md +84 -0
- package/skills/prompt-optimizer/SKILL.md +369 -0
- package/skills/refactor-clean/SKILL.md +136 -0
- package/skills/resume-session/SKILL.md +114 -0
- package/skills/save-session/SKILL.md +186 -0
- package/skills/search-first/SKILL.md +144 -0
- package/skills/security-scan/SKILL.md +146 -0
- package/skills/sessions/SKILL.md +127 -0
- package/skills/sf-agentforce-development/SKILL.md +450 -0
- package/skills/sf-apex-async-patterns/SKILL.md +326 -0
- package/skills/sf-apex-best-practices/SKILL.md +425 -0
- package/skills/sf-apex-constraints/SKILL.md +81 -0
- package/skills/sf-apex-cursor/SKILL.md +338 -0
- package/skills/sf-apex-enterprise-patterns/SKILL.md +348 -0
- package/skills/sf-apex-testing/SKILL.md +409 -0
- package/skills/sf-api-design/SKILL.md +238 -0
- package/skills/sf-approval-processes/SKILL.md +315 -0
- package/skills/sf-aura-development/SKILL.md +263 -0
- package/skills/sf-build-fix/SKILL.md +121 -0
- package/skills/sf-data-modeling/SKILL.md +278 -0
- package/skills/sf-debugging/SKILL.md +363 -0
- package/skills/sf-deployment/SKILL.md +295 -0
- package/skills/sf-deployment-constraints/SKILL.md +155 -0
- package/skills/sf-devops-ci-cd/SKILL.md +325 -0
- package/skills/sf-docs-lookup/SKILL.md +103 -0
- package/skills/sf-e2e-testing/SKILL.md +324 -0
- package/skills/sf-experience-cloud/SKILL.md +249 -0
- package/skills/sf-flow-development/SKILL.md +377 -0
- package/skills/sf-governor-limits/SKILL.md +323 -0
- package/skills/sf-harness-audit/SKILL.md +142 -0
- package/skills/sf-help/SKILL.md +159 -0
- package/skills/sf-integration/SKILL.md +483 -0
- package/skills/sf-lwc-constraints/SKILL.md +130 -0
- package/skills/sf-lwc-development/SKILL.md +303 -0
- package/skills/sf-lwc-testing/SKILL.md +388 -0
- package/skills/sf-metadata-management/SKILL.md +288 -0
- package/skills/sf-platform-events-cdc/SKILL.md +375 -0
- package/skills/sf-quickstart/SKILL.md +173 -0
- package/skills/sf-security/SKILL.md +334 -0
- package/skills/sf-security-constraints/SKILL.md +127 -0
- package/skills/sf-soql-constraints/SKILL.md +131 -0
- package/skills/sf-soql-optimization/SKILL.md +354 -0
- package/skills/sf-tdd-workflow/SKILL.md +336 -0
- package/skills/sf-testing-constraints/SKILL.md +200 -0
- package/skills/sf-trigger-constraints/SKILL.md +90 -0
- package/skills/sf-trigger-frameworks/SKILL.md +347 -0
- package/skills/sf-visualforce-development/SKILL.md +260 -0
- package/skills/strategic-compact/SKILL.md +208 -0
- package/skills/update-docs/SKILL.md +165 -0
- package/skills/update-platform-docs/SKILL.md +90 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sf-lwc-development
|
|
3
|
+
description: "LWC development — components, reactive properties, wire service, Apex integration, events, lifecycle hooks. Use when building LWC components or debugging wire/reactivity. Do NOT use for Aura, Visualforce, or Flow."
|
|
4
|
+
origin: SCC
|
|
5
|
+
user-invocable: false
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# LWC Development
|
|
9
|
+
|
|
10
|
+
Lightning Web Components (LWC) is Salesforce's modern component model based on web standards — Custom Elements, Shadow DOM, and ES modules.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- When building new Lightning Web Components from scratch
|
|
15
|
+
- When migrating Aura components to LWC
|
|
16
|
+
- When debugging reactive property or wire service issues
|
|
17
|
+
- When implementing parent-child communication with events or @api
|
|
18
|
+
- When adding LWC components to record pages, app pages, or Experience Cloud sites
|
|
19
|
+
|
|
20
|
+
@../_reference/LWC_PATTERNS.md
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Component Creation Procedure
|
|
25
|
+
|
|
26
|
+
Every LWC component is a folder with at minimum an HTML template and a JavaScript class.
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
force-app/main/default/lwc/
|
|
30
|
+
accountList/
|
|
31
|
+
accountList.html <- Template
|
|
32
|
+
accountList.js <- Component class
|
|
33
|
+
accountList.css <- Component-scoped styles (optional)
|
|
34
|
+
accountList.js-meta.xml <- Metadata (targets, properties)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Step 1 — HTML Template
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<template>
|
|
41
|
+
<lightning-card title="Accounts" icon-name="standard:account">
|
|
42
|
+
<template lwc:if={isLoading}>
|
|
43
|
+
<lightning-spinner alternative-text="Loading" size="small"></lightning-spinner>
|
|
44
|
+
</template>
|
|
45
|
+
<template lwc:elseif={hasError}>
|
|
46
|
+
<p class="slds-text-color_error">{errorMessage}</p>
|
|
47
|
+
</template>
|
|
48
|
+
<template lwc:else>
|
|
49
|
+
<template for:each={accounts} for:item="account">
|
|
50
|
+
<div key={account.Id} class="account-row">
|
|
51
|
+
<span>{account.Name}</span>
|
|
52
|
+
<lightning-button label="View" data-id={account.Id}
|
|
53
|
+
onclick={handleViewAccount}></lightning-button>
|
|
54
|
+
</div>
|
|
55
|
+
</template>
|
|
56
|
+
</template>
|
|
57
|
+
</lightning-card>
|
|
58
|
+
</template>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Step 2 — JavaScript Class
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
import { LightningElement, api, wire } from 'lwc';
|
|
65
|
+
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
|
|
66
|
+
import { NavigationMixin } from 'lightning/navigation';
|
|
67
|
+
import getAccounts from '@salesforce/apex/AccountsController.getAccounts';
|
|
68
|
+
|
|
69
|
+
export default class AccountList extends NavigationMixin(LightningElement) {
|
|
70
|
+
@api recordId;
|
|
71
|
+
@api maxRecords = 10;
|
|
72
|
+
|
|
73
|
+
accounts = [];
|
|
74
|
+
isLoading = false;
|
|
75
|
+
error;
|
|
76
|
+
|
|
77
|
+
get hasError() { return this.error !== undefined; }
|
|
78
|
+
get isEmpty() { return !this.isLoading && this.accounts.length === 0; }
|
|
79
|
+
get errorMessage() {
|
|
80
|
+
return this.error?.body?.message ?? this.error?.message ?? 'An unknown error occurred.';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
connectedCallback() { this.loadAccounts(); }
|
|
84
|
+
|
|
85
|
+
handleViewAccount(event) {
|
|
86
|
+
this[NavigationMixin.Navigate]({
|
|
87
|
+
type: 'standard__recordPage',
|
|
88
|
+
attributes: { recordId: event.currentTarget.dataset.id,
|
|
89
|
+
objectApiName: 'Account', actionName: 'view' }
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async loadAccounts() {
|
|
94
|
+
this.isLoading = true;
|
|
95
|
+
this.error = undefined;
|
|
96
|
+
try {
|
|
97
|
+
this.accounts = await getAccounts({
|
|
98
|
+
searchTerm: this.searchTerm, maxRecords: this.maxRecords
|
|
99
|
+
});
|
|
100
|
+
} catch (error) {
|
|
101
|
+
this.error = error;
|
|
102
|
+
} finally {
|
|
103
|
+
this.isLoading = false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Step 3 — Meta XML
|
|
110
|
+
|
|
111
|
+
```xml
|
|
112
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
113
|
+
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
114
|
+
<apiVersion>66.0</apiVersion>
|
|
115
|
+
<isExposed>true</isExposed>
|
|
116
|
+
<targets>
|
|
117
|
+
<target>lightning__RecordPage</target>
|
|
118
|
+
<target>lightning__AppPage</target>
|
|
119
|
+
<target>lightning__HomePage</target>
|
|
120
|
+
</targets>
|
|
121
|
+
<targetConfigs>
|
|
122
|
+
<targetConfig targets="lightning__RecordPage">
|
|
123
|
+
<property name="maxRecords" type="Integer" default="10"
|
|
124
|
+
label="Maximum Records to Display" />
|
|
125
|
+
</targetConfig>
|
|
126
|
+
</targetConfigs>
|
|
127
|
+
</LightningComponentBundle>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Wire Service Usage
|
|
133
|
+
|
|
134
|
+
The wire service declaratively connects components to Salesforce data and re-runs when reactive parameters change.
|
|
135
|
+
|
|
136
|
+
### Wire with Apex
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
import { LightningElement, wire, api } from 'lwc';
|
|
140
|
+
import { refreshApex } from '@salesforce/apex';
|
|
141
|
+
import getAccountDetails from '@salesforce/apex/AccountsController.getAccountDetails';
|
|
142
|
+
|
|
143
|
+
export default class AccountDetails extends LightningElement {
|
|
144
|
+
@api recordId;
|
|
145
|
+
_wiredResult;
|
|
146
|
+
|
|
147
|
+
@wire(getAccountDetails, { accountId: '$recordId' })
|
|
148
|
+
wiredAccount(result) {
|
|
149
|
+
this._wiredResult = result;
|
|
150
|
+
if (result.data) {
|
|
151
|
+
this.account = result.data;
|
|
152
|
+
this.error = undefined;
|
|
153
|
+
} else if (result.error) {
|
|
154
|
+
this.error = result.error;
|
|
155
|
+
this.account = undefined;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async handleSave(event) {
|
|
160
|
+
await updateAccount({ accountId: this.recordId, fields: event.detail.fields });
|
|
161
|
+
await refreshApex(this._wiredResult);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Wire with Lightning Data Service
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
import { LightningElement, api, wire } from 'lwc';
|
|
170
|
+
import { getRecord, getFieldValue, updateRecord } from 'lightning/uiRecordApi';
|
|
171
|
+
import ACCOUNT_NAME from '@salesforce/schema/Account.Name';
|
|
172
|
+
import ACCOUNT_INDUSTRY from '@salesforce/schema/Account.Industry';
|
|
173
|
+
|
|
174
|
+
export default class AccountHeader extends LightningElement {
|
|
175
|
+
@api recordId;
|
|
176
|
+
|
|
177
|
+
@wire(getRecord, { recordId: '$recordId', fields: [ACCOUNT_NAME, ACCOUNT_INDUSTRY] })
|
|
178
|
+
account;
|
|
179
|
+
|
|
180
|
+
get name() { return getFieldValue(this.account.data, ACCOUNT_NAME); }
|
|
181
|
+
get industry() { return getFieldValue(this.account.data, ACCOUNT_INDUSTRY); }
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Event Handling — Component Communication
|
|
188
|
+
|
|
189
|
+
### Child to Parent: Custom Events
|
|
190
|
+
|
|
191
|
+
```javascript
|
|
192
|
+
// child: opportunityCard.js
|
|
193
|
+
handleSelect() {
|
|
194
|
+
this.dispatchEvent(new CustomEvent('opportunityselect', {
|
|
195
|
+
detail: { opportunityId: this.opportunity.Id },
|
|
196
|
+
bubbles: false, composed: false
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
```html
|
|
202
|
+
<!-- parent template -->
|
|
203
|
+
<c-opportunity-card opportunity={opp}
|
|
204
|
+
onopportunityselect={handleOpportunitySelect}></c-opportunity-card>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Cross-Component: Lightning Message Service
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
import { publish, subscribe, unsubscribe, MessageContext, APPLICATION_SCOPE }
|
|
211
|
+
from 'lightning/messageService';
|
|
212
|
+
import CHANNEL from '@salesforce/messageChannel/OpportunitySelected__c';
|
|
213
|
+
|
|
214
|
+
// Publisher
|
|
215
|
+
@wire(MessageContext) messageContext;
|
|
216
|
+
handleSelect(event) {
|
|
217
|
+
publish(this.messageContext, CHANNEL, { opportunityId: event.target.dataset.id });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Subscriber
|
|
221
|
+
connectedCallback() {
|
|
222
|
+
this.subscription = subscribe(this.messageContext, CHANNEL,
|
|
223
|
+
(msg) => this.handleMessage(msg), { scope: APPLICATION_SCOPE });
|
|
224
|
+
}
|
|
225
|
+
disconnectedCallback() { unsubscribe(this.subscription); }
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Slots — Composition Patterns
|
|
231
|
+
|
|
232
|
+
```html
|
|
233
|
+
<!-- child: modalWrapper.html -->
|
|
234
|
+
<template>
|
|
235
|
+
<div class="modal-header"><slot name="header"><h2>Default Header</h2></slot></div>
|
|
236
|
+
<div class="modal-body"><slot></slot></div>
|
|
237
|
+
<div class="modal-footer"><slot name="footer"></slot></div>
|
|
238
|
+
</template>
|
|
239
|
+
|
|
240
|
+
<!-- parent usage -->
|
|
241
|
+
<c-modal-wrapper>
|
|
242
|
+
<span slot="header">Edit Account</span>
|
|
243
|
+
<lightning-record-edit-form record-id={recordId} object-api-name="Account">
|
|
244
|
+
<lightning-input-field field-name="Name"></lightning-input-field>
|
|
245
|
+
</lightning-record-edit-form>
|
|
246
|
+
<div slot="footer">
|
|
247
|
+
<lightning-button label="Save" variant="brand" onclick={handleSave}></lightning-button>
|
|
248
|
+
</div>
|
|
249
|
+
</c-modal-wrapper>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Light DOM
|
|
255
|
+
|
|
256
|
+
Renders component markup directly into the parent DOM (no shadow boundary).
|
|
257
|
+
|
|
258
|
+
```javascript
|
|
259
|
+
export default class ThemedComponent extends LightningElement {
|
|
260
|
+
static renderMode = 'light';
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Use for: global CSS theming, third-party library integration, Experience Cloud sites, simple leaf components. Query with `this.querySelector()` instead of `this.template.querySelector()`.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Spring '26 Features
|
|
269
|
+
|
|
270
|
+
### SLDS 2.0
|
|
271
|
+
|
|
272
|
+
Use `--slds-c-*` styling hooks (replaces `--lwc-*` design tokens). Run `npx slds-lint` to check compliance.
|
|
273
|
+
|
|
274
|
+
### TypeScript Support
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
npm install --save-dev @salesforce/lightning-types
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Complex Template Expressions (Beta)
|
|
281
|
+
|
|
282
|
+
```html
|
|
283
|
+
<p>{account.Name ?? 'Unknown Account'}</p>
|
|
284
|
+
<p>{formatRevenue(account.AnnualRevenue)}</p>
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Use getters for production code until this reaches GA.
|
|
288
|
+
|
|
289
|
+
### LWC in Screen Flows
|
|
290
|
+
|
|
291
|
+
Expose components as Flow screen actions via `lightning__FlowScreen` target. Implement `@api validate()` for Flow navigation validation.
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Related
|
|
296
|
+
|
|
297
|
+
### Guardrails
|
|
298
|
+
|
|
299
|
+
- **sf-lwc-constraints** — Enforced rules for LWC naming, reactivity, security, and accessibility
|
|
300
|
+
|
|
301
|
+
### Agents
|
|
302
|
+
|
|
303
|
+
- **sf-lwc-agent** — For interactive, in-depth LWC review guidance
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sf-lwc-testing
|
|
3
|
+
description: "LWC Jest testing — component mounting, wire/Apex mocking, user interaction simulation, toast/navigation verification. Use when writing or debugging LWC Jest tests. Do NOT use for Apex or Flow testing."
|
|
4
|
+
origin: SCC
|
|
5
|
+
user-invocable: false
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# LWC Testing with Jest
|
|
9
|
+
|
|
10
|
+
LWC uses Jest as its test runner. Salesforce provides `@salesforce/sfdx-lwc-jest` to handle Salesforce-specific imports. Tests run in Node.js — no browser, no Salesforce org.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- When writing Jest unit tests for Lightning Web Components
|
|
15
|
+
- When mocking Wire adapters, Apex imperative calls, or navigation in LWC tests
|
|
16
|
+
- When debugging flaky or async LWC tests
|
|
17
|
+
- When setting up LWC test infrastructure for a new project
|
|
18
|
+
|
|
19
|
+
@../_reference/LWC_PATTERNS.md
|
|
20
|
+
@../_reference/TESTING_STANDARDS.md
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Setup Procedure
|
|
25
|
+
|
|
26
|
+
### Step 1 — Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install --save-dev @salesforce/sfdx-lwc-jest
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Step 2 — jest.config.js
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
const { jestConfig } = require('@salesforce/sfdx-lwc-jest/config');
|
|
36
|
+
module.exports = {
|
|
37
|
+
...jestConfig,
|
|
38
|
+
modulePathIgnorePatterns: ['<rootDir>/.localdevserver'],
|
|
39
|
+
testEnvironment: 'jsdom',
|
|
40
|
+
testMatch: ['**/__tests__/**/*.test.js'],
|
|
41
|
+
setupFiles: ['<rootDir>/jest.setup.js']
|
|
42
|
+
};
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Step 3 — jest.setup.js
|
|
46
|
+
|
|
47
|
+
```javascript
|
|
48
|
+
global.ResizeObserver = jest.fn().mockImplementation(() => ({
|
|
49
|
+
observe: jest.fn(), unobserve: jest.fn(), disconnect: jest.fn()
|
|
50
|
+
}));
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Step 4 — Test File Location
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
lwc/accountSearch/
|
|
57
|
+
accountSearch.html
|
|
58
|
+
accountSearch.js
|
|
59
|
+
__tests__/
|
|
60
|
+
accountSearch.test.js
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Basic Component Rendering
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
import { createElement } from 'lwc';
|
|
69
|
+
import AccountSearch from 'c/accountSearch';
|
|
70
|
+
|
|
71
|
+
describe('c-account-search', () => {
|
|
72
|
+
afterEach(() => {
|
|
73
|
+
while (document.body.firstChild) {
|
|
74
|
+
document.body.removeChild(document.body.firstChild);
|
|
75
|
+
}
|
|
76
|
+
jest.clearAllMocks();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('renders the search input', () => {
|
|
80
|
+
const element = createElement('c-account-search', { is: AccountSearch });
|
|
81
|
+
document.body.appendChild(element);
|
|
82
|
+
|
|
83
|
+
const input = element.shadowRoot.querySelector('lightning-input[type="search"]');
|
|
84
|
+
expect(input).not.toBeNull();
|
|
85
|
+
expect(input.label).toBe('Search Accounts');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('renders with public @api property', () => {
|
|
89
|
+
const element = createElement('c-account-search', { is: AccountSearch });
|
|
90
|
+
element.maxRecords = 25;
|
|
91
|
+
document.body.appendChild(element);
|
|
92
|
+
expect(element.maxRecords).toBe(25);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Wire Adapter Mocking (Modern Pattern)
|
|
100
|
+
|
|
101
|
+
Use `jest.mock()` with the wire adapter directly. The deprecated `registerApexTestWireAdapter` pattern should not be used in new projects.
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
import { createElement } from 'lwc';
|
|
105
|
+
import AccountDetails from 'c/accountDetails';
|
|
106
|
+
import getAccountDetails from '@salesforce/apex/AccountsController.getAccountDetails';
|
|
107
|
+
|
|
108
|
+
jest.mock(
|
|
109
|
+
'@salesforce/apex/AccountsController.getAccountDetails',
|
|
110
|
+
() => ({ default: jest.fn() }),
|
|
111
|
+
{ virtual: true }
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
describe('c-account-details wire', () => {
|
|
115
|
+
afterEach(() => {
|
|
116
|
+
while (document.body.firstChild) {
|
|
117
|
+
document.body.removeChild(document.body.firstChild);
|
|
118
|
+
}
|
|
119
|
+
jest.clearAllMocks();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('displays account name when wire returns data', async () => {
|
|
123
|
+
getAccountDetails.mockResolvedValue({
|
|
124
|
+
Id: '001000000000001AAA', Name: 'Acme Corporation'
|
|
125
|
+
});
|
|
126
|
+
const element = createElement('c-account-details', { is: AccountDetails });
|
|
127
|
+
element.recordId = '001000000000001AAA';
|
|
128
|
+
document.body.appendChild(element);
|
|
129
|
+
|
|
130
|
+
await Promise.resolve();
|
|
131
|
+
await Promise.resolve();
|
|
132
|
+
|
|
133
|
+
expect(element.shadowRoot.querySelector('.account-name').textContent)
|
|
134
|
+
.toBe('Acme Corporation');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('displays error state when wire returns error', async () => {
|
|
138
|
+
getAccountDetails.mockRejectedValue({
|
|
139
|
+
body: { message: 'Record not found' }, status: 404
|
|
140
|
+
});
|
|
141
|
+
const element = createElement('c-account-details', { is: AccountDetails });
|
|
142
|
+
element.recordId = '001000000000001AAA';
|
|
143
|
+
document.body.appendChild(element);
|
|
144
|
+
|
|
145
|
+
await Promise.resolve();
|
|
146
|
+
await Promise.resolve();
|
|
147
|
+
|
|
148
|
+
expect(element.shadowRoot.querySelector('.error-container')).not.toBeNull();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Apex Imperative Call Mocking
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
jest.mock(
|
|
159
|
+
'@salesforce/apex/AccountSearchController.searchAccounts',
|
|
160
|
+
() => jest.fn(), // imperative: module IS the function
|
|
161
|
+
{ virtual: true }
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
import searchAccounts from '@salesforce/apex/AccountSearchController.searchAccounts';
|
|
165
|
+
|
|
166
|
+
it('calls Apex on search and displays results', async () => {
|
|
167
|
+
searchAccounts.mockResolvedValue([
|
|
168
|
+
{ Id: '001000000000001AAA', Name: 'Acme Corp' }
|
|
169
|
+
]);
|
|
170
|
+
const element = createElement('c-account-search', { is: AccountSearch });
|
|
171
|
+
document.body.appendChild(element);
|
|
172
|
+
|
|
173
|
+
// Trigger search
|
|
174
|
+
const input = element.shadowRoot.querySelector('lightning-input');
|
|
175
|
+
input.dispatchEvent(new CustomEvent('change', { detail: { value: 'Acme' } }));
|
|
176
|
+
element.shadowRoot.querySelector('lightning-button[label="Search"]').click();
|
|
177
|
+
|
|
178
|
+
await flushPromises();
|
|
179
|
+
|
|
180
|
+
expect(searchAccounts).toHaveBeenCalledWith({ searchTerm: 'Acme' });
|
|
181
|
+
const rows = element.shadowRoot.querySelectorAll('.account-row');
|
|
182
|
+
expect(rows).toHaveLength(1);
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Key distinction: for imperative Apex, mock as `() => jest.fn()`. For wired Apex, mock as `() => ({ default: jest.fn() })`.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Async Testing — flushPromises
|
|
191
|
+
|
|
192
|
+
LWC re-renders are asynchronous. Use `flushPromises` instead of chaining multiple `Promise.resolve()` calls.
|
|
193
|
+
|
|
194
|
+
```javascript
|
|
195
|
+
function flushPromises() {
|
|
196
|
+
return new Promise(resolve => setTimeout(resolve, 0));
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## User Interaction Simulation
|
|
203
|
+
|
|
204
|
+
### Click Events and Custom Event Assertions
|
|
205
|
+
|
|
206
|
+
```javascript
|
|
207
|
+
it('dispatches select event when button clicked', () => {
|
|
208
|
+
const element = createElement('c-account-card', { is: AccountCard });
|
|
209
|
+
element.account = { Id: '001000000000001AAA', Name: 'Test Corp' };
|
|
210
|
+
document.body.appendChild(element);
|
|
211
|
+
|
|
212
|
+
const handler = jest.fn();
|
|
213
|
+
element.addEventListener('accountselect', handler);
|
|
214
|
+
|
|
215
|
+
element.shadowRoot.querySelector('[data-id="view-btn"]').click();
|
|
216
|
+
|
|
217
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
218
|
+
expect(handler.mock.calls[0][0].detail).toEqual({
|
|
219
|
+
accountId: '001000000000001AAA', accountName: 'Test Corp'
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Input Value Changes
|
|
225
|
+
|
|
226
|
+
```javascript
|
|
227
|
+
const input = element.shadowRoot.querySelector('lightning-input[type="search"]');
|
|
228
|
+
input.dispatchEvent(new CustomEvent('change', { detail: { value: 'Acme' } }));
|
|
229
|
+
await Promise.resolve();
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Toast Notification Testing
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
import { ShowToastEventName } from 'lightning/platformShowToastEvent';
|
|
238
|
+
|
|
239
|
+
it('shows success toast after save', async () => {
|
|
240
|
+
const toastHandler = jest.fn();
|
|
241
|
+
element.addEventListener(ShowToastEventName, toastHandler);
|
|
242
|
+
|
|
243
|
+
element.shadowRoot.querySelector('[data-id="save-btn"]').click();
|
|
244
|
+
await flushPromises();
|
|
245
|
+
|
|
246
|
+
expect(toastHandler).toHaveBeenCalledTimes(1);
|
|
247
|
+
expect(toastHandler.mock.calls[0][0].detail.variant).toBe('success');
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Navigation Mocking
|
|
254
|
+
|
|
255
|
+
```javascript
|
|
256
|
+
it('navigates to record page on view', () => {
|
|
257
|
+
const { navigate } = require('lightning/navigation');
|
|
258
|
+
const element = createElement('c-account-card', { is: AccountCard });
|
|
259
|
+
element.account = { Id: '001000000000001AAA' };
|
|
260
|
+
document.body.appendChild(element);
|
|
261
|
+
|
|
262
|
+
element.shadowRoot.querySelector('[data-id="view"]').click();
|
|
263
|
+
|
|
264
|
+
expect(navigate).toHaveBeenCalledWith(
|
|
265
|
+
expect.objectContaining({
|
|
266
|
+
type: 'standard__recordPage',
|
|
267
|
+
attributes: expect.objectContaining({ recordId: '001000000000001AAA' })
|
|
268
|
+
})
|
|
269
|
+
);
|
|
270
|
+
});
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## What to Test vs What Not to Test
|
|
276
|
+
|
|
277
|
+
### Test These
|
|
278
|
+
|
|
279
|
+
- Business logic in computed properties and event handlers
|
|
280
|
+
- Correct Apex method called with correct arguments
|
|
281
|
+
- Component response to wire data and errors
|
|
282
|
+
- User interactions and their effects on component state
|
|
283
|
+
- Edge cases: empty arrays, null values, error states
|
|
284
|
+
|
|
285
|
+
### Do Not Test These
|
|
286
|
+
|
|
287
|
+
- LWC framework behavior (that `for:each` renders items)
|
|
288
|
+
- Base component rendering (that `lightning-button` shows text)
|
|
289
|
+
- CSS styling or implementation details (internal variable names)
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Spring '26: Local Component Testing
|
|
294
|
+
|
|
295
|
+
Test LWC components in isolation without deploying to a scratch org.
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
npm install --save-dev @lwc/jest-preset
|
|
299
|
+
npx lwc-jest --watchAll=false
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## TypeScript LWC Testing (Spring '26 Experimental)
|
|
305
|
+
|
|
306
|
+
Spring '26 introduces experimental TypeScript support for LWC. Test `.ts` component files with these adjustments.
|
|
307
|
+
|
|
308
|
+
### Jest Configuration
|
|
309
|
+
|
|
310
|
+
```javascript
|
|
311
|
+
// jest.config.js — add ts transform
|
|
312
|
+
const { jestConfig } = require('@salesforce/sfdx-lwc-jest/config');
|
|
313
|
+
module.exports = {
|
|
314
|
+
...jestConfig,
|
|
315
|
+
transform: {
|
|
316
|
+
...jestConfig.transform,
|
|
317
|
+
'^.+\\.ts$': ['@swc/jest'] // or 'ts-jest'
|
|
318
|
+
},
|
|
319
|
+
moduleFileExtensions: ['ts', 'js', 'html'],
|
|
320
|
+
testMatch: ['**/__tests__/**/*.test.(js|ts)']
|
|
321
|
+
};
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Install: `npm install --save-dev @swc/jest @swc/core` (faster) or `npm install --save-dev ts-jest typescript`.
|
|
325
|
+
|
|
326
|
+
### Typed Wire Adapter Mocking
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
import { createElement } from 'lwc';
|
|
330
|
+
import AccountList from 'c/accountList';
|
|
331
|
+
import getAccounts from '@salesforce/apex/AccountController.getAccounts';
|
|
332
|
+
|
|
333
|
+
jest.mock('@salesforce/apex/AccountController.getAccounts',
|
|
334
|
+
() => ({ default: jest.fn() }), { virtual: true });
|
|
335
|
+
|
|
336
|
+
const mockGetAccounts = getAccounts as jest.MockedFunction<typeof getAccounts>;
|
|
337
|
+
|
|
338
|
+
describe('c-account-list (TypeScript)', () => {
|
|
339
|
+
afterEach(() => {
|
|
340
|
+
while (document.body.firstChild) document.body.removeChild(document.body.firstChild);
|
|
341
|
+
jest.clearAllMocks();
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('renders typed account data', async () => {
|
|
345
|
+
mockGetAccounts.mockResolvedValue([
|
|
346
|
+
{ Id: '001xx0001', Name: 'Typed Corp', Industry: 'Technology' }
|
|
347
|
+
]);
|
|
348
|
+
const element = createElement('c-account-list', { is: AccountList });
|
|
349
|
+
document.body.appendChild(element);
|
|
350
|
+
|
|
351
|
+
await Promise.resolve();
|
|
352
|
+
await Promise.resolve();
|
|
353
|
+
|
|
354
|
+
const rows = element.shadowRoot.querySelectorAll('.account-row');
|
|
355
|
+
expect(rows).toHaveLength(1);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Typed Event Detail Assertions
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
it('dispatches typed custom event', () => {
|
|
364
|
+
const element = createElement('c-account-card', { is: AccountCard });
|
|
365
|
+
document.body.appendChild(element);
|
|
366
|
+
|
|
367
|
+
const handler = jest.fn();
|
|
368
|
+
element.addEventListener('select', handler);
|
|
369
|
+
element.shadowRoot.querySelector('[data-id="select-btn"]').click();
|
|
370
|
+
|
|
371
|
+
const detail = handler.mock.calls[0][0].detail as { accountId: string };
|
|
372
|
+
expect(detail.accountId).toBeDefined();
|
|
373
|
+
});
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
> **Note:** TypeScript LWC support is experimental in Spring '26. Type definitions and tooling may change in future releases. Pin `@salesforce/sfdx-lwc-jest` to a known-good version.
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## Related
|
|
381
|
+
|
|
382
|
+
### Guardrails
|
|
383
|
+
|
|
384
|
+
- **sf-testing-constraints** — Enforced rules for test coverage, assertions, and test data patterns
|
|
385
|
+
|
|
386
|
+
### Agents
|
|
387
|
+
|
|
388
|
+
- **sf-lwc-agent** — For interactive, in-depth LWC review guidance
|