UNPKG

@carbon/ibm-products-web-components

Version:

Carbon for IBM Products Web Components

341 lines (338 loc) 16.4 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, vi } from 'vitest'; import { fixture, html, oneEvent, elementUpdated } from '@open-wc/testing'; import './notification-panel.js'; import './notification.js'; import './notification-footer.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 defaultProps = { class: 'custom-class', titleText: 'Notifications', dismissAllLabel: 'Dismiss all', doNotDisturbLabel: 'Do not disturb', todayText: 'Today', previousText: 'previous', open: true, dateTimeLocale: 'en-US', }; const template = (props = defaultProps) => { const timeStamp = new Date(new Date().getTime() - 30 * 1000); return html ` <c4p-notification-panel ?open=${props.open} title-text=${props.titleText} dismiss-all-label=${props.dismissAllLabel} donot-disturb-label=${props.doNotDisturbLabel} today-text=${props.todayText} previous-text=${props.previousText} date-time-locale="${props.dateTimeLocale}" > <c4p-notification slot="today" @c4p-notification-dismiss=${() => { console.log('dismiss notification'); }} type="error" ?unread="true" .timestamp=${timeStamp} > <h4 class="c4p--notifications-panel__notification" slot="title"> LogDNA cannot be reached. </h4> <div slot="description">Unable to communicate with LogDNA.</div> </c4p-notification> <c4p-notification slot="previous" @c4p-notification-dismiss=${() => { console.log('dismiss notification'); }} type="error" ?unread="true" .timestamp=${timeStamp} > <h4 class="c4p--notifications-panel__notification" slot="title"> LogDNA cannot be reached. </h4> <div slot="description">Unable to communicate with LogDNA.</div> </c4p-notification> <c4p-notification slot="previous" @c4p-notification-dismiss=${() => { console.log('dismiss notification'); }} type="error" ?unread="true" .timestamp=${timeStamp} > <h4 class="c4p--notifications-panel__notification" slot="title"> LogDNA cannot be reached. </h4> <div slot="description">Unable to communicate with LogDNA.</div> </c4p-notification> <c4p-notification-footer slot="footer" view-all-label="View all (10)" @c4p-notification-view-all=${() => { console.log('view all clicked'); }} @c4p-notification-settings=${() => { console.log('Settings clicked'); }} ></c4p-notification-footer> </c4p-notification-panel> `; }; describe('c4p-notification-panel', () => { it('should render the notification panel', async () => { const panel = (await fixture(template())); expect(panel === null || panel === void 0 ? void 0 : panel.open).toBeTruthy(); expect(panel).toBeDefined(); }); it('should have focus on Dismiss all button after render', async () => { var _a, _b; const panel = (await fixture(template())); await new Promise((resolve) => requestAnimationFrame(resolve)); const dismissButton = (_a = panel.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.c4p--notifications-panel__dismiss-button'); expect((_b = panel.shadowRoot) === null || _b === void 0 ? void 0 : _b.activeElement).toBe(dismissButton); }); it('should have titleText rendered inside the panel header', async () => { var _a, _b; const panel = (await fixture(template())); const panelHeading = (_a = panel.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.c4p--notifications-panel__header'); expect((_b = panelHeading === null || panelHeading === void 0 ? void 0 : panelHeading.textContent) === null || _b === void 0 ? void 0 : _b.trim()).toBe(panel.titleText); }); it('should have "dismissAllLabel" prop passed be the label for the dismiss all button', async () => { var _a, _b; const panel = (await fixture(template())); const dismissButton = (_a = panel.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.c4p--notifications-panel__dismiss-button'); expect((_b = dismissButton === null || dismissButton === void 0 ? void 0 : dismissButton.textContent) === null || _b === void 0 ? void 0 : _b.trim()).toBe(panel.dismissAllLabel); }); it('should have "doNotDisturbLabel" prop passed be the label for the Do not disturb toggle', async () => { var _a, _b, _c; const panel = (await fixture(template())); const doNotDisturbToggle = (_a = panel.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.c4p--notifications-panel__do-not-disturb-toggle'); const toggleText = (_b = doNotDisturbToggle === null || doNotDisturbToggle === void 0 ? void 0 : doNotDisturbToggle.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('.cds--toggle__text'); expect((_c = toggleText === null || toggleText === void 0 ? void 0 : toggleText.textContent) === null || _c === void 0 ? void 0 : _c.trim()).toBe(panel.doNotDisturbLabel); }); it('should have "todayText" prop passed be title for Today Section', async () => { var _a, _b; const panel = (await fixture(template())); const todayTitle = (_a = panel.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.c4p--notifications-panel__time-section-label--today'); expect((_b = todayTitle === null || todayTitle === void 0 ? void 0 : todayTitle.textContent) === null || _b === void 0 ? void 0 : _b.trim()).toBe(panel.todayText); }); it('should have "previousText" prop passed be title for Previous Section', async () => { var _a, _b; const panel = (await fixture(template())); const previousTitle = (_a = panel.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.c4p--notifications-panel__time-section-label--previous'); expect((_b = previousTitle === null || previousTitle === void 0 ? void 0 : previousTitle.textContent) === null || _b === void 0 ? void 0 : _b.trim()).toBe(panel.previousText); }); it('should emit "c4p-notification-click-outside" when clicking outside the panel', async () => { const element = await fixture(html ` <div> ${template()} <div id="outside"></div> </div> `); const panel = element.querySelector('c4p-notification-panel'); const eventPromise = oneEvent(panel, 'c4p-notification-click-outside'); const outsideDiv = element.querySelector('#outside'); outsideDiv.click(); const event = await eventPromise; expect(event).toBeDefined(); }); it('should emit "c4p-notification-click-outside" when pressing Escape', async () => { const panel = (await fixture(template())); await elementUpdated(panel); const eventPromise = oneEvent(panel, 'c4p-notification-click-outside'); panel.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })); const event = await eventPromise; expect(event).toBeDefined(); }); it('should emit "c4p-notification-dismiss-all" when clicked on dismiss all button', async () => { var _a; const panel = (await fixture(template())); const eventPromise = oneEvent(panel, 'c4p-notification-dismiss-all'); const dismissButton = (_a = panel.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.c4p--notifications-panel__dismiss-button'); dismissButton === null || dismissButton === void 0 ? void 0 : dismissButton.click(); const event = await eventPromise; expect(event).toBeDefined(); }); it('should emit "c4p-notification-donot-disturb-change" when clicked on Do not disturb toggle', async () => { var _a; const panel = (await fixture(template())); const eventPromise = oneEvent(panel, 'c4p-notification-donot-disturb-change'); const doNotDisturbToggle = (_a = panel.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.c4p--notifications-panel__do-not-disturb-toggle'); doNotDisturbToggle === null || doNotDisturbToggle === void 0 ? void 0 : doNotDisturbToggle.dispatchEvent(new CustomEvent('cds-toggle-changed', { bubbles: true, composed: true, detail: {}, })); const event = await eventPromise; expect(event).toBeDefined(); }); it('should render c4p-notification inside the "today" slot', async () => { var _a; const panel = (await fixture(template())); await elementUpdated(panel); const todaySlot = (_a = panel.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('slot[name="today"]'); expect(todaySlot).toBeDefined(); const assignedElements = todaySlot.assignedElements({ flatten: true }); expect(assignedElements[0].tagName.toLowerCase()).toBe('c4p-notification'); }); it('should render c4p-notification inside the "previous" slot', async () => { var _a; const panel = (await fixture(template())); await elementUpdated(panel); const previousSlot = (_a = panel.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('slot[name="previous"]'); expect(previousSlot).toBeDefined(); const assignedElements = previousSlot.assignedElements({ flatten: true, }); const isNotifications = assignedElements.every((el) => el.tagName.toLowerCase() === 'c4p-notification'); expect(isNotifications).toBe(true); expect(assignedElements.length).toBeGreaterThan(0); }); it('should render c4p-notification-footer inside the "footer" slot', async () => { var _a; const panel = (await fixture(template())); const footerSlot = (_a = panel.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('slot[name="footer"]'); expect(footerSlot).toBeDefined(); const assignedElements = footerSlot.assignedElements({ flatten: true, }); expect(assignedElements[0].tagName.toLowerCase()).toBe('c4p-notification-footer'); }); it('should update _providedLocale when dateTimeLocale changes', async () => { const panel = (await fixture(template())); panel.dateTimeLocale = 'fr-FR'; await panel.updateComplete; // @ts-ignore expect(panel._providedLocale).to.equal('fr-FR'); }); it('should correctly update _hasTodayContent and _hasPreviousContent based on slotted elements', async () => { const panel = (await fixture(template())); await panel.updateComplete; // @ts-ignore expect(panel._hasTodayContent).to.be.true; // @ts-ignore expect(panel._hasPreviousContent).to.be.true; const previousItem = document.createElement('div'); previousItem.slot = 'previous'; previousItem.textContent = 'Previous item'; panel.appendChild(previousItem); await panel.updateComplete; // @ts-ignore expect(panel._hasTodayContent).to.be.true; const todayItem = panel.querySelector('[slot="today"]'); todayItem === null || todayItem === void 0 ? void 0 : todayItem.remove(); await panel.updateComplete; // @ts-ignore expect(panel._hasTodayContent).to.be.false; }); it('should disconnect the mutation observer when disconnected', async () => { const disconnectSpy = vi.fn(); const panel = (await fixture(template())); // @ts-ignore panel._mutationObserver = { disconnect: disconnectSpy }; panel.remove(); expect(disconnectSpy).toHaveBeenCalled(); }); it('should focus on trigger button when closed by clicking outside on non-actionable element', async () => { const element = await fixture(html ` <div> <button id="trigger-button">Trigger</button> ${template()} <div id="outside"></div> </div> `); const panel = element.querySelector('c4p-notification-panel'); const triggerButton = element.querySelector('#trigger-button'); const outsideDiv = element.querySelector('#outside'); panel.triggerButtonRef = triggerButton; outsideDiv.click(); await elementUpdated(panel); expect(document.activeElement).toBe(triggerButton); }); it('should not focus on trigger button when closed by clicking outside on actionable element', async () => { const element = await fixture(html ` <div> <button id="trigger-button">Trigger</button> ${template()} <div id="outside"> <button id="actionable-button">Actionable</button> </div> </div> `); const panel = element.querySelector('c4p-notification-panel'); const triggerButton = element.querySelector('#trigger-button'); const actionableButton = element.querySelector('#actionable-button'); panel.triggerButtonRef = triggerButton; actionableButton.click(); await elementUpdated(panel); expect(document.activeElement).not.toBe(triggerButton); }); it('should render notifications empty state', async () => { var _a; const props = defaultProps; const panel = await fixture(html ` <c4p-notification-panel ?open=${props.open} title-text=${props.titleText} dismiss-all-label=${props.dismissAllLabel} donot-disturb-label=${props.doNotDisturbLabel} today-text=${props.todayText} previous-text=${props.previousText} date-time-locale="${props.dateTimeLocale}" ></c4p-notification-panel>`); const slot = (_a = panel.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector(`slot[name="empty-state"]`); expect(slot).toBeTruthy(); }); it('should display the "emptyStateLabel" prop as the label for the empty state message', async () => { var _a; const props = defaultProps; const panel = await fixture(html ` <c4p-notification-panel ?open=${props.open} title-text=${props.titleText} dismiss-all-label=${props.dismissAllLabel} empty-state-label="No notifications yet!" donot-disturb-label=${props.doNotDisturbLabel} today-text=${props.todayText} previous-text=${props.previousText} date-time-locale="${props.dateTimeLocale}" ></c4p-notification-panel>`); const emptyStateEl = (_a = panel.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector(`clabs-empty-state`); expect(emptyStateEl).toBeTruthy(); expect(emptyStateEl === null || emptyStateEl === void 0 ? void 0 : emptyStateEl.getAttribute('subtitle')).to.equal('No notifications yet!'); }); it('should render custom empty state component', async () => { var _a, _b; const props = defaultProps; const panel = await fixture(html ` <c4p-notification-panel ?open=${props.open} title-text=${props.titleText} dismiss-all-label=${props.dismissAllLabel} empty-state-label="No notifications yet!" donot-disturb-label=${props.doNotDisturbLabel} today-text=${props.todayText} previous-text=${props.previousText} date-time-locale="${props.dateTimeLocale}" > <div slot="empty-state"> <span class="empty-state-message">empty state message</span> </div> </c4p-notification-panel>`); const slot = (_a = panel.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector(`slot[name="empty-state"]`); const node = slot.assignedNodes()[0]; expect((_b = node === null || node === void 0 ? void 0 : node.querySelector('.empty-state-message')) === null || _b === void 0 ? void 0 : _b.textContent).toBe('empty state message'); }); }); //# sourceMappingURL=notification-panel.test.js.map