@skyux/modals
Version:
This library was generated with [Nx](https://nx.dev).
642 lines (627 loc) • 22.3 kB
JavaScript
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