UNPKG

@skyux/modals

Version:

This library was generated with [Nx](https://nx.dev).

642 lines (627 loc) 22.3 kB
import { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing'; import { SkyComponentHarness } from '@skyux/core/testing'; import { SkyConfirmType, SkyConfirmInstance, SkyConfirmService, SkyModalInstance, SkyModalService } from '@skyux/modals'; import * as i0 from '@angular/core'; import { NgModule, Injectable } from '@angular/core'; import { SkyHelpInlineHarness } from '@skyux/help-inline/testing'; /** * Allows interaction with a SKY UX modal component. * @deprecated Use `SkyModalHarness` instead. * @internal */ class SkyModalFixture { #modalElement; #fixture; constructor(fixture, skyTestId) { this.#fixture = fixture; const modalElement = document.querySelector('sky-modal[data-sky-id="' + skyTestId + '"]'); if (!modalElement) { throw new Error(`No element was found with a \`data-sky-id\` value of "${skyTestId}".`); } this.#modalElement = modalElement; } /** * The modal component's ARIA describedby attribute. */ get ariaDescribedBy() { const modalDialogElement = this.#getModalDialogElement(); /* Non-null assertion as our component has a default for if the user does not provide this attribute or if they provide "undefined" */ const describedByAttribute = // eslint-disable-next-line @typescript-eslint/no-non-null-assertion modalDialogElement.getAttribute('aria-describedby'); return describedByAttribute; } /** * The modal component's ARIA labelledby attribute. */ get ariaLabelledBy() { const modalDialogElement = this.#getModalDialogElement(); /* Non-null assertion as our component has a default for if the user does not provide this attribute or if they provide "undefined" */ const labelledByAttribute = // eslint-disable-next-line @typescript-eslint/no-non-null-assertion modalDialogElement.getAttribute('aria-labelledby'); return labelledByAttribute; } /** * The modal component's role attribute. */ get ariaRole() { const modalDialogElement = this.#getModalDialogElement(); /* Non-null assertion as our component has a default for if the user does not provide this attribute or if they provide "undefined" */ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const roleAttribute = modalDialogElement.getAttribute('role'); return roleAttribute; } /** * Whether or not the modal is a full page modal. */ get fullPage() { const modalDivElement = this.getModalDiv(); return modalDivElement.classList.contains('sky-modal-full-page'); } /** * The size of the modal. */ get size() { const modalDivElement = this.getModalDiv(); const possibleSizes = ['small', 'medium', 'large']; for (const size of possibleSizes) { if (modalDivElement.classList.contains('sky-modal-' + size)) { return size; } } return; } /** * Whether or not the modal is set up for tiled content. */ get tiledBody() { const modalDivElement = this.getModalDiv(); return modalDivElement.classList.contains('sky-modal-tiled'); } /** * Clicks the modal header's "close" button. */ clickHeaderCloseButton() { this.#checkModalElement(); const closeButton = this.#modalElement.querySelector('.sky-modal .sky-modal-btn-close'); if (closeButton && window.getComputedStyle(closeButton).display !== 'none') { closeButton.click(); this.#fixture.detectChanges(); } else { throw new Error(`No header close button exists.`); } } /** * Clicks the modal header's "help" button. */ clickHelpButton() { this.#checkModalElement(); const helpButton = this.#modalElement.querySelector('.sky-modal .sky-modal-header-buttons button[name="help-button"]'); if (helpButton && window.getComputedStyle(helpButton).display !== 'none') { helpButton.click(); this.#fixture.detectChanges(); } else { throw new Error(`No help button exists.`); } } /** * Returns the main modal element. */ getModalDiv() { this.#checkModalElement(); return this.#modalElement.querySelector('.sky-modal'); } /** * Returns the modal's content element. */ getModalContentEl() { this.#checkModalElement(); return this.#modalElement.querySelector('.sky-modal-content'); } /** * Returns the modal's footer element. */ getModalFooterEl() { this.#checkModalElement(); return this.#modalElement.querySelector('.sky-modal-footer'); } /** * Returns the modal's header element. */ getModalHeaderEl() { this.#checkModalElement(); return this.#modalElement.querySelector('.sky-modal-header'); } #checkModalElement() { if (!document.contains(this.#modalElement)) { throw new Error('Modal element no longer exists. Was the modal closed?'); } } #getModalDialogElement() { this.#checkModalElement(); // We can always know that the dialog element will exist if the modal is open and exists. return this.#modalElement.querySelector('.sky-modal-dialog'); } } /** * Harness for interacting with a confirm component in tests. */ class SkyConfirmButtonHarness extends ComponentHarness { /** * @internal */ static { this.hostSelector = '.sky-confirm-buttons .sky-btn'; } /** * Gets a `HarnessPredicate` that can be used to search for a * `SkyConfirmButtonHarness` that meets certain criteria. */ static with(filters) { return new HarnessPredicate(SkyConfirmButtonHarness, filters) .addOption('text', filters.text, async (harness, text) => { const buttonText = await harness.getText(); return await HarnessPredicate.stringMatches(buttonText, text); }) .addOption('styleType', filters.styleType, async (harness, styleType) => { const buttonStyleType = await harness.getStyleType(); return await HarnessPredicate.stringMatches(buttonStyleType, styleType); }); } /** * Clicks the confirm button. */ async click() { await (await this.host()).click(); } /** * Gets the button style of the confirm button. */ async getStyleType() { const hostEl = await this.host(); if (await hostEl.hasClass('sky-btn-primary')) { return 'primary'; } else if (await hostEl.hasClass('sky-btn-link')) { return 'link'; } else if (await hostEl.hasClass('sky-btn-danger')) { return 'danger'; } return 'default'; } /** * Gets the text content of the confirm button. */ async getText() { return await (await this.host()).text(); } } /** * Harness for interacting with a confirm component in tests. */ class SkyConfirmHarness extends SkyComponentHarness { /** * @internal */ static { this.hostSelector = 'sky-confirm'; } #getBodyEl = this.locatorForOptional('.sky-confirm-body'); #getButtons = this.locatorForAll(SkyConfirmButtonHarness); #getConfirmEl = this.locatorFor('.sky-confirm'); #getMessageEl = this.locatorFor('.sky-confirm-message'); /** * Clicks a confirm button. */ async clickCustomButton(filters) { const buttons = await this.getCustomButtons(filters); if (buttons.length > 1) { if (filters.text instanceof RegExp) { filters.text = filters.text.toString(); } throw new Error(`More than one button matches the filter(s): ${JSON.stringify(filters)}.`); } await buttons[0].click(); } /** * Clicks a confirm button. */ async clickOkButton() { const type = await this.getType(); if (type === SkyConfirmType.Custom) { throw new Error('Cannot click OK button on a confirm of type custom.'); } const buttons = await this.#getButtons(); await buttons[0].click(); } /** * Gets the body of the confirm component. */ async getBodyText() { return await (await this.#getBodyEl())?.text(); } /** * Gets a specific confirm custom button based on the filter criteria. * @param filter The filter criteria. */ async getCustomButton(filter) { const confirmType = await this.getType(); if (confirmType !== SkyConfirmType.Custom) { throw new Error('Cannot get a custom button for non-custom confirm modals.'); } return await this.locatorFor(SkyConfirmButtonHarness.with(filter))(); } /** * Gets an array of confirm custom buttons based on the filter criteria. * If no filter is provided, returns all confirm custom buttons. * @param filters The optional filter criteria. */ async getCustomButtons(filters) { const confirmType = await this.getType(); if (confirmType !== SkyConfirmType.Custom) { throw new Error('Cannot get custom buttons for non-custom confirm modals.'); } return await this.locatorForAll(SkyConfirmButtonHarness.with(filters || {}))(); } /** * Gets the message of the confirm component. */ async getMessageText() { return await (await this.#getMessageEl()).text(); } /** * Gets the type of the confirm component. */ async getType() { const confirmEl = await this.#getConfirmEl(); if (await confirmEl.hasClass('sky-confirm-type-ok')) { return SkyConfirmType.OK; } return SkyConfirmType.Custom; } /** * Whether the whitespace is preserved on the confirm component. */ async isWhiteSpacePreserved() { return await (await this.#getMessageEl()).hasClass('sky-confirm-preserve-white-space'); } } /** * A controller to be injected into tests, which mocks the confirm service * and handles interactions with confirm dialogs. */ class SkyConfirmTestingController { } function assertConfirmOpen(value) { if (value === undefined) { throw new Error('A confirm dialog is expected to be open but is closed.'); } return; } function assertConfirmClosed(value) { if (value !== undefined) { throw new Error('A confirm dialog is expected to be closed but is open.'); } return; } function isButtonConfigArray(val) { return (Array.isArray(val) && (val.length === 0 || val[0].action !== undefined)); } function buttonConfigMatches(actual, expected) { return (expected.action === actual.action && expected.text === actual.text && expected.styleType === actual.styleType); } /** * @internal */ class SkyConfirmTestingService extends SkyConfirmTestingController { #testSubject; cancel() { this.close({ action: 'cancel' }); } ok() { this.close({ action: 'ok' }); } close(args) { assertConfirmOpen(this.#testSubject); const isActionPermitted = this.#testSubject?.buttons.some((b) => b.action === args.action); if (isActionPermitted) { this.#testSubject.instance.close(args); this.#testSubject = undefined; } else { throw new Error(`The confirm dialog does not have a button configured for the "${args.action}" action.`); } } expectNone() { assertConfirmClosed(this.#testSubject); } expectOpen(expectedConfig) { assertConfirmOpen(this.#testSubject); const actualConfig = this.#testSubject.config; for (const [key, expectedValue] of Object.entries(expectedConfig)) { const k = key; const actualValue = actualConfig[k]; if (isButtonConfigArray(expectedValue) && isButtonConfigArray(actualValue)) { if (expectedValue.length !== actualValue.length) throwDetailedError(); expectedValue.forEach((expectedButton, index) => { if (!buttonConfigMatches(expectedButton, actualValue[index])) { throwDetailedError(); } }); } else if (actualValue !== expectedValue) { throwDetailedError(); } } function throwDetailedError() { throw new Error(`Expected a confirm dialog to be open with a specific configuration. Expected: ${JSON.stringify(expectedConfig, undefined, 2)} Actual: ${JSON.stringify(actualConfig, undefined, 2)} `); } } open(config) { assertConfirmClosed(this.#testSubject); const instance = new SkyConfirmInstance(); const testSubject = { buttons: [], config, instance, }; switch (config.type) { case SkyConfirmType.Custom: config.buttons?.forEach((b) => { testSubject.buttons.push({ action: b.action, text: b.text }); }); break; case SkyConfirmType.OK: default: testSubject.buttons.push({ action: 'ok', text: 'Ok' }); testSubject.buttons.push({ action: 'cancel', text: 'Cancel' }); break; } this.#testSubject = testSubject; return instance; } } /** * @internal */ function provideConfirmTesting() { return [ SkyConfirmTestingService, { provide: SkyConfirmService, useExisting: SkyConfirmTestingService, }, { provide: SkyConfirmTestingController, useExisting: SkyConfirmTestingService, }, ]; } /** * Configures the `SkyConfirmTestingController` as the backend for the `SkyConfirmService`. */ class SkyConfirmTestingModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SkyConfirmTestingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: SkyConfirmTestingModule }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SkyConfirmTestingModule, providers: [provideConfirmTesting()] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SkyConfirmTestingModule, decorators: [{ type: NgModule, args: [{ providers: [provideConfirmTesting()], }] }] }); /** * A controller to be injected into tests, which mocks the modal service * and handles interactions with modal instances. For testing interactions * with the modal component itself, use the `SkyModalHarness`. */ class SkyModalTestingController { } /** * @internal */ class SkyModalTestingService extends SkyModalTestingController { #modals = new Map(); ngOnDestroy() { for (const instance of this.#modals.keys()) { instance.close(); } } closeTopModal(args) { const modal = this.#getTopmostModal(); if (!modal) { throw new Error('Expected to close the topmost modal, but no modals are open.'); } modal.instance.close(args?.data, args?.reason); } expectCount(value) { const count = this.#modals.size; if (count !== value) { throw new Error(`Expected ${value} open ${value === 1 ? 'modal' : 'modals'}, but ${count} ${count === 1 ? 'is' : 'are'} open.`); } } expectNone() { const count = this.#modals.size; if (count > 0) { throw new Error(`Expected no modals to be open, but there ${count === 1 ? 'is' : 'are'} ${count} open.`); } } expectOpen(component) { const modal = this.#getTopmostModal(); if (!modal) { throw new Error('A modal is expected to be open, but no modals are open.'); } if (modal.component !== component) { throw new Error(`Expected the topmost modal to be of type ${component.name}, but it is of type ${modal.component.name}.`); } } open(component, config) { const instance = new SkyModalInstance(); instance.closed.subscribe(() => { this.#modals.delete(instance); }); this.#modals.set(instance, { component, config, instance }); return instance; } #getTopmostModal() { return Array.from(this.#modals.values()).pop(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SkyModalTestingService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SkyModalTestingService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SkyModalTestingService, decorators: [{ type: Injectable }] }); /** * @internal */ function provideModalTesting() { return [ SkyModalTestingService, { provide: SkyModalService, useExisting: SkyModalTestingService, }, { provide: SkyModalTestingController, useExisting: SkyModalTestingService, }, ]; } /** * Configures the `SkyModalTestingController` as the implementation for the `SkyModalService`. */ class SkyModalTestingModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SkyModalTestingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: SkyModalTestingModule }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SkyModalTestingModule, providers: [provideModalTesting()] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SkyModalTestingModule, decorators: [{ type: NgModule, args: [{ providers: [provideModalTesting()], }] }] }); /** * Harness for interacting with a modal component in tests. */ class SkyModalHarness extends SkyComponentHarness { /** * @internal */ static { this.hostSelector = 'sky-modal'; } #getModal = this.locatorFor('.sky-modal'); #getModalDialog = this.locatorFor('.sky-modal-dialog'); #getModalHeading = this.locatorFor('.sky-modal-heading'); /** * Gets a `HarnessPredicate` that can be used to search for a * `SkyModalHarness` that meets certain criteria */ static with(filters) { return SkyModalHarness.getDataSkyIdPredicate(filters); } /** * Clicks the help inline button. */ async clickHelpInline() { await (await this.#getHelpInline()).click(); } /** * Gets the aria-describedBy property of the modal. * @deprecated */ async getAriaDescribedBy() { return await (await this.#getModalDialog()).getAttribute('aria-describedby'); } /** * Gets the aria-labelledBy property of the modal. * @deprecated */ async getAriaLabelledBy() { return await (await this.#getModalDialog()).getAttribute('aria-labelledby'); } /** * Gets the role of the modal. */ async getAriaRole() { return await (await this.#getModalDialog()).getAttribute('role'); } /** * Gets the modal's heading text. */ async getHeadingText() { return await (await this.#getModalHeading()).text(); } /** * Gets the help popover content. */ async getHelpPopoverContent() { return await (await this.#getHelpInline()).getPopoverContent(); } /** * Gets the help popover title. */ async getHelpPopoverTitle() { return await (await this.#getHelpInline()).getPopoverTitle(); } /** * Gets the modal size. */ async getSize() { if (await this.isFullPage()) { throw new Error('Size cannot be determined because size property is overridden when modal is full page'); } const modal = await this.#getModal(); if (await modal.hasClass('sky-modal-small')) { return 'small'; } if (await modal.hasClass('sky-modal-large')) { return 'large'; } return 'medium'; } /** * Gets the wrapper class of the modal. */ async getWrapperClass() { return await (await this.host()).getProperty('className'); } /** * Whether the modal is full page. */ async isFullPage() { const modal = this.#getModal(); return await (await modal).hasClass('sky-modal-full-page'); } /** * Whether the modal has {@link SkyModalIsDirtyDirective.isDirty} set to dirty. */ async isDirty() { const modalHost = await this.host(); const isDirtyAttribute = await modalHost.getAttribute('data-sky-modal-is-dirty'); return isDirtyAttribute === 'true'; } async #getHelpInline() { const harness = await this.locatorForOptional(SkyHelpInlineHarness)(); if (harness) { return harness; } throw Error('No help inline found.'); } } /** * Generated bundle index. Do not edit. */ export { SkyConfirmButtonHarness, SkyConfirmHarness, SkyConfirmTestingController, SkyConfirmTestingModule, SkyModalFixture, SkyModalHarness, SkyModalTestingController, SkyModalTestingModule }; //# sourceMappingURL=skyux-modals-testing.mjs.map