Skip to content

Commit 88592b9

Browse files
spelak-salesforcesalesforce-org-metaci[bot]Sean Cuevo
authored
[W-9143348] Enforce LWC Jest code coverage (#49)
* Enforce LWC Jest code coverage - Enforces 100% LWC Jest code coverage among all measures except for the following block list: - `gauExpenditureRow` - `lookup` - `manageExpenditures` - `manageExpendituresTableHeader` - We needed at least one test at 100% code coverage, so fully tested warningBanner. - For a11y, needed to change to only render `<h2>{message}</h2>` if there is a message. Else, empty headers is not allowed. - warningBanner does not yet support translations. Exported English labels so test can assert the proper labels are used. - Updated `package.json` name to follow proper naming convention. * Update force-app/main/default/lwc/warningBanner/__tests__/warningBanner.test.js Co-authored-by: Sean Cuevo <scuevo@salesforce.com> * Update force-app/main/default/lwc/warningBanner/__tests__/warningBanner.test.js Co-authored-by: Sean Cuevo <scuevo@salesforce.com> Co-authored-by: salesforce-org-metaci[bot] <53311887+salesforce-org-metaci[bot]@users.noreply.github.com> Co-authored-by: Sean Cuevo <scuevo@salesforce.com>
1 parent e1bad83 commit 88592b9

6 files changed

Lines changed: 220 additions & 24 deletions

File tree

.github/workflows/jest.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ jobs:
2121
yarn install
2222
- name: Run Jest Tests
2323
run: |
24-
npx lwc-jest
24+
npx lwc-jest --coverage -- --passWithNoTests
Lines changed: 179 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
import { createElement } from "lwc";
2-
import WarningBanner from "c/warningBanner";
2+
import WarningBanner, {
3+
WARNING_ASSISTIVE_TEXT,
4+
WARNING_TITLE,
5+
WARNING_ICON_ALTERNATIVE_TEXT,
6+
INFO_ASSISTIVE_TEXT,
7+
INFO_TITLE,
8+
INFO_ICON_ALTERNATIVE_TEXT,
9+
CLOSE_ICON_ALTERNATIVE_TEXT,
10+
} from "c/warningBanner";
311

4-
describe("warning banner", () => {
12+
describe.each([
13+
["with message", `random number: ${Math.random()}`, "should display message"],
14+
["without message", null, "should not display message"],
15+
])("warning-banner %s", (_, message, shouldDisplayMessageTitle) => {
516
let component;
6-
const message = "message";
717

818
beforeEach(() => {
919
component = createElement("c-warning-banner", {
@@ -19,28 +29,187 @@ describe("warning banner", () => {
1929
component = undefined;
2030
});
2131

22-
describe("info", () => {
32+
describe("info variant", () => {
2333
beforeEach(() => {
2434
component.variant = "info-dismissable";
2535
document.body.appendChild(component);
36+
37+
// Return one Promise.resolve so <template> tags are re-rendered.
38+
return Promise.resolve();
2639
});
2740

2841
it("should be accessible", async () => {
29-
return Promise.resolve(() => {
42+
await expect(component).toBeAccessible();
43+
});
44+
45+
it("should display info alert", () => {
46+
// component has one child.
47+
expect(component.shadowRoot.childNodes.length).toEqual(1);
48+
49+
const alerts = component.shadowRoot.querySelectorAll(
50+
`div.slds-notify.slds-theme_info[role="alert"]`
51+
);
52+
expect(alerts).not.toBeNull();
53+
expect(alerts.length).toEqual(1);
54+
55+
// The info alert is the first child.
56+
expect(alerts[0]).toEqual(component.shadowRoot.childNodes[0]);
57+
});
58+
59+
it("should display info assistive text", () => {
60+
const assistiveTexts = component.shadowRoot.querySelectorAll(
61+
"div.slds-notify.slds-theme_info span.slds-assistive-text"
62+
);
63+
expect(assistiveTexts).not.toBeNull();
64+
expect(assistiveTexts.length).toEqual(1);
65+
expect(assistiveTexts[0].textContent).toEqual(INFO_ASSISTIVE_TEXT);
66+
});
67+
68+
it("should display info icon", () => {
69+
const iconContainers = component.shadowRoot.querySelectorAll(
70+
"div.slds-notify.slds-theme_info span.slds-icon_container.slds-m-right_x-small.slds-icon-utility-user"
71+
);
72+
expect(iconContainers).not.toBeNull();
73+
expect(iconContainers.length).toEqual(1);
74+
expect(iconContainers[0].title).toEqual(INFO_TITLE);
75+
76+
const icons = iconContainers[0].querySelectorAll("lightning-icon");
77+
expect(icons).not.toBeNull();
78+
expect(icons.length).toEqual(1);
79+
80+
const icon = icons[0];
81+
expect(icon.iconName).toEqual("utility:announcement");
82+
expect(icon.alternativeText).toEqual(INFO_ICON_ALTERNATIVE_TEXT);
83+
expect(icon.variant).toEqual("inverse");
84+
expect(icon.size).toEqual("x-small");
85+
});
86+
87+
it(shouldDisplayMessageTitle, () => {
88+
const messages = component.shadowRoot.querySelectorAll(
89+
"div.slds-notify.slds-theme_info h2"
90+
);
91+
expect(messages).not.toBeNull();
92+
93+
if (message) {
94+
expect(messages.length).toEqual(1);
95+
expect(messages[0].textContent).toEqual(message);
96+
} else {
97+
expect(messages.length).toEqual(0);
98+
}
99+
});
100+
101+
it("should display close icon", () => {
102+
const closeIcons = component.shadowRoot.querySelectorAll(
103+
`div.slds-notify.slds-theme_info > lightning-icon.slds-notify__close`
104+
);
105+
expect(closeIcons).not.toBeNull();
106+
expect(closeIcons.length).toEqual(1);
107+
108+
const closeIcon = closeIcons[0];
109+
expect(closeIcon.iconName).toEqual("utility:close");
110+
expect(closeIcon.alternativeText).toEqual(CLOSE_ICON_ALTERNATIVE_TEXT);
111+
expect(closeIcon.variant).toEqual("inverse");
112+
expect(closeIcon.size).toEqual("x-small");
113+
});
114+
115+
it("clicking close icon hides info alert", () => {
116+
const closeIcons = component.shadowRoot.querySelectorAll(
117+
`div.slds-notify.slds-theme_info > lightning-icon.slds-notify__close`
118+
);
119+
expect(closeIcons).not.toBeNull();
120+
expect(closeIcons.length).toEqual(1);
121+
122+
const closeIcon = closeIcons[0];
123+
closeIcon.click();
124+
125+
// Clicking close un-renders info alert.
126+
return Promise.resolve().then(() => {
127+
expect(component.shadowRoot.childNodes.length).toEqual(0);
128+
129+
// Component should still be accessible after closing.
30130
return expect(component).toBeAccessible();
31131
});
32132
});
33133
});
34134

35-
describe("warning", () => {
135+
describe.each([
136+
[`not "info-dismissable"`, `anything that is not exactly "info-dismissable"`],
137+
["null", null],
138+
])("default variant as warning: variant %s", (__, variant) => {
36139
beforeEach(() => {
140+
component.variant = variant;
37141
document.body.appendChild(component);
142+
143+
// Return one Promise.resolve so <template> tags are re-rendered.
144+
return Promise.resolve();
38145
});
39146

40-
it("should be accessible", () => {
41-
return Promise.resolve(() => {
42-
return expect(component).toBeAccessible();
43-
});
147+
it("should be accessible", async () => {
148+
await expect(component).toBeAccessible();
149+
});
150+
151+
it("should display warning alert", () => {
152+
// component has one child.
153+
expect(component.shadowRoot.childNodes.length).toEqual(1);
154+
155+
const alerts = component.shadowRoot.querySelectorAll(
156+
`div.slds-notify.slds-theme_warning[role="alert"]`
157+
);
158+
expect(alerts).not.toBeNull();
159+
expect(alerts.length).toEqual(1);
160+
161+
// The info alert is the first child.
162+
expect(alerts[0]).toEqual(component.shadowRoot.childNodes[0]);
163+
});
164+
165+
it("should display info assistive text", () => {
166+
const assistiveTexts = component.shadowRoot.querySelectorAll(
167+
"div.slds-notify.slds-theme_warning span.slds-assistive-text"
168+
);
169+
expect(assistiveTexts).not.toBeNull();
170+
expect(assistiveTexts.length).toEqual(1);
171+
expect(assistiveTexts[0].textContent).toEqual(WARNING_ASSISTIVE_TEXT);
172+
});
173+
174+
it("should display warning icon", () => {
175+
const iconContainers = component.shadowRoot.querySelectorAll(
176+
"div.slds-notify.slds-theme_warning span.slds-icon_container.slds-m-right_x-small.slds-icon-utility-warning"
177+
);
178+
expect(iconContainers).not.toBeNull();
179+
expect(iconContainers.length).toEqual(1);
180+
expect(iconContainers[0].title).toEqual(WARNING_TITLE);
181+
182+
const icons = iconContainers[0].querySelectorAll("lightning-icon");
183+
expect(icons).not.toBeNull();
184+
expect(icons.length).toEqual(1);
185+
186+
const icon = icons[0];
187+
expect(icon.iconName).toEqual("utility:warning");
188+
expect(icon.alternativeText).toEqual(WARNING_ICON_ALTERNATIVE_TEXT);
189+
expect(icon.variant).toEqual("warning");
190+
expect(icon.size).toEqual("x-small");
191+
});
192+
193+
it(shouldDisplayMessageTitle, () => {
194+
const messages = component.shadowRoot.querySelectorAll(
195+
"div.slds-notify.slds-theme_warning h2"
196+
);
197+
expect(messages).not.toBeNull();
198+
199+
if (message) {
200+
expect(messages.length).toEqual(1);
201+
expect(messages[0].textContent).toEqual(message);
202+
} else {
203+
expect(messages.length).toEqual(0);
204+
}
205+
});
206+
207+
it("should not display close icon", () => {
208+
const closeIcons = component.shadowRoot.querySelectorAll(
209+
`div.slds-notify.slds-theme_warning > lightning-icon.slds-notify__close`
210+
);
211+
expect(closeIcons).not.toBeNull();
212+
expect(closeIcons.length).toEqual(0);
44213
});
45214
});
46215
});

force-app/main/default/lwc/warningBanner/warningBanner.html

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
size="x-small"
1717
></lightning-icon>
1818
</span>
19-
<h2>{message}</h2>
19+
<template if:true={message}>
20+
<h2>{message}</h2>
21+
</template>
2022
</div>
2123
</template>
2224
<template if:true={info}>
@@ -36,7 +38,9 @@ <h2>{message}</h2>
3638
size="x-small"
3739
></lightning-icon>
3840
</span>
39-
<h2>{message}</h2>
41+
<template if:true={message}>
42+
<h2>{message}</h2>
43+
</template>
4044
<lightning-icon
4145
icon-name="utility:close"
4246
alternative-text={labels.close.icon.alternativeText}

force-app/main/default/lwc/warningBanner/warningBanner.js

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import { LightningElement, api, track } from "lwc";
22

33
// TODO: Localize with Custom Labels.
4-
const WARNING_ASSISTIVE_TEXT = "warning";
5-
const WARNING_TITLE = "Description of icon when needed";
6-
const WARNING_ICON_ALTERNATIVE_TEXT = "Warning!";
7-
8-
const INFO_ASSISTIVE_TEXT = "info";
9-
const INFO_TITLE = "Description of icon when needed";
10-
const INFO_ICON_ALTERNATIVE_TEXT = "Information";
11-
12-
const CLOSE_ICON_ALTERNATIVE_TEXT = "close";
4+
export const WARNING_ASSISTIVE_TEXT = "warning";
5+
export const WARNING_TITLE = "Description of icon when needed";
6+
export const WARNING_ICON_ALTERNATIVE_TEXT = "Warning!";
7+
export const INFO_ASSISTIVE_TEXT = "info";
8+
export const INFO_TITLE = "Description of icon when needed";
9+
export const INFO_ICON_ALTERNATIVE_TEXT = "Information";
10+
export const CLOSE_ICON_ALTERNATIVE_TEXT = "close";
1311

1412
export default class WarningBanner extends LightningElement {
1513
labels = {

jest.config.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,33 @@
11
const { jestConfig } = require("@salesforce/sfdx-lwc-jest/config");
2+
3+
const setupFilesAfterEnv = jestConfig.setupFilesAfterEnv || [];
4+
setupFilesAfterEnv.push("<rootDir>/jest.setup.js");
5+
6+
// Repo needs to have have 100% LWC Jest test code coverage across all measures.
7+
const coverageThreshold = jestConfig.coverageThreshold || {};
8+
/*
9+
coverageThreshold.global = {
10+
branches: 100,
11+
functions: 100,
12+
lines: 100,
13+
statements: 0,
14+
};
15+
*/
16+
// TODO: All LWC Jest tests require 100% code coverage that are not in the block list specified by this glob. Remove components from the block list specified in this glob when code coverage is met.
17+
coverageThreshold[
18+
"./force-app/main/default/lwc/!(gauExpenditureRow|lookup|manageExpenditures|manageExpendituresTableHeader)/**"
19+
] = {
20+
branches: 100,
21+
functions: 100,
22+
lines: 100,
23+
statements: 0,
24+
};
25+
226
module.exports = {
327
...jestConfig,
428
moduleNameMapper: {},
529
testPathIgnorePatterns: ["force-app/main/default/lwc/__(tests|mocks)__/"],
630
reporters: ["default"],
7-
setupFilesAfterEnv: ["./jest.setup.js"],
31+
setupFilesAfterEnv: setupFilesAfterEnv,
32+
coverageThreshold: coverageThreshold,
833
};

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "OutboundFundsNPSP",
2+
"name": "outboundfundsmodulenpsp",
33
"license": "UNLICENSED",
44
"author": "Salesforce.org Impact Engineering",
55
"devDependencies": {

0 commit comments

Comments
 (0)