@carbon/ibm-products-web-components
Version:
Carbon for IBM Products Web Components
330 lines (322 loc) • 18.2 kB
JavaScript
/**
* Copyright IBM Corp. 2024
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { fixture, oneEvent, html } from '@open-wc/testing';
import './interstitial-screen.js';
import './interstitial-screen-header.js';
import './interstitial-screen-body.js';
import './interstitial-screen-body-item.js';
import CDSInterstitialScreenFooter from './interstitial-screen-footer.js';
import { interstitialDetailsSignal } from './interstitial-screen-context.js';
/**
* Copyright IBM Corp. 2025
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
const templateSingleStep = (args = {}) => {
return html `
<c4p-interstitial-screen ?fullscreen=${args.fullscreen} ?open=${args.open}>
<c4p-interstitial-screen-header
header-title="Use case-specific title"
header-subtitle="Use case-specific sub title"
closeIconDescription="Close"
></c4p-interstitial-screen-header>
<c4p-interstitial-screen-body>
<c4p-interstitial-screen-body-item id="${1}">
<div
role="complementary"
aria-label="Use case-specific heading"
class="c4p--interstitial-screen-view"
>
<section class="bodyText">body text</section>
</div></c4p-interstitial-screen-body-item
>
</c4p-interstitial-screen-body>
<c4p-interstitial-screen-footer></c4p-interstitial-screen-footer>
</c4p-interstitial-screen>
`;
};
const templateMultiStep = (args = {}) => {
return html `
<c4p-interstitial-screen ?fullscreen=${args.fullscreen} ?open="true">
<c4p-interstitial-screen-header
header-title="Use case-specific title"
header-subtitle="Use case-specific sub title
"
></c4p-interstitial-screen-header>
<c4p-interstitial-screen-body>
<c4p-interstitial-screen-body-item id="${1}" stepTitle="step 1">
<div
role="complementary"
aria-label="Use case-specific heading"
class="c4p--interstitial-screen-view"
>
<section>
<h1>Use case-specific heading 1</h1>
</section>
</div></c4p-interstitial-screen-body-item
>
<c4p-interstitial-screen-body-item id="${2}" stepTitle="step 2">
<div
role="complementary"
aria-label="Use case-specific heading"
class="c4p--interstitial-screen-view"
>
<section>
<h1>Use case-specific heading 2</h1>
</section>
</div></c4p-interstitial-screen-body-item
>
<c4p-interstitial-screen-body-item id="${3}" stepTitle="step 3">
<div
role="complementary"
aria-label="Use case-specific heading"
class="c4p--interstitial-screen-view"
>
<section>
<h1>Use case-specific heading 3</h1>
</section>
</div></c4p-interstitial-screen-body-item
>
</c4p-interstitial-screen-body>
<c4p-interstitial-screen-footer></c4p-interstitial-screen-footer>
</c4p-interstitial-screen>
`;
};
const prefix = 'c4p';
describe('c4p-interstitial-screen', function () {
it('should render single step modal variant', async () => {
var _a, _b, _c, _d, _e, _f, _g, _h;
const el = await fixture(templateSingleStep({ fullscreen: false, open: true }));
// Header
const header = el === null || el === void 0 ? void 0 : el.querySelector(`${prefix}-interstitial-screen-header`);
const headerShadow = header.shadowRoot;
const headerTitle = (_b = (_a = headerShadow.querySelector('h1')) === null || _a === void 0 ? void 0 : _a.textContent) === null || _b === void 0 ? void 0 : _b.trim();
const headerSubTitle = (_d = (_c = headerShadow
.querySelector('h2')) === null || _c === void 0 ? void 0 : _c.textContent) === null || _d === void 0 ? void 0 : _d.trim();
expect(headerTitle).to.equal('Use case-specific title');
expect(headerSubTitle).to.equal('Use case-specific sub title');
// Body
const body = el === null || el === void 0 ? void 0 : el.querySelector(`${prefix}-interstitial-screen-body`);
const bodyItem = body === null || body === void 0 ? void 0 : body.querySelector(`${prefix}-interstitial-screen-body-item`);
const contentText = (_f = (_e = bodyItem === null || bodyItem === void 0 ? void 0 : bodyItem.querySelector('.bodyText')) === null || _e === void 0 ? void 0 : _e.textContent) === null || _f === void 0 ? void 0 : _f.trim();
expect(contentText).to.equal('body text');
// Footer
const footer = el === null || el === void 0 ? void 0 : el.querySelector(`${prefix}-interstitial-screen-footer`);
const footerShadow = footer.shadowRoot;
const startButtonText = (_h = (_g = footerShadow
.querySelector('cds-custom-button')) === null || _g === void 0 ? void 0 : _g.textContent) === null || _h === void 0 ? void 0 : _h.trim();
expect(startButtonText).to.equal('Get Started');
});
it('responds to hasCloseIcon and renders closeIconDescription', async () => {
var _a, _b, _c;
const el = (await fixture(templateSingleStep({ open: true, fullscreen: false })));
expect(el === null || el === void 0 ? void 0 : el.open).toBeTruthy();
const header = el === null || el === void 0 ? void 0 : el.querySelector(`${prefix}-interstitial-screen-header`);
expect(header === null || header === void 0 ? void 0 : header.closeIconDescription).toBe('Close');
const headerEle = (_a = header.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('cds-custom-modal-header');
expect(headerEle).to.exist;
const closeButton = (_c = (_b = headerEle === null || headerEle === void 0 ? void 0 : headerEle.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('slot')) === null || _c === void 0 ? void 0 : _c.assignedNodes({ flatten: true }).find((node) => node.nodeName.toLowerCase() === 'cds-custom-modal-close-button');
expect(closeButton).to.exist;
// Capture both events
const beforeClosePromise = oneEvent(el, 'c4p-interstitial-beingclosed');
const closePromise = oneEvent(el, 'c4p-interstitial-closed');
closeButton === null || closeButton === void 0 ? void 0 : closeButton.click();
await el.updateComplete;
// Wait for the first event
const beforeCloseEvent = await beforeClosePromise;
expect(beforeCloseEvent.type).to.equal('c4p-interstitial-beingclosed');
expect(beforeCloseEvent.defaultPrevented).to.be.false;
// Wait for the second event
const closeEvent = await closePromise;
expect(closeEvent.type).to.equal('c4p-interstitial-closed');
expect(el === null || el === void 0 ? void 0 : el.open).to.be.false;
});
it('should render multi step modal variant', async () => {
var _a, _b, _c, _d, _e, _f, _g, _h;
const el = await fixture(templateMultiStep({ fullscreen: false}));
//verify c4p-interstitial-opened is dispatched
const eventPromise = oneEvent(el, 'c4p-interstitial-opened');
el.open = true;
await el.updateComplete;
const event = await eventPromise;
expect(event).to.exist;
expect(event.type).to.equal('c4p-interstitial-opened');
// Header
const header = el === null || el === void 0 ? void 0 : el.querySelector(`${prefix}-interstitial-screen-header`);
const headerShadow = header.shadowRoot;
const headerTitle = (_b = (_a = headerShadow.querySelector('h1')) === null || _a === void 0 ? void 0 : _a.textContent) === null || _b === void 0 ? void 0 : _b.trim();
const headerSubTitle = (_d = (_c = headerShadow
.querySelector('h2')) === null || _c === void 0 ? void 0 : _c.textContent) === null || _d === void 0 ? void 0 : _d.trim();
expect(headerTitle).to.equal('Use case-specific title');
expect(headerSubTitle).to.equal('Use case-specific sub title');
//progress indicator
const progressIndicatorStepsLength = (_e = headerShadow === null || headerShadow === void 0 ? void 0 : headerShadow.querySelector(`cds-custom-progress-indicator`)) === null || _e === void 0 ? void 0 : _e.children.length;
expect(progressIndicatorStepsLength).to.be.equal(3);
//check step 1 is active
const step1 = (_f = headerShadow === null || headerShadow === void 0 ? void 0 : headerShadow.querySelector(`cds-custom-progress-indicator`)) === null || _f === void 0 ? void 0 : _f.children[0];
expect(step1 === null || step1 === void 0 ? void 0 : step1.getAttribute('label')).to.equal('step 1');
expect(step1 === null || step1 === void 0 ? void 0 : step1.getAttribute('state')).to.equal('current');
// check content is of step 1
const body = el === null || el === void 0 ? void 0 : el.querySelector(`${prefix}-interstitial-screen-body`);
const bodyItem = body === null || body === void 0 ? void 0 : body.querySelector(`${prefix}-interstitial-screen-body-item`);
const contentText = (_h = (_g = bodyItem === null || bodyItem === void 0 ? void 0 : bodyItem.querySelector('h1')) === null || _g === void 0 ? void 0 : _g.textContent) === null || _h === void 0 ? void 0 : _h.trim();
expect(contentText).to.equal('Use case-specific heading 1');
});
it('step navigation using footer action buttons', async () => {
var _a, _b, _c, _d, _e, _f, _g;
const el = await fixture(templateMultiStep({ fullscreen: false}));
// Header
const header = el.querySelector(`${prefix}-interstitial-screen-header`);
const headerShadow = header.shadowRoot;
// Step 1 active
let stepIndicator = headerShadow.querySelector(`cds-custom-progress-indicator`);
const step1 = stepIndicator === null || stepIndicator === void 0 ? void 0 : stepIndicator.children[0];
expect(step1 === null || step1 === void 0 ? void 0 : step1.getAttribute('label')).to.equal('step 1');
expect(step1 === null || step1 === void 0 ? void 0 : step1.getAttribute('state')).to.equal('current');
// Step 1 content check
const body = el.querySelector(`${prefix}-interstitial-screen-body`);
const bodyItem = body.querySelector(`${prefix}-interstitial-screen-body-item`);
expect((_b = (_a = bodyItem === null || bodyItem === void 0 ? void 0 : bodyItem.querySelector('h1')) === null || _a === void 0 ? void 0 : _a.textContent) === null || _b === void 0 ? void 0 : _b.trim()).to.equal('Use case-specific heading 1');
// Footer
const footer = el.querySelector(`${prefix}-interstitial-screen-footer`);
const footerShadow = footer.shadowRoot;
const nextButton = footerShadow.querySelector('.c4p--interstitial-screen--next-btn');
// Go to Step 2
nextButton.click();
(_c = body
.querySelectorAll(`${prefix}-interstitial-screen-body-item`)[1]) === null || _c === void 0 ? void 0 : _c.dispatchEvent(new Event('transitionend', { bubbles: true }));
await el.updateComplete;
await Promise.all([footer.updateComplete, header.updateComplete]);
stepIndicator = headerShadow.querySelector(`cds-custom-progress-indicator`);
const step2 = stepIndicator === null || stepIndicator === void 0 ? void 0 : stepIndicator.children[1];
expect(step2 === null || step2 === void 0 ? void 0 : step2.getAttribute('label')).to.equal('step 2');
expect(step2 === null || step2 === void 0 ? void 0 : step2.getAttribute('state')).to.equal('current');
// Go to Step 3
nextButton.click();
(_d = body
.querySelectorAll(`${prefix}-interstitial-screen-body-item`)[2]) === null || _d === void 0 ? void 0 : _d.dispatchEvent(new Event('transitionend', { bubbles: true }));
await el.updateComplete;
await Promise.all([footer.updateComplete, header.updateComplete]);
stepIndicator = headerShadow.querySelector(`cds-custom-progress-indicator`);
const step3 = stepIndicator === null || stepIndicator === void 0 ? void 0 : stepIndicator.children[2];
expect(step3 === null || step3 === void 0 ? void 0 : step3.getAttribute('label')).to.equal('step 3');
expect(step3 === null || step3 === void 0 ? void 0 : step3.getAttribute('state')).to.equal('current');
// Back to Step 2
const backButton = footerShadow.querySelector('.c4p--interstitial-screen--prev-btn');
expect(backButton).to.exist;
backButton.click();
(_e = body
.querySelectorAll(`${prefix}-interstitial-screen-body-item`)[1]) === null || _e === void 0 ? void 0 : _e.dispatchEvent(new Event('transitionend', { bubbles: true }));
await el.updateComplete;
await Promise.all([footer.updateComplete, header.updateComplete]);
stepIndicator = headerShadow.querySelector(`cds-custom-progress-indicator`);
expect((_f = stepIndicator === null || stepIndicator === void 0 ? void 0 : stepIndicator.children[1]) === null || _f === void 0 ? void 0 : _f.getAttribute('state')).to.equal('current');
// Forward to Step 3 again
nextButton.click();
(_g = body
.querySelectorAll(`${prefix}-interstitial-screen-body-item`)[2]) === null || _g === void 0 ? void 0 : _g.dispatchEvent(new Event('transitionend', { bubbles: true }));
await el.updateComplete;
await Promise.all([footer.updateComplete, header.updateComplete]);
const startButton = footerShadow.querySelector('.c4p--interstitial-screen--start-btn');
expect(startButton).to.exist;
// Close component
startButton.click();
await el.updateComplete;
expect(el.open).to.be.false;
});
it('should render single step fullscreen variant', async () => {
const el = await fixture(templateSingleStep({ fullscreen: true, open: true }));
expect(el === null || el === void 0 ? void 0 : el.isFullScreen).to.be.true;
const style = getComputedStyle(el);
expect(style.position).to.equal('fixed');
// Header
const header = el === null || el === void 0 ? void 0 : el.querySelector(`${prefix}-interstitial-screen-header`);
expect(header).to.exist;
// Body
const body = el === null || el === void 0 ? void 0 : el.querySelector(`${prefix}-interstitial-screen-body`);
expect(body).to.exist;
const footer = el === null || el === void 0 ? void 0 : el.querySelector(`${prefix}-interstitial-screen-footer`);
expect(footer).to.exist;
});
describe('handleAction', () => {
let component;
const mockCarouselAPI = {
next: vi.fn(),
prev: vi.fn(),
};
const mockDetails = {
currentStep: 1,
stepDetails: [
{ stepTitle: 'Step 1', id: 1 },
{ stepTitle: 'Step 2', id: 2 },
{ stepTitle: 'Step 3', id: 3 },
],
carouselAPI: mockCarouselAPI,
isFullScreen: false,
disableActions: {
next: false,
back: false,
cancel: false,
},
};
beforeEach(() => {
component = new CDSInterstitialScreenFooter();
component.asyncAction = true;
interstitialDetailsSignal.get = vi.fn(() => mockDetails);
mockCarouselAPI.next.mockClear();
mockCarouselAPI.prev.mockClear();
});
it('should dispatch "c4p-on-action" and proceed when allowed', async () => {
const dispatchSpy = vi
.spyOn(component, 'dispatchEvent')
.mockImplementation((event) => {
expect(event.type).toBe('c4p-on-action');
event.detail.proceed(true);
return true;
});
await component['handleAction']('next');
expect(dispatchSpy).toHaveBeenCalled();
expect(mockCarouselAPI.next).toHaveBeenCalled();
expect(component.loadingAction).toBe('');
});
it('should cancel if proceed(false) is called', async () => {
vi.spyOn(component, 'dispatchEvent').mockImplementation((event) => {
expect(event.type).toBe('c4p-on-action');
event.detail.proceed(false);
return true;
});
await component['handleAction']('next');
expect(mockCarouselAPI.next).not.toHaveBeenCalled();
expect(component.loadingAction).toBe('');
});
it('should cancel if event is canceled (dispatchEvent returns false)', async () => {
vi.spyOn(component, 'dispatchEvent').mockReturnValue(false);
await component['handleAction']('next');
expect(mockCarouselAPI.next).not.toHaveBeenCalled();
});
it('should call prev if actionType is "back"', async () => {
vi.spyOn(component, 'dispatchEvent').mockImplementation((event) => {
event.detail.proceed(true);
return true;
});
await component['handleAction']('back');
expect(mockCarouselAPI.prev).toHaveBeenCalled();
});
it('should call _handleUserInitiatedClose if actionType is not "next" or "back"', async () => {
component._handleUserInitiatedClose = vi.fn();
vi.spyOn(component, 'dispatchEvent').mockImplementation((event) => {
event.detail.proceed(true);
return true;
});
await component['handleAction']('skip');
expect(component._handleUserInitiatedClose).toHaveBeenCalledWith('skip');
});
});
});
//# sourceMappingURL=interstitial-screen.test.js.map