@carbon/ibm-products-web-components
Version:
Carbon for IBM Products Web Components
280 lines (276 loc) • 16 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, afterEach } from 'vitest';
import { fixture, html } from '@open-wc/testing';
import './tearsheet.js';
import '@carbon/web-components/es-custom/components/tabs/index.js';
import '@carbon/web-components/es-custom/components/slug/index.js';
import '@carbon/web-components/es-custom/components/dropdown/index.js';
import '@carbon/web-components/es-custom/components/progress-bar/index.js';
import '@carbon/web-components/es-custom/components/button/index.js';
import '@carbon/web-components/es-custom/components/text-input/index.js';
import '@carbon/web-components/es-custom/components/textarea/index.js';
import { getContent, getLabel, getSlug, getActionToolbarItems, getNavigation, influencers, getActionItems } from './utils.js';
/**
* Copyright IBM Corp. 2024, 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 defaultProps = {
headerActions: '',
open: true,
influencerWidth: 'narrow',
influencerPlacement: 'left',
influencer: '',
preventCloseOnClickOutside: false,
selectorInitialFocus: '',
width: 'wide',
slug: '',
description: 'Description used to describe the flow if need be.',
title: 'Title used to designate the overarching flow of the tearsheet.',
headerNavigation: '',
hasCloseIcon: false,
};
const template = (props = defaultProps, children = getContent(1)) => html `
<c4p-tearsheet
selector-initial-focus=${props.selectorInitialFocus}
?open=${props.open}
influencer-placement=${props.influencerPlacement}
influencer-width=${props.influencerWidth}
?prevent-close-on-click-outside=${props.preventCloseOnClickOutside}
width=${props.width}
?has-close-icon=${props.hasCloseIcon}
>
${children}
<!-- slotted header title -->
${props.title ? html `<span slot="title">Three ${props.title}</span>` : ''}
<!-- slotted header description -->
${props.description
? html `<span slot="description">${props.description}</span>`
: ''}
</c4p-tearsheet>
`;
describe('c4p-tearsheet', () => {
it('should render a tearsheet', async () => {
const tearsheet = (await fixture(template(Object.assign({}, defaultProps))));
expect(tearsheet === null || tearsheet === void 0 ? void 0 : tearsheet.open).toBeTruthy();
expect(tearsheet).toBeDefined();
});
it('responds to hasCloseIcon and renders closeIconDescription', async () => {
var _a, _b, _c;
const tearsheet = (await fixture(template(Object.assign(Object.assign({}, defaultProps), { open: true, hasCloseIcon: true }))));
expect(tearsheet === null || tearsheet === void 0 ? void 0 : tearsheet.open).toBeTruthy();
expect(tearsheet === null || tearsheet === void 0 ? void 0 : tearsheet.closeIconDescription).toBe('Close');
const headerEle = (_a = tearsheet.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;
closeButton === null || closeButton === void 0 ? void 0 : closeButton.click();
await new Promise((resolve) => setTimeout(resolve, 50));
expect(tearsheet === null || tearsheet === void 0 ? void 0 : tearsheet.open).to.be.false;
});
it('should render label', async () => {
var _a, _b;
const tearsheet = (await fixture(template(defaultProps, getLabel(1))));
expect(tearsheet === null || tearsheet === void 0 ? void 0 : tearsheet.open).toBeTruthy();
const headerEle = (_a = tearsheet.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('cds-custom-modal-header');
expect(headerEle).to.exist;
// Check if slot[name="label"] is present in 'cds-custom-modal-header'
const labelSlot = headerEle === null || headerEle === void 0 ? void 0 : headerEle.querySelector('slot[name="label"]');
expect(labelSlot).to.exist;
const assignedNodes = (_b = labelSlot === null || labelSlot === void 0 ? void 0 : labelSlot.assignedNodes({ flatten: true })) !== null && _b !== void 0 ? _b : [];
// Extract text content from assigned nodes
const labelText = assignedNodes
.map((node) => { var _a; return ((_a = node.textContent) !== null && _a !== void 0 ? _a : '').trim(); })
.join(' ');
// Assert the label matches expected text
expect(labelText).to.equal('Optional label for context');
});
it('should render description and title', async () => {
var _a;
const tearsheet = (await fixture(template(Object.assign({}, defaultProps))));
expect(tearsheet === null || tearsheet === void 0 ? void 0 : tearsheet.open).toBeTruthy();
const headerEle = (_a = tearsheet.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('cds-custom-modal-header');
expect(headerEle).to.exist;
['description', 'title'].forEach((name, i) => {
var _a;
const slot = headerEle === null || headerEle === void 0 ? void 0 : headerEle.querySelector(`slot[name="${name}"]`);
expect(slot).to.exist;
const text = ((_a = slot === null || slot === void 0 ? void 0 : slot.assignedNodes({ flatten: true })) !== null && _a !== void 0 ? _a : [])
.map((node) => { var _a, _b; return (_b = (_a = node.textContent) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : ''; })
.join(' ');
expect(text).to.equal([
'Description used to describe the flow if need be.',
'Three Title used to designate the overarching flow of the tearsheet.',
][i]);
});
});
it('should render a slug', async () => {
const tearsheet = (await fixture(template(defaultProps, getSlug(1))));
await new Promise((resolve) => setTimeout(resolve, 100));
document.body.offsetHeight;
expect(tearsheet === null || tearsheet === void 0 ? void 0 : tearsheet.open).toBeTruthy();
// Ensure slug is present
expect(tearsheet === null || tearsheet === void 0 ? void 0 : tearsheet._hasSlug).toBeTruthy();
// Locate slug element
const slug = tearsheet.querySelector('cds-custom-slug');
// Expect the slug element is present
expect(slug).toBeDefined();
// Expect the default slug size is sm
expect(slug.size).toBe('sm');
});
it('should render navigation button', async () => {
const tearsheet = (await fixture(template(defaultProps, getActionToolbarItems(1))));
expect(tearsheet === null || tearsheet === void 0 ? void 0 : tearsheet.open).toBeTruthy();
// Get the dropdown with initial value as option 1
const dropdown = Array.prototype.find.call(tearsheet === null || tearsheet === void 0 ? void 0 : tearsheet.querySelectorAll('cds-custom-dropdown'), (el) => el.getAttribute('value') === 'option 1');
expect(dropdown).toBeDefined();
// Check if dropdown contains all the available options
const dropdownItems = dropdown === null || dropdown === void 0 ? void 0 : dropdown.querySelectorAll('cds-custom-dropdown-item');
const expectedOptions = ['option 1', 'option 2', 'option 3', 'option 4'];
expect(dropdownItems === null || dropdownItems === void 0 ? void 0 : dropdownItems.length).toBe(expectedOptions.length);
// Ensure all expected options are present
expectedOptions.forEach((option) => {
const item = Array.prototype.find.call(dropdownItems, (el) => {
var _a;
return el.getAttribute('value') === option &&
((_a = el.textContent) === null || _a === void 0 ? void 0 : _a.trim()) === option;
});
expect(item).toBeDefined();
});
});
it('should close tearsheet when pressing Esc', async () => {
const tearsheet = (await fixture(template(Object.assign(Object.assign({}, defaultProps), { open: true }))));
// Ensure the tearsheet is initially open
expect(tearsheet === null || tearsheet === void 0 ? void 0 : tearsheet.open).toBeTruthy();
// Dispatch the before-close event via Esc
const beforeCloseEvent = new CustomEvent('cds-custom-tearsheet-before-close', {
bubbles: true,
composed: true,
cancelable: true,
detail: { triggeredBy: 'Escape' },
});
// Dispatch the close event
const closeEvent = new CustomEvent('cds-custom-tearsheet-close', {
bubbles: true,
composed: true,
detail: { triggeredBy: 'Escape' },
});
// Fire the before-close event
const beforeCloseAccepted = tearsheet.dispatchEvent(beforeCloseEvent);
if (beforeCloseAccepted) {
tearsheet.dispatchEvent(closeEvent);
tearsheet.open = false;
}
// Wait for the modal to update
await new Promise((resolve) => setTimeout(resolve, 200));
await tearsheet.updateComplete;
// Expect modal to be closed
expect(tearsheet.open).toBeFalsy();
});
it('should not close tearsheet when preventCloseOnClickOutside is true', async () => {
const tearsheet = (await fixture(template(Object.assign(Object.assign({}, defaultProps), { preventCloseOnClickOutside: true }))));
expect(tearsheet === null || tearsheet === void 0 ? void 0 : tearsheet.open).toBeTruthy();
// Dispatch the 'cds-custom-tearsheet-before-close' event
const beforeCloseEvent = new CustomEvent('cds-custom-tearsheet-before-close', {
bubbles: true,
composed: true,
cancelable: true,
detail: { triggeredBy: document.body },
});
// Prevent the 'cds-custom-tearsheet-before-close' event via event listener
tearsheet.addEventListener('cds-custom-tearsheet-before-close', (event) => {
event.preventDefault();
});
const beforeCloseAccepted = tearsheet.dispatchEvent(beforeCloseEvent);
expect(beforeCloseAccepted).toBeFalsy();
// Expect the modal is still open
expect(tearsheet.open).toBeTruthy();
});
it('renders navigation', async () => {
var _a;
const tearsheet = (await fixture(template(defaultProps, getNavigation(1))));
// Ensure the tearsheet is initially open
expect(tearsheet === null || tearsheet === void 0 ? void 0 : tearsheet.open).toBeTruthy();
// Find the slot element
const slotElement = (_a = tearsheet.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('slot[name="header-navigation"]');
expect(slotElement).to.exist;
const assignedElements = slotElement.assignedElements();
const tabsContainer = assignedElements.find((el) => el instanceof HTMLElement &&
(el.classList.contains('tearsheet-stories__tabs') ||
el.getAttribute('classname') === 'tearsheet-stories__tabs'));
expect(tabsContainer).to.exist;
const tabs = tabsContainer === null || tabsContainer === void 0 ? void 0 : tabsContainer.querySelector('cds-custom-tabs');
expect(tabs).to.exist;
expect(tabs === null || tabs === void 0 ? void 0 : tabs.querySelectorAll('cds-custom-tab')).to.have.lengthOf(4);
});
it('responds to influencerPosition', async () => {
var _a;
const tearsheet = (await fixture(template(Object.assign(Object.assign({}, defaultProps), { influencer: influencers[1], influencerPlacement: 'right', influencerWidth: 'wide' }))));
expect(tearsheet === null || tearsheet === void 0 ? void 0 : tearsheet.open).toBeTruthy();
expect(tearsheet === null || tearsheet === void 0 ? void 0 : tearsheet.influencerPlacement).toBe('right');
expect(tearsheet === null || tearsheet === void 0 ? void 0 : tearsheet.influencerWidth).toBe('wide');
const bodyEle = (_a = tearsheet.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('cds-custom-modal-body');
expect(bodyEle).to.exist;
const influencerSlot = bodyEle === null || bodyEle === void 0 ? void 0 : bodyEle.querySelector('slot[name="influencer"]');
expect(influencerSlot).to.exist;
const influencer = bodyEle === null || bodyEle === void 0 ? void 0 : bodyEle.querySelector('.c4p--tearsheet__influencer');
const rightSection = bodyEle === null || bodyEle === void 0 ? void 0 : bodyEle.querySelector('.c4p--tearsheet__right');
expect(influencer).to.exist;
expect(influencer.getAttribute('wide')).to.not.be.null;
expect(rightSection).to.exist;
// Ensure content is before influencer in DOM
expect(rightSection.compareDocumentPosition(influencer) &
Node.DOCUMENT_POSITION_FOLLOWING).to.be.greaterThan(0);
});
it('responds to action items', async () => {
var _a;
const actionItems = getActionItems(2);
const mergedActionItems = actionItems
? html `${actionItems}`
: null;
const tearsheet = (await fixture(template(defaultProps, mergedActionItems)));
expect(tearsheet === null || tearsheet === void 0 ? void 0 : tearsheet.open).toBeTruthy();
const slot = (_a = tearsheet.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('cds-custom-modal-body cds-custom-button-set-base slot[name="actions"]');
const buttons = slot === null || slot === void 0 ? void 0 : slot.assignedElements({ flatten: true }).filter((node) => node.tagName === 'CDS-BUTTON');
expect(buttons === null || buttons === void 0 ? void 0 : buttons.length).to.equal(2);
});
it('Exceeded more than 4 action items', async () => {
var _a, _b;
const actionItems = getActionItems(8);
const mergedActionItems = actionItems
? html `${actionItems}`
: null;
const tearsheet = (await fixture(template(defaultProps, mergedActionItems)));
expect(tearsheet === null || tearsheet === void 0 ? void 0 : tearsheet.open).toBeTruthy();
const slot = (_a = tearsheet.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('cds-custom-modal-body cds-custom-button-set-base slot[name="actions"]');
expect(slot).to.exist;
const allButtons = (_b = slot === null || slot === void 0 ? void 0 : slot.assignedElements({ flatten: true })) !== null && _b !== void 0 ? _b : [];
// Fetching only visible buttons
const visibleButtons = allButtons.filter((node) => node.tagName === 'CDS-BUTTON' &&
!node.hasAttribute('data-actions-limit-4-exceeded'));
expect(visibleButtons.length).to.equal(4);
const fifthButton = allButtons[4];
expect(fifthButton).to.exist;
expect(fifthButton.hasAttribute('data-actions-limit-4-exceeded')).to.be
.true;
});
it('render stacked tearsheet with depth 3', async () => {
const tearsheets = await Promise.all([
fixture(template(Object.assign({}, defaultProps))),
fixture(template(Object.assign({}, defaultProps))),
fixture(template(Object.assign({}, defaultProps))),
]);
expect(tearsheets).to.have.lengthOf(3);
});
afterEach(() => {
// Clears the DOM after each test
document.body.innerHTML = '';
});
});
//# sourceMappingURL=tearsheet.test.js.map