UNPKG

@carbon/ibm-products-web-components

Version:

Carbon for IBM Products Web Components

280 lines (276 loc) 16 kB
/** * 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