Skip to content

Commit

Permalink
[W-9143348] Enforce LWC Jest code coverage (#49)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* Update force-app/main/default/lwc/warningBanner/__tests__/warningBanner.test.js

Co-authored-by: Sean Cuevo <[email protected]>

Co-authored-by: salesforce-org-metaci[bot] <53311887+salesforce-org-metaci[bot]@users.noreply.github.com>
Co-authored-by: Sean Cuevo <[email protected]>
  • Loading branch information
3 people authored Apr 19, 2021
1 parent e1bad83 commit 88592b9
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 24 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/jest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ jobs:
yarn install
- name: Run Jest Tests
run: |
npx lwc-jest
npx lwc-jest --coverage -- --passWithNoTests
189 changes: 179 additions & 10 deletions force-app/main/default/lwc/warningBanner/__tests__/warningBanner.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import { createElement } from "lwc";
import WarningBanner from "c/warningBanner";
import WarningBanner, {
WARNING_ASSISTIVE_TEXT,
WARNING_TITLE,
WARNING_ICON_ALTERNATIVE_TEXT,
INFO_ASSISTIVE_TEXT,
INFO_TITLE,
INFO_ICON_ALTERNATIVE_TEXT,
CLOSE_ICON_ALTERNATIVE_TEXT,
} from "c/warningBanner";

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

beforeEach(() => {
component = createElement("c-warning-banner", {
Expand All @@ -19,28 +29,187 @@ describe("warning banner", () => {
component = undefined;
});

describe("info", () => {
describe("info variant", () => {
beforeEach(() => {
component.variant = "info-dismissable";
document.body.appendChild(component);

// Return one Promise.resolve so <template> tags are re-rendered.
return Promise.resolve();
});

it("should be accessible", async () => {
return Promise.resolve(() => {
await expect(component).toBeAccessible();
});

it("should display info alert", () => {
// component has one child.
expect(component.shadowRoot.childNodes.length).toEqual(1);

const alerts = component.shadowRoot.querySelectorAll(
`div.slds-notify.slds-theme_info[role="alert"]`
);
expect(alerts).not.toBeNull();
expect(alerts.length).toEqual(1);

// The info alert is the first child.
expect(alerts[0]).toEqual(component.shadowRoot.childNodes[0]);
});

it("should display info assistive text", () => {
const assistiveTexts = component.shadowRoot.querySelectorAll(
"div.slds-notify.slds-theme_info span.slds-assistive-text"
);
expect(assistiveTexts).not.toBeNull();
expect(assistiveTexts.length).toEqual(1);
expect(assistiveTexts[0].textContent).toEqual(INFO_ASSISTIVE_TEXT);
});

it("should display info icon", () => {
const iconContainers = component.shadowRoot.querySelectorAll(
"div.slds-notify.slds-theme_info span.slds-icon_container.slds-m-right_x-small.slds-icon-utility-user"
);
expect(iconContainers).not.toBeNull();
expect(iconContainers.length).toEqual(1);
expect(iconContainers[0].title).toEqual(INFO_TITLE);

const icons = iconContainers[0].querySelectorAll("lightning-icon");
expect(icons).not.toBeNull();
expect(icons.length).toEqual(1);

const icon = icons[0];
expect(icon.iconName).toEqual("utility:announcement");
expect(icon.alternativeText).toEqual(INFO_ICON_ALTERNATIVE_TEXT);
expect(icon.variant).toEqual("inverse");
expect(icon.size).toEqual("x-small");
});

it(shouldDisplayMessageTitle, () => {
const messages = component.shadowRoot.querySelectorAll(
"div.slds-notify.slds-theme_info h2"
);
expect(messages).not.toBeNull();

if (message) {
expect(messages.length).toEqual(1);
expect(messages[0].textContent).toEqual(message);
} else {
expect(messages.length).toEqual(0);
}
});

it("should display close icon", () => {
const closeIcons = component.shadowRoot.querySelectorAll(
`div.slds-notify.slds-theme_info > lightning-icon.slds-notify__close`
);
expect(closeIcons).not.toBeNull();
expect(closeIcons.length).toEqual(1);

const closeIcon = closeIcons[0];
expect(closeIcon.iconName).toEqual("utility:close");
expect(closeIcon.alternativeText).toEqual(CLOSE_ICON_ALTERNATIVE_TEXT);
expect(closeIcon.variant).toEqual("inverse");
expect(closeIcon.size).toEqual("x-small");
});

it("clicking close icon hides info alert", () => {
const closeIcons = component.shadowRoot.querySelectorAll(
`div.slds-notify.slds-theme_info > lightning-icon.slds-notify__close`
);
expect(closeIcons).not.toBeNull();
expect(closeIcons.length).toEqual(1);

const closeIcon = closeIcons[0];
closeIcon.click();

// Clicking close un-renders info alert.
return Promise.resolve().then(() => {
expect(component.shadowRoot.childNodes.length).toEqual(0);

// Component should still be accessible after closing.
return expect(component).toBeAccessible();
});
});
});

describe("warning", () => {
describe.each([
[`not "info-dismissable"`, `anything that is not exactly "info-dismissable"`],
["null", null],
])("default variant as warning: variant %s", (__, variant) => {
beforeEach(() => {
component.variant = variant;
document.body.appendChild(component);

// Return one Promise.resolve so <template> tags are re-rendered.
return Promise.resolve();
});

it("should be accessible", () => {
return Promise.resolve(() => {
return expect(component).toBeAccessible();
});
it("should be accessible", async () => {
await expect(component).toBeAccessible();
});

it("should display warning alert", () => {
// component has one child.
expect(component.shadowRoot.childNodes.length).toEqual(1);

const alerts = component.shadowRoot.querySelectorAll(
`div.slds-notify.slds-theme_warning[role="alert"]`
);
expect(alerts).not.toBeNull();
expect(alerts.length).toEqual(1);

// The info alert is the first child.
expect(alerts[0]).toEqual(component.shadowRoot.childNodes[0]);
});

it("should display info assistive text", () => {
const assistiveTexts = component.shadowRoot.querySelectorAll(
"div.slds-notify.slds-theme_warning span.slds-assistive-text"
);
expect(assistiveTexts).not.toBeNull();
expect(assistiveTexts.length).toEqual(1);
expect(assistiveTexts[0].textContent).toEqual(WARNING_ASSISTIVE_TEXT);
});

it("should display warning icon", () => {
const iconContainers = component.shadowRoot.querySelectorAll(
"div.slds-notify.slds-theme_warning span.slds-icon_container.slds-m-right_x-small.slds-icon-utility-warning"
);
expect(iconContainers).not.toBeNull();
expect(iconContainers.length).toEqual(1);
expect(iconContainers[0].title).toEqual(WARNING_TITLE);

const icons = iconContainers[0].querySelectorAll("lightning-icon");
expect(icons).not.toBeNull();
expect(icons.length).toEqual(1);

const icon = icons[0];
expect(icon.iconName).toEqual("utility:warning");
expect(icon.alternativeText).toEqual(WARNING_ICON_ALTERNATIVE_TEXT);
expect(icon.variant).toEqual("warning");
expect(icon.size).toEqual("x-small");
});

it(shouldDisplayMessageTitle, () => {
const messages = component.shadowRoot.querySelectorAll(
"div.slds-notify.slds-theme_warning h2"
);
expect(messages).not.toBeNull();

if (message) {
expect(messages.length).toEqual(1);
expect(messages[0].textContent).toEqual(message);
} else {
expect(messages.length).toEqual(0);
}
});

it("should not display close icon", () => {
const closeIcons = component.shadowRoot.querySelectorAll(
`div.slds-notify.slds-theme_warning > lightning-icon.slds-notify__close`
);
expect(closeIcons).not.toBeNull();
expect(closeIcons.length).toEqual(0);
});
});
});
8 changes: 6 additions & 2 deletions force-app/main/default/lwc/warningBanner/warningBanner.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
size="x-small"
></lightning-icon>
</span>
<h2>{message}</h2>
<template if:true={message}>
<h2>{message}</h2>
</template>
</div>
</template>
<template if:true={info}>
Expand All @@ -36,7 +38,9 @@ <h2>{message}</h2>
size="x-small"
></lightning-icon>
</span>
<h2>{message}</h2>
<template if:true={message}>
<h2>{message}</h2>
</template>
<lightning-icon
icon-name="utility:close"
alternative-text={labels.close.icon.alternativeText}
Expand Down
16 changes: 7 additions & 9 deletions force-app/main/default/lwc/warningBanner/warningBanner.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { LightningElement, api, track } from "lwc";

// TODO: Localize with Custom Labels.
const WARNING_ASSISTIVE_TEXT = "warning";
const WARNING_TITLE = "Description of icon when needed";
const WARNING_ICON_ALTERNATIVE_TEXT = "Warning!";

const INFO_ASSISTIVE_TEXT = "info";
const INFO_TITLE = "Description of icon when needed";
const INFO_ICON_ALTERNATIVE_TEXT = "Information";

const CLOSE_ICON_ALTERNATIVE_TEXT = "close";
export const WARNING_ASSISTIVE_TEXT = "warning";
export const WARNING_TITLE = "Description of icon when needed";
export const WARNING_ICON_ALTERNATIVE_TEXT = "Warning!";
export const INFO_ASSISTIVE_TEXT = "info";
export const INFO_TITLE = "Description of icon when needed";
export const INFO_ICON_ALTERNATIVE_TEXT = "Information";
export const CLOSE_ICON_ALTERNATIVE_TEXT = "close";

export default class WarningBanner extends LightningElement {
labels = {
Expand Down
27 changes: 26 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,33 @@
const { jestConfig } = require("@salesforce/sfdx-lwc-jest/config");

const setupFilesAfterEnv = jestConfig.setupFilesAfterEnv || [];
setupFilesAfterEnv.push("<rootDir>/jest.setup.js");

// Repo needs to have have 100% LWC Jest test code coverage across all measures.
const coverageThreshold = jestConfig.coverageThreshold || {};
/*
coverageThreshold.global = {
branches: 100,
functions: 100,
lines: 100,
statements: 0,
};
*/
// 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.
coverageThreshold[
"./force-app/main/default/lwc/!(gauExpenditureRow|lookup|manageExpenditures|manageExpendituresTableHeader)/**"
] = {
branches: 100,
functions: 100,
lines: 100,
statements: 0,
};

module.exports = {
...jestConfig,
moduleNameMapper: {},
testPathIgnorePatterns: ["force-app/main/default/lwc/__(tests|mocks)__/"],
reporters: ["default"],
setupFilesAfterEnv: ["./jest.setup.js"],
setupFilesAfterEnv: setupFilesAfterEnv,
coverageThreshold: coverageThreshold,
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "OutboundFundsNPSP",
"name": "outboundfundsmodulenpsp",
"license": "UNLICENSED",
"author": "Salesforce.org Impact Engineering",
"devDependencies": {
Expand Down

0 comments on commit 88592b9

Please sign in to comment.