lightning-base-components 1.17.7-alpha → 1.18.2-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/metadata/raptor.json +5 -0
- package/package.json +43 -1
- package/src/lightning/accordion/accordion.css +12 -0
- package/src/lightning/accordion/accordion.html +3 -1
- package/src/lightning/accordion/accordion.js +4 -2
- package/src/lightning/accordion/accordion.slds.css +671 -0
- package/src/lightning/accordionSection/accordion-section.slds.css +647 -0
- package/src/lightning/accordionSection/accordionSection.css +14 -0
- package/src/lightning/accordionSection/accordionSection.html +23 -19
- package/src/lightning/accordionSection/accordionSection.js +29 -2
- package/src/lightning/ariaObserver/__docs__/ariaObserver.md +21 -9
- package/src/lightning/ariaObserver/ariaObserver.js +185 -154
- package/src/lightning/ariaObserver/polyfill.js +639 -0
- package/src/lightning/avatar/avatar.css +2 -0
- package/src/lightning/avatar/avatar.html +2 -0
- package/src/lightning/avatar/avatar.js +18 -15
- package/src/lightning/avatar/avatar.slds.css +272 -0
- package/src/lightning/baseCombobox/base-combobox.slds.css +1585 -0
- package/src/lightning/baseCombobox/baseCombobox.css +11 -1
- package/src/lightning/baseCombobox/baseCombobox.html +154 -146
- package/src/lightning/baseCombobox/baseCombobox.js +82 -46
- package/src/lightning/baseCombobox/spinner.slds.css +438 -0
- package/src/lightning/baseComboboxItem/baseComboboxItem.js +4 -2
- package/src/lightning/baseComboboxItem/inline.css +2 -0
- package/src/lightning/breadcrumb/breadcrumb.css +2 -2
- package/src/lightning/breadcrumb/breadcrumb.js +4 -2
- package/src/lightning/breadcrumb/breadcrumb.slds.css +2 -7
- package/src/lightning/breadcrumbs/breadcrumbs.css +2 -2
- package/src/lightning/breadcrumbs/breadcrumbs.js +3 -2
- package/src/lightning/breadcrumbs/breadcrumbs.slds.css +7 -1
- package/src/lightning/button/__examples__/inverse/inverse.css +8 -0
- package/src/lightning/button/__examples__/inverse/inverse.html +3 -2
- package/src/lightning/button/button.css +2 -0
- package/src/lightning/button/button.html +4 -2
- package/src/lightning/button/button.js +21 -0
- package/src/lightning/button/button.slds.css +527 -0
- package/src/lightning/buttonGroup/buttonGroup.css +2 -2
- package/src/lightning/buttonGroup/buttonGroup.js +3 -2
- package/src/lightning/buttonIcon/button-icon.slds.css +215 -453
- package/src/lightning/buttonIcon/buttonIcon.css +2 -2
- package/src/lightning/buttonIcon/buttonIcon.js +4 -0
- package/src/lightning/buttonIconStateful/button-icon-stateful.slds.css +215 -453
- package/src/lightning/buttonIconStateful/buttonIconStateful.css +2 -2
- package/src/lightning/buttonMenu/{dropdown.slds.css → button-menu.slds.css} +853 -217
- package/src/lightning/buttonMenu/buttonMenu.css +2 -2
- package/src/lightning/buttonMenu/buttonMenu.html +2 -2
- package/src/lightning/buttonMenu/buttonMenu.js +10 -14
- package/src/lightning/buttonStateful/button-stateful.slds.css +225 -457
- package/src/lightning/buttonStateful/buttonStateful.css +2 -2
- package/src/lightning/buttonStateful/buttonStateful.js +3 -2
- package/src/lightning/calendar/__examples__/basic/basic.html +7 -0
- package/src/lightning/calendar/__examples__/basic/basic.js +3 -0
- package/src/lightning/calendar/calendar.css +3 -0
- package/src/lightning/calendar/calendar.html +12 -9
- package/src/lightning/calendar/calendar.js +15 -1
- package/src/lightning/calendar/calendar.slds.css +2048 -0
- package/src/lightning/card/card.css +2 -2
- package/src/lightning/card/card.js +3 -2
- package/src/lightning/card/card.slds.css +141 -88
- package/src/lightning/colorPickerCustom/colorPickerCustom.css +2 -2
- package/src/lightning/colorPickerCustom/colorPickerCustom.js +3 -2
- package/src/lightning/colorPickerPanel/color-picker-panel.slds.css +11 -38
- package/src/lightning/colorPickerPanel/colorPickerPanel.css +3 -2
- package/src/lightning/colorPickerPanel/colorPickerPanel.js +4 -2
- package/src/lightning/colorPickerPanel/popover.slds.css +121 -0
- package/src/lightning/combobox/combobox.css +4 -0
- package/src/lightning/combobox/combobox.html +31 -29
- package/src/lightning/combobox/combobox.js +21 -4
- package/src/lightning/combobox/combobox.slds.css +13 -0
- package/src/lightning/combobox/form-element.slds.css +281 -0
- package/src/lightning/configProvider/defaultConfig.js +2 -1
- package/src/lightning/datepicker/datepicker.css +3 -0
- package/src/lightning/datepicker/datepicker.html +7 -4
- package/src/lightning/datepicker/datepicker.js +73 -19
- package/src/lightning/datepicker/form-element.slds.css +281 -0
- package/src/lightning/datepicker/input-text.slds.css +398 -0
- package/src/lightning/datetimepicker/datetimepicker.css +3 -0
- package/src/lightning/datetimepicker/datetimepicker.html +9 -3
- package/src/lightning/datetimepicker/datetimepicker.js +39 -35
- package/src/lightning/datetimepicker/form-element.slds.css +281 -0
- package/src/lightning/datetimepicker/input-text.slds.css +398 -0
- package/src/lightning/dualListbox/dualListbox.css +2 -2
- package/src/lightning/dualListbox/dualListbox.html +3 -3
- package/src/lightning/dualListbox/dualListbox.js +31 -6
- package/src/lightning/dualListbox/form-element.slds.css +83 -34
- package/src/lightning/dualListbox/keyboard.js +20 -1
- package/src/lightning/dynamicIcon/dynamicIcon.js +3 -2
- package/src/lightning/dynamicIcon/ellie.css +1 -1
- package/src/lightning/dynamicIcon/eq.css +1 -1
- package/src/lightning/dynamicIcon/score.css +1 -1
- package/src/lightning/dynamicIcon/strength.css +1 -1
- package/src/lightning/dynamicIcon/trend.css +1 -1
- package/src/lightning/dynamicIcon/waffle.css +1 -1
- package/src/lightning/formattedRichText/linkify.js +2 -2
- package/src/lightning/helptext/form-element.slds.css +83 -34
- package/src/lightning/helptext/help-text.slds.css +215 -453
- package/src/lightning/helptext/helptext.css +2 -2
- package/src/lightning/helptext/helptext.js +3 -2
- package/src/lightning/i18nCldrOptions/README.md +5 -0
- package/src/lightning/i18nService/README.md +5 -0
- package/src/lightning/icon/icon.css +2 -2
- package/src/lightning/icon/icon.js +16 -2
- package/src/lightning/icon/icon.slds.css +29 -17
- package/src/lightning/icon/iconColors.js +1 -0
- package/src/lightning/input/__examples__/text/text.html +0 -1
- package/src/lightning/input/form-element.slds.css +281 -0
- package/src/lightning/input/input-checkbox.slds.css +3 -12
- package/src/lightning/input/input-text.slds.css +239 -128
- package/src/lightning/input/input.css +2 -1
- package/src/lightning/input/input.html +8 -8
- package/src/lightning/input/input.js +107 -73
- package/src/lightning/internationalizationLibrary/README.md +24 -0
- package/src/lightning/internationalizationLibrary/utils.js +4 -1
- package/src/lightning/layout/__docs__/layout.md +1 -1
- package/src/lightning/layout/__examples__/simple/simple.css +1 -1
- package/src/lightning/layout/layout.css +5 -1
- package/src/lightning/layout/layout.js +4 -2
- package/src/lightning/layoutItem/__examples__/alignmentBump/alignmentBump.css +1 -1
- package/src/lightning/layoutItem/__examples__/sizePerDevice/sizePerDevice.css +0 -1
- package/src/lightning/layoutItem/layoutItem.css +5 -0
- package/src/lightning/layoutItem/layoutItem.js +4 -2
- package/src/lightning/menuDivider/menu-divider.slds.css +15 -0
- package/src/lightning/menuDivider/menuDivider.css +3 -0
- package/src/lightning/menuDivider/menuDivider.html +1 -1
- package/src/lightning/menuDivider/menuDivider.js +4 -2
- package/src/lightning/menuItem/menu-item.slds.css +140 -0
- package/src/lightning/menuItem/menuItem.css +3 -0
- package/src/lightning/menuItem/menuItem.html +43 -41
- package/src/lightning/menuItem/menuItem.js +4 -4
- package/src/lightning/menuSubheader/menu-subheader.slds.css +22 -0
- package/src/lightning/menuSubheader/menuSubheader.css +3 -0
- package/src/lightning/menuSubheader/menuSubheader.html +3 -1
- package/src/lightning/menuSubheader/menuSubheader.js +4 -6
- package/src/lightning/modal/__docs__/modal.md +3 -1
- package/src/lightning/modal/__modalUtils__/modalContainerTestConstants.js +267 -0
- package/src/lightning/modal/__modalUtils__/modalContainerTestMethods.js +1165 -0
- package/src/lightning/modal/__modalUtils__/modalContainerTestMockData.js +131 -0
- package/src/lightning/modal/modal.js +1 -1
- package/src/lightning/pill/avatar.slds.css +272 -0
- package/src/lightning/pill/link.css +3 -0
- package/src/lightning/pill/link.html +1 -1
- package/src/lightning/pill/pill.js +29 -9
- package/src/lightning/pill/pill.slds.css +168 -0
- package/src/lightning/pill/plain.css +3 -0
- package/src/lightning/pill/plain.html +1 -1
- package/src/lightning/pill/plainLink.css +3 -0
- package/src/lightning/pill/plainLink.html +1 -1
- package/src/lightning/pillContainer/barePillContainer.css +3 -0
- package/src/lightning/pillContainer/barePillContainer.html +1 -2
- package/src/lightning/pillContainer/listbox.slds.css +267 -0
- package/src/lightning/pillContainer/pill-container.slds.css +22 -0
- package/src/lightning/pillContainer/pill.slds.css +168 -0
- package/src/lightning/pillContainer/pillContainer.js +7 -3
- package/src/lightning/pillContainer/standardPillContainer.css +4 -0
- package/src/lightning/pillContainer/standardPillContainer.html +2 -2
- package/src/lightning/popup/popover.slds.css +119 -119
- package/src/lightning/popup/popup.css +1 -2
- package/src/lightning/popup/popup.js +3 -2
- package/src/lightning/positionLibrary/elementProxy.js +7 -2
- package/src/lightning/positionLibrary/util.js +8 -0
- package/src/lightning/primitiveBubble/primitiveBubble.css +2 -2
- package/src/lightning/primitiveBubble/primitiveBubble.js +4 -2
- package/src/lightning/primitiveButton/primitiveButton.js +5 -4
- package/src/lightning/primitiveCellFactory/cellWithStandardLayout.html +29 -21
- package/src/lightning/primitiveCellFactory/primitiveCellFactory.js +4 -0
- package/src/lightning/primitiveColorpickerButton/color-picker-button.slds.css +31 -19
- package/src/lightning/primitiveColorpickerButton/primitiveColorpickerButton.css +2 -2
- package/src/lightning/primitiveColorpickerButton/primitiveColorpickerButton.js +5 -3
- package/src/lightning/primitiveIcon/icon.slds.css +209 -0
- package/src/lightning/primitiveIcon/primitiveIcon.css +2 -1
- package/src/lightning/primitiveIcon/primitiveIcon.html +1 -1
- package/src/lightning/primitiveIcon/primitiveIcon.js +18 -11
- package/src/lightning/progressStep/progressStep.js +10 -13
- package/src/lightning/radioGroup/radioGroup.css +2 -1
- package/src/lightning/radioGroup/radioGroup.js +4 -2
- package/src/lightning/select/form-element.slds.css +83 -34
- package/src/lightning/select/select.css +2 -2
- package/src/lightning/select/select.js +4 -2
- package/src/lightning/select/select.slds.css +86 -34
- package/src/lightning/sldsCommon/sldsCommon.css +135 -75
- package/src/lightning/spinner/spinner.css +2 -2
- package/src/lightning/spinner/spinner.js +4 -2
- package/src/lightning/tabBar/tab-bar.slds.css +334 -0
- package/src/lightning/tabBar/tabBar.css +2 -0
- package/src/lightning/tabBar/tabBar.html +4 -3
- package/src/lightning/tabBar/tabBar.js +30 -3
- package/src/lightning/tabset/tabset.html +5 -4
- package/src/lightning/tabset/tabset.js +29 -11
- package/src/lightning/timepicker/form-element.slds.css +281 -0
- package/src/lightning/timepicker/timepicker.css +3 -0
- package/src/lightning/timepicker/timepicker.html +5 -1
- package/src/lightning/timepicker/timepicker.js +18 -15
- package/src/lightning/timepicker/timepicker.slds.css +18 -0
- package/src/lightning/tooltipLibrary/tooltipLibrary.js +21 -19
- package/src/lightning/utilsPrivate/browser.js +5 -3
- package/src/lightning/utilsPrivate/os.js +6 -4
- package/src/lightning/utilsPrivate/ssr.js +4 -0
- package/src/lightning/utilsPrivate/utilsPrivate.js +2 -0
- package/src/lightning/verticalNavigation/verticalNavigation.css +2 -1
- package/src/lightning/verticalNavigation/verticalNavigation.js +3 -2
- package/src/lightning/verticalNavigationSection/verticalNavigationSection.css +2 -1
- package/src/lightning/verticalNavigationSection/verticalNavigationSection.js +3 -2
- package/src/lightning/accordion/__perf__DISABLED/accordion-perf-utils.js +0 -76
- package/src/lightning/accordion/__perf__DISABLED/accordion10Multiple25SectionEach.perf.js +0 -57
- package/src/lightning/accordion/__perf__DISABLED/accordion10Simple25SectionEach.perf.js +0 -37
- package/src/lightning/accordion/__perf__DISABLED/accordionMultiple50Section.perf.js +0 -45
- package/src/lightning/accordion/__perf__DISABLED/accordionSimple50Section.perf.js +0 -35
- package/src/lightning/accordion/__perf__DISABLED/container/container.html +0 -15
- package/src/lightning/accordion/__perf__DISABLED/container/container.js +0 -7
- package/src/lightning/positionLibrary/__component__/positionLibraryBounding.spec.js +0 -319
- package/src/lightning/positionLibrary/__component__/x/bounding/bounding.css +0 -16
- package/src/lightning/positionLibrary/__component__/x/bounding/bounding.html +0 -36
- package/src/lightning/positionLibrary/__component__/x/bounding/bounding.js +0 -122
- /package/src/lightning/{baseCombobox → baseComboboxItem}/listbox.slds.css +0 -0
|
@@ -0,0 +1,1165 @@
|
|
|
1
|
+
/* Contains methods for modal testing
|
|
2
|
+
* All helper methods included expect that Webdriver
|
|
3
|
+
* is in scope: $, $$, browser, element
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
OVERLAY_CONTAINER,
|
|
8
|
+
MODAL_BASE,
|
|
9
|
+
MODAL,
|
|
10
|
+
MODAL_DIV_SLOT,
|
|
11
|
+
MODAL_CONTAINER_DIV,
|
|
12
|
+
MODAL_HEADER,
|
|
13
|
+
MODAL_BODY,
|
|
14
|
+
MODAL_BODY_DIV,
|
|
15
|
+
MODAL_BODY_BACKDROP,
|
|
16
|
+
MODAL_BODY_SLOT,
|
|
17
|
+
MODAL_FOOTER,
|
|
18
|
+
FOCUS_TRAP,
|
|
19
|
+
MODAL_CLOSE_BTN,
|
|
20
|
+
MODAL_CLOSE_BTN_CLASS,
|
|
21
|
+
MODAL_CLOSE_BTN_FULL_CLASS,
|
|
22
|
+
MODAL_CLOSE_BTN_ICON_BORDER_CLASS,
|
|
23
|
+
MODAL_CLOSE_BUTTON_FULL_VARIANT,
|
|
24
|
+
MODAL_CLOSE_BUTTON_NORMAL_VARIANT,
|
|
25
|
+
SCREEN_SIZE_LARGE,
|
|
26
|
+
MODAL_SIZE_FULL,
|
|
27
|
+
MODAL_FULL_SCREEN_SMALL_BREAKPOINT,
|
|
28
|
+
MODAL_DEFAULT_PX_OFFSET_X,
|
|
29
|
+
MODAL_DEFAULT_PX_OFFSET_Y,
|
|
30
|
+
MODAL_ELEM_FULL_PX_OFFSET_X,
|
|
31
|
+
MODAL_ELEM_FULL_PX_OFFSET_Y,
|
|
32
|
+
MAX_HEIGHT,
|
|
33
|
+
MIN_HEIGHT,
|
|
34
|
+
MODAL_BODY_MIN_HEIGHT_PX,
|
|
35
|
+
SPEC_TO_TABS_TO_CLOSE_BTN,
|
|
36
|
+
BROWSER_RESIZE_PAUSE,
|
|
37
|
+
MODAL_RENDER_PAUSE,
|
|
38
|
+
PAUSE_MICRO,
|
|
39
|
+
SELECTORS,
|
|
40
|
+
NAME_TO_SIZE,
|
|
41
|
+
SCREEN_SIZE,
|
|
42
|
+
KEY,
|
|
43
|
+
} from './modalContainerTestConstants.js';
|
|
44
|
+
|
|
45
|
+
let wrapper = null;
|
|
46
|
+
|
|
47
|
+
// eslint-disable-next-line
|
|
48
|
+
async function clickButton(btnSelector) {
|
|
49
|
+
// find the correct button, and click
|
|
50
|
+
// to launch modal
|
|
51
|
+
// eslint-disable-next-line
|
|
52
|
+
wrapper = await kontajner.getWrapper();
|
|
53
|
+
await wrapper.waitForDisplayed();
|
|
54
|
+
const button = await wrapper.shadow$(btnSelector);
|
|
55
|
+
await button.click();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// eslint-disable-next-line
|
|
59
|
+
async function getOverlayContainer() {
|
|
60
|
+
// overlay container isn't present in the DOM
|
|
61
|
+
// until first overlay or modal is created
|
|
62
|
+
// eslint-disable-next-line
|
|
63
|
+
// wrapper = await kontajner.getWrapper();
|
|
64
|
+
// await wrapper.waitForDisplayed();
|
|
65
|
+
// eslint-disable-next-line no-undef
|
|
66
|
+
const htmlElem = await $('html');
|
|
67
|
+
// eslint-disable-next-line no-undef
|
|
68
|
+
const bodyElem = await $('body');
|
|
69
|
+
// eslint-disable-next-line no-undef
|
|
70
|
+
const headElem = await $('head');
|
|
71
|
+
// eslint-disable-next-line no-undef
|
|
72
|
+
const overlayContainerElem = await $(OVERLAY_CONTAINER);
|
|
73
|
+
return { htmlElem, bodyElem, headElem, overlayContainerElem };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// eslint-disable-next-line
|
|
77
|
+
async function setupStrategy() {
|
|
78
|
+
// couldn't find a good strategy for access to the slot's content
|
|
79
|
+
// using this technique requires that you return a single element
|
|
80
|
+
// eslint-disable-next-line
|
|
81
|
+
await browser.addLocatorStrategy(
|
|
82
|
+
'getFirstSlotElement',
|
|
83
|
+
(slotElem) => slotElem.assignedNodes()[0]
|
|
84
|
+
);
|
|
85
|
+
// eslint-disable-next-line
|
|
86
|
+
await browser.addLocatorStrategy('getSlotElements', (slotElem) =>
|
|
87
|
+
slotElem.assignedNodes()
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function isModalOfVariantType(modalVariantType, type) {
|
|
92
|
+
if (!modalVariantType || !type) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const lowerCaseVariantType = modalVariantType.toLowerCase();
|
|
96
|
+
return lowerCaseVariantType.includes(type);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/*
|
|
100
|
+
* get all of the modal internals, top level elements only
|
|
101
|
+
* to get contents of header, body, footer
|
|
102
|
+
* use getModalHeaderInternals, getModalBodyInternals,
|
|
103
|
+
* and getModalFooterInternals
|
|
104
|
+
*/
|
|
105
|
+
// eslint-disable-next-line
|
|
106
|
+
async function getModalInternals(config, modalIndex = 0) {
|
|
107
|
+
const { modalVariantType } = config;
|
|
108
|
+
expect(modalVariantType).not.toBeFalsy();
|
|
109
|
+
// initialize returned values
|
|
110
|
+
let modalBaseElem = null;
|
|
111
|
+
let modalBaseElems = null;
|
|
112
|
+
let modalBaseBackdropElem = null;
|
|
113
|
+
let modalSectionElem = null;
|
|
114
|
+
let modalCloseButton = null;
|
|
115
|
+
let modalDataSlot = null;
|
|
116
|
+
let modalContainerElem = null;
|
|
117
|
+
let modalElem = null;
|
|
118
|
+
let modalHeaderElem = null;
|
|
119
|
+
let modalBodyElem = null;
|
|
120
|
+
let modalFooterElem = null;
|
|
121
|
+
let focusTrapElem = null;
|
|
122
|
+
let focusTrapSlotElem = null;
|
|
123
|
+
|
|
124
|
+
// get overlay container
|
|
125
|
+
const { overlayContainerElem } = await getOverlayContainer();
|
|
126
|
+
// get modal base element
|
|
127
|
+
if (modalIndex === 0) {
|
|
128
|
+
modalBaseElem = await overlayContainerElem.shadow$(MODAL_BASE);
|
|
129
|
+
} else {
|
|
130
|
+
modalBaseElems = await overlayContainerElem.shadow$$(MODAL_BASE);
|
|
131
|
+
const modalBaseElemRequested = await modalBaseElems[modalIndex];
|
|
132
|
+
modalBaseElem = modalBaseElemRequested ? modalBaseElemRequested : null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const modalBaseExists = await modalBaseElem.isExisting();
|
|
136
|
+
// if modal base exists, continue getting modal internals
|
|
137
|
+
if (modalBaseExists) {
|
|
138
|
+
// get modal base element backdrop
|
|
139
|
+
modalBaseBackdropElem = await modalBaseElem.shadow$(
|
|
140
|
+
MODAL_BODY_BACKDROP
|
|
141
|
+
);
|
|
142
|
+
// get focus trap element
|
|
143
|
+
focusTrapElem = await modalBaseElem.shadow$(FOCUS_TRAP);
|
|
144
|
+
await focusTrapElem.waitForDisplayed();
|
|
145
|
+
focusTrapSlotElem = await focusTrapElem.shadow$('slot');
|
|
146
|
+
|
|
147
|
+
// get modal's <section> element just inside focus trap
|
|
148
|
+
// eslint-disable-next-line
|
|
149
|
+
modalSectionElem = await browser.custom$(
|
|
150
|
+
'getFirstSlotElement',
|
|
151
|
+
focusTrapSlotElem
|
|
152
|
+
);
|
|
153
|
+
await modalSectionElem.waitForDisplayed();
|
|
154
|
+
// get modal close button
|
|
155
|
+
modalCloseButton = await modalSectionElem.$(MODAL_CLOSE_BTN);
|
|
156
|
+
await modalCloseButton.waitForDisplayed();
|
|
157
|
+
|
|
158
|
+
// get modal div slot (not an actual slot)
|
|
159
|
+
modalDataSlot = await modalSectionElem.$(MODAL_DIV_SLOT);
|
|
160
|
+
await modalDataSlot.waitForDisplayed();
|
|
161
|
+
// get div[data-container].slds-modal__container
|
|
162
|
+
modalContainerElem = await modalSectionElem.$(MODAL_CONTAINER_DIV);
|
|
163
|
+
await modalContainerElem.waitForDisplayed();
|
|
164
|
+
// get lightning-modal element
|
|
165
|
+
modalElem = await modalSectionElem.$(MODAL);
|
|
166
|
+
await modalElem.waitForDisplayed();
|
|
167
|
+
|
|
168
|
+
// skip looking for modalHeader when type of modal is 'headless'
|
|
169
|
+
if (!isModalOfVariantType(modalVariantType, 'headless')) {
|
|
170
|
+
// get lightning-modal-header, it doesn't always exist
|
|
171
|
+
modalHeaderElem = await modalElem.shadow$(MODAL_HEADER);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// get lightning-modal-body, it doesn't always exist
|
|
175
|
+
// but in our examples it's always present
|
|
176
|
+
modalBodyElem = await modalElem.shadow$(MODAL_BODY);
|
|
177
|
+
// skip looking for modalHeader when type of modal is 'footless'
|
|
178
|
+
if (!isModalOfVariantType(modalVariantType, 'footless')) {
|
|
179
|
+
// get lightning-modal-footer, it doesn't always exist
|
|
180
|
+
modalFooterElem = await modalElem.shadow$(MODAL_FOOTER);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
modalBaseElem,
|
|
185
|
+
modalBaseBackdropElem,
|
|
186
|
+
modalSectionElem,
|
|
187
|
+
modalCloseButton,
|
|
188
|
+
modalDataSlot,
|
|
189
|
+
modalContainerElem,
|
|
190
|
+
modalElem,
|
|
191
|
+
modalHeaderElem,
|
|
192
|
+
modalBodyElem,
|
|
193
|
+
modalFooterElem,
|
|
194
|
+
focusTrapElem,
|
|
195
|
+
focusTrapSlotElem,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/*
|
|
200
|
+
* gets the internal elements of lightning-modal-header component
|
|
201
|
+
*/
|
|
202
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
203
|
+
async function getModalHeaderInternals(headerElem) {
|
|
204
|
+
if (!headerElem) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
let headerOuterDiv = null;
|
|
208
|
+
let headerHeading = null;
|
|
209
|
+
let headerSlot = null;
|
|
210
|
+
|
|
211
|
+
if (headerElem) {
|
|
212
|
+
headerOuterDiv = await headerElem.shadow$('.slds-modal__header');
|
|
213
|
+
headerHeading = await headerOuterDiv.$('[data-label]');
|
|
214
|
+
headerSlot = await headerOuterDiv.$('[data-default-slot]');
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
headerOuterDiv,
|
|
218
|
+
headerHeading,
|
|
219
|
+
headerSlot,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/*
|
|
224
|
+
* gets the internal elements of lightning-modal-body component
|
|
225
|
+
*/
|
|
226
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
227
|
+
async function getModalBodyInternals(bodyElem) {
|
|
228
|
+
if (!bodyElem) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
let bodyOuterDiv = null;
|
|
232
|
+
let bodySlotContents = null;
|
|
233
|
+
if (bodyElem) {
|
|
234
|
+
bodyOuterDiv = await bodyElem.shadow$(MODAL_BODY_DIV);
|
|
235
|
+
const bodySlot = await bodyOuterDiv.shadow$(MODAL_BODY_SLOT);
|
|
236
|
+
// eslint-disable-next-line no-undef
|
|
237
|
+
bodySlotContents = await browser.custom$('getSlotElements', bodySlot);
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
bodyOuterDiv,
|
|
241
|
+
bodySlotContents,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/*
|
|
246
|
+
* gets the internal elements of lightning-modal-footer component
|
|
247
|
+
*/
|
|
248
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
249
|
+
async function getModalFooterInternals(footerElem) {
|
|
250
|
+
if (!footerElem) {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
let footerOuterDiv = null;
|
|
254
|
+
let footerSlotContents = null;
|
|
255
|
+
if (footerElem) {
|
|
256
|
+
footerOuterDiv = await footerElem.shadow$('.slds-modal__footer');
|
|
257
|
+
const footerSlot = await footerOuterDiv.$('[data-footer-slot]');
|
|
258
|
+
// eslint-disable-next-line no-undef
|
|
259
|
+
footerSlotContents = await browser.custom$(
|
|
260
|
+
'getSlotElements',
|
|
261
|
+
footerSlot
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
footerOuterDiv,
|
|
266
|
+
footerSlotContents,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// get a specific element from the modal footer
|
|
271
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
272
|
+
async function getElementInModalFooter(
|
|
273
|
+
footerSlotContents,
|
|
274
|
+
selector,
|
|
275
|
+
elemIndex = 0
|
|
276
|
+
) {
|
|
277
|
+
let element = null;
|
|
278
|
+
const elements = await footerSlotContents.$$(selector);
|
|
279
|
+
if (elements) {
|
|
280
|
+
element = await elements[elemIndex];
|
|
281
|
+
}
|
|
282
|
+
return element;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
286
|
+
async function getNumModals() {
|
|
287
|
+
// initialize returned values
|
|
288
|
+
let numModals = 0;
|
|
289
|
+
// get overlay container
|
|
290
|
+
const { overlayContainerElem } = await getOverlayContainer();
|
|
291
|
+
// get modal base elements
|
|
292
|
+
const modalBaseElems = await overlayContainerElem.shadow$$(MODAL_BASE);
|
|
293
|
+
if (modalBaseElems) {
|
|
294
|
+
numModals = modalBaseElems.length;
|
|
295
|
+
}
|
|
296
|
+
return numModals;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/*
|
|
300
|
+
* Function includes expect tests to validate the expected
|
|
301
|
+
* modal close button variant set based on screen size,
|
|
302
|
+
* size attribute, and modalVariant, and actual screen size that was set.
|
|
303
|
+
*/
|
|
304
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
305
|
+
async function validateModalCloseButtonAttributes(config) {
|
|
306
|
+
const { modalSize } = config;
|
|
307
|
+
|
|
308
|
+
// first, get window size
|
|
309
|
+
// eslint-disable-next-line no-undef
|
|
310
|
+
const { width: windowWidth } = await browser.getWindowSize();
|
|
311
|
+
let modalCloseButtonVariant = null;
|
|
312
|
+
let modalCloseButtonCssClass = null;
|
|
313
|
+
const { modalCloseButton } = await getModalInternals(config);
|
|
314
|
+
if (modalCloseButton) {
|
|
315
|
+
modalCloseButtonVariant = await modalCloseButton.getAttribute(
|
|
316
|
+
'variant'
|
|
317
|
+
);
|
|
318
|
+
modalCloseButtonCssClass = await modalCloseButton.getAttribute('class');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// variant and css class should always be set
|
|
322
|
+
// to a value. specific checks based on full
|
|
323
|
+
// screen behavior occur below
|
|
324
|
+
expect(modalCloseButtonVariant).not.toBeNull();
|
|
325
|
+
expect(modalCloseButtonCssClass).toContain(MODAL_CLOSE_BTN_CLASS);
|
|
326
|
+
|
|
327
|
+
// the only time size='full' actual renders full page width and height
|
|
328
|
+
// is when windowWidth is set <= 767
|
|
329
|
+
if (
|
|
330
|
+
modalSize === MODAL_SIZE_FULL &&
|
|
331
|
+
windowWidth <= MODAL_FULL_SCREEN_SMALL_BREAKPOINT
|
|
332
|
+
) {
|
|
333
|
+
expect(modalCloseButtonVariant).toEqual(
|
|
334
|
+
MODAL_CLOSE_BUTTON_FULL_VARIANT
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
expect(modalCloseButtonCssClass).toContain(MODAL_CLOSE_BTN_FULL_CLASS);
|
|
338
|
+
expect(modalCloseButtonCssClass).toContain(
|
|
339
|
+
MODAL_CLOSE_BTN_ICON_BORDER_CLASS
|
|
340
|
+
);
|
|
341
|
+
} else {
|
|
342
|
+
// 'small', 'medium', 'large', and ('full' when windowWidth is set > 767) are normal modal behavior
|
|
343
|
+
expect(modalCloseButtonVariant).toEqual(
|
|
344
|
+
MODAL_CLOSE_BUTTON_NORMAL_VARIANT
|
|
345
|
+
);
|
|
346
|
+
expect(modalCloseButtonCssClass).not.toContain(
|
|
347
|
+
MODAL_CLOSE_BTN_FULL_CLASS
|
|
348
|
+
);
|
|
349
|
+
expect(modalCloseButtonCssClass).not.toContain(
|
|
350
|
+
MODAL_CLOSE_BTN_ICON_BORDER_CLASS
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/* Function includes expect tests to validate the expected
|
|
356
|
+
* modal HEIGHT behavior based on screen size, size attribute,
|
|
357
|
+
* and modalVariant, and actual screen size that was set.
|
|
358
|
+
*/
|
|
359
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
360
|
+
async function validateModalHeightBehavior(config) {
|
|
361
|
+
const { modalSize } = config;
|
|
362
|
+
|
|
363
|
+
// first, get window size
|
|
364
|
+
// eslint-disable-next-line no-undef
|
|
365
|
+
const { width: windowWidth } = await browser.getWindowSize();
|
|
366
|
+
// get <lightning-modal> element
|
|
367
|
+
const { modalBodyElem, modalElem } = await getModalInternals(config);
|
|
368
|
+
|
|
369
|
+
const { x: modalElemLocX, y: modalElemLocY } =
|
|
370
|
+
await modalElem.getLocation();
|
|
371
|
+
|
|
372
|
+
// get <lightning-modal-body> element
|
|
373
|
+
const { bodyOuterDiv } = await getModalBodyInternals(modalBodyElem);
|
|
374
|
+
const modalBodyOuterDivStyle = await bodyOuterDiv.getAttribute('style');
|
|
375
|
+
const modalBodyStyleProps = parseStyleAttributes(modalBodyOuterDivStyle);
|
|
376
|
+
|
|
377
|
+
// the only time size='full' actual renders full page width and height
|
|
378
|
+
// is when windowWidth is set <= 767
|
|
379
|
+
// note: reliably testing outer div element was not possible as
|
|
380
|
+
// webdriver getSize didn't provide correct values for comparison
|
|
381
|
+
// the tests below verify that the code path for setting full height are invoked
|
|
382
|
+
if (
|
|
383
|
+
modalSize === MODAL_SIZE_FULL &&
|
|
384
|
+
windowWidth <= MODAL_FULL_SCREEN_SMALL_BREAKPOINT
|
|
385
|
+
) {
|
|
386
|
+
// the <lightning-modal> element is consistently accurate for
|
|
387
|
+
// location measurment in the CI
|
|
388
|
+
// typical values are: { x: 0, y: 48 },
|
|
389
|
+
// y=48 comes from margin/padding and is expected
|
|
390
|
+
expect(modalElemLocX).toEqual(MODAL_ELEM_FULL_PX_OFFSET_X);
|
|
391
|
+
expect(modalElemLocY).toBeLessThan(MODAL_ELEM_FULL_PX_OFFSET_Y);
|
|
392
|
+
// these next two tests are proxy behavior indicating
|
|
393
|
+
// that the event listeners, and rendering has updated
|
|
394
|
+
// to make the modal go full height
|
|
395
|
+
expect(modalBodyStyleProps[MAX_HEIGHT]).toEqual(
|
|
396
|
+
modalBodyStyleProps[MIN_HEIGHT]
|
|
397
|
+
);
|
|
398
|
+
expect(modalBodyStyleProps[MIN_HEIGHT]).toBeGreaterThan(
|
|
399
|
+
MODAL_BODY_MIN_HEIGHT_PX
|
|
400
|
+
);
|
|
401
|
+
} else {
|
|
402
|
+
// 'small', 'medium', 'large', and ('full' when windowWidth is set > 767) are normal modal behavior
|
|
403
|
+
// the <lightning-modal> element is consistently accurate for
|
|
404
|
+
// location measurment in the CI
|
|
405
|
+
// location values vary based on screen and size value
|
|
406
|
+
// typical value are: { x: > 25, y: > 60 },
|
|
407
|
+
// y=48 comes from margin/padding and is expected
|
|
408
|
+
expect(modalElemLocX).toBeGreaterThan(MODAL_DEFAULT_PX_OFFSET_X);
|
|
409
|
+
expect(modalElemLocY).toBeGreaterThan(MODAL_DEFAULT_PX_OFFSET_Y);
|
|
410
|
+
// these next two tests are proxy behavior indicating
|
|
411
|
+
// that the event listeners, and rendering has updated
|
|
412
|
+
// to make the modal go full height
|
|
413
|
+
expect(modalBodyStyleProps[MAX_HEIGHT]).not.toEqual(
|
|
414
|
+
modalBodyStyleProps[MIN_HEIGHT]
|
|
415
|
+
);
|
|
416
|
+
expect(modalBodyStyleProps[MIN_HEIGHT]).toEqual(
|
|
417
|
+
MODAL_BODY_MIN_HEIGHT_PX
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/* Function includes expect tests to validate the expected
|
|
423
|
+
* modal WIDTH behavior based on screen size, size attribute,
|
|
424
|
+
* and modalVariant, and actual screen size that was set.
|
|
425
|
+
*/
|
|
426
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
427
|
+
async function validateModalWidthBehavior(config, modalIndex = 0) {
|
|
428
|
+
const { modalSize } = config;
|
|
429
|
+
// first, get outer window size
|
|
430
|
+
// eslint-disable-next-line no-undef
|
|
431
|
+
const { width: windowWidth } = await browser.getWindowSize();
|
|
432
|
+
|
|
433
|
+
// second, find modal elem, then get width
|
|
434
|
+
const { modalElem } = await getModalInternals(config, modalIndex);
|
|
435
|
+
const { width: modalElemWidth } = await modalElem.getSize();
|
|
436
|
+
|
|
437
|
+
// the only time size='full' actual renders full page width and height
|
|
438
|
+
// is when windowWidth is set <= 767
|
|
439
|
+
if (
|
|
440
|
+
modalSize === MODAL_SIZE_FULL &&
|
|
441
|
+
windowWidth <= MODAL_FULL_SCREEN_SMALL_BREAKPOINT
|
|
442
|
+
) {
|
|
443
|
+
expect(modalElemWidth).toEqual(windowWidth);
|
|
444
|
+
} else {
|
|
445
|
+
// 'small', 'medium', 'large', and ('full' when windowWidth is set > 767) are normal modal behavior
|
|
446
|
+
expect(modalElemWidth).toBeLessThan(windowWidth);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// convert from screen size label to actual pixel values
|
|
451
|
+
const getScreenSizeValues = (size = SCREEN_SIZE_LARGE) => {
|
|
452
|
+
if (!size) {
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
return SCREEN_SIZE[size];
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
// convert from modal name label (all, footless) to
|
|
459
|
+
// the size value that is set (small, large, full)
|
|
460
|
+
// for that modal example
|
|
461
|
+
const getModalSizeFromName = (name = 'all') => {
|
|
462
|
+
if (!name) {
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
return NAME_TO_SIZE[name];
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
/*
|
|
469
|
+
* Function to set window size to requested values
|
|
470
|
+
*/
|
|
471
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
472
|
+
async function setWindowSize(screenSizeToSet) {
|
|
473
|
+
// set screen size for testing modal behavior
|
|
474
|
+
const { width, height } = getScreenSizeValues(screenSizeToSet);
|
|
475
|
+
// eslint-disable-next-line no-undef
|
|
476
|
+
await browser.setWindowSize(width, height);
|
|
477
|
+
// eslint-disable-next-line no-undef
|
|
478
|
+
await browser.pause(BROWSER_RESIZE_PAUSE);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/*
|
|
482
|
+
* Function to open a modal based on the config passed in
|
|
483
|
+
*/
|
|
484
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
485
|
+
async function openModal({
|
|
486
|
+
modalVariantType = '',
|
|
487
|
+
screenSizeToSet = SCREEN_SIZE_LARGE,
|
|
488
|
+
runMockMatchMedia = false,
|
|
489
|
+
}) {
|
|
490
|
+
if (!modalVariantType) {
|
|
491
|
+
expect(modalVariantType).not.toEqual('');
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
// run matchMedia, if needed
|
|
495
|
+
if (runMockMatchMedia) {
|
|
496
|
+
await mockMatchMedia();
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// set a specific window size based on sizing descriptor (SMALL, MEDIUM)
|
|
500
|
+
await setWindowSize(screenSizeToSet);
|
|
501
|
+
|
|
502
|
+
// find the button that launches the modal, and click
|
|
503
|
+
const modalBtnSelector = SELECTORS[modalVariantType];
|
|
504
|
+
await clickButton(modalBtnSelector);
|
|
505
|
+
// eslint-disable-next-line no-undef
|
|
506
|
+
await browser.pause(MODAL_RENDER_PAUSE);
|
|
507
|
+
return true;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/*
|
|
511
|
+
* Provides a means of verifying that the html and type
|
|
512
|
+
* for the element details passed in match the attributes
|
|
513
|
+
* of the modal's close button
|
|
514
|
+
*/
|
|
515
|
+
function isModalCloseButton(info) {
|
|
516
|
+
if (!info) {
|
|
517
|
+
expect(info).not.toBeFalsy();
|
|
518
|
+
}
|
|
519
|
+
const { html, type } = info;
|
|
520
|
+
const isTypeButton = type === 'button';
|
|
521
|
+
const hasCorrectSldsClasses =
|
|
522
|
+
html.indexOf('slds-button') >= 0 &&
|
|
523
|
+
html.indexOf('slds-button_icon') >= 0;
|
|
524
|
+
const hasCorrectText = html.indexOf('Cancel and close') >= 0;
|
|
525
|
+
return (isTypeButton && hasCorrectSldsClasses && hasCorrectText) || false;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/*
|
|
529
|
+
* Function to close a modal based on the config passed in
|
|
530
|
+
*/
|
|
531
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
532
|
+
async function closeModal(config, modalIndex = 0, closeMethod = 'click') {
|
|
533
|
+
if (typeof modalIndex !== 'number' && modalIndex >= 0) {
|
|
534
|
+
console.error(
|
|
535
|
+
'closeModal :: requires modalIndex value to correctly function'
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
if (closeMethod === 'click') {
|
|
539
|
+
const { modalCloseButton } = await getModalInternals(
|
|
540
|
+
config,
|
|
541
|
+
modalIndex
|
|
542
|
+
);
|
|
543
|
+
if (modalCloseButton) {
|
|
544
|
+
await modalCloseButton.click();
|
|
545
|
+
// eslint-disable-next-line no-undef
|
|
546
|
+
await browser.pause(MODAL_RENDER_PAUSE);
|
|
547
|
+
}
|
|
548
|
+
} else if (closeMethod === KEY.ENTER || closeMethod === KEY.SPACE) {
|
|
549
|
+
// this method assumes you have already placed focus on the close button
|
|
550
|
+
// since there is no webdriver means of setting focus
|
|
551
|
+
// for example, this isn't possible: await modalCloseButton.focus()
|
|
552
|
+
const activeElement = await getActiveShadowElement();
|
|
553
|
+
const closeButtonIsFocused = isModalCloseButton(activeElement);
|
|
554
|
+
expect(closeButtonIsFocused).toBeTrue();
|
|
555
|
+
if (closeButtonIsFocused) {
|
|
556
|
+
// eslint-disable-next-line no-undef
|
|
557
|
+
await browser.keys(closeMethod);
|
|
558
|
+
// eslint-disable-next-line no-undef
|
|
559
|
+
await browser.pause(MODAL_RENDER_PAUSE);
|
|
560
|
+
}
|
|
561
|
+
} else if (closeMethod === KEY.ESC) {
|
|
562
|
+
// eslint-disable-next-line no-undef
|
|
563
|
+
await browser.keys(closeMethod);
|
|
564
|
+
// eslint-disable-next-line no-undef
|
|
565
|
+
await browser.pause(MODAL_RENDER_PAUSE);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/*
|
|
570
|
+
* Function to mock up matchMedia for reduced motion
|
|
571
|
+
*/
|
|
572
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
573
|
+
async function mockMatchMedia() {
|
|
574
|
+
// eslint-disable-next-line no-undef
|
|
575
|
+
await browser.execute(() => {
|
|
576
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
577
|
+
writable: true,
|
|
578
|
+
value: () => ({
|
|
579
|
+
matches: true,
|
|
580
|
+
media: '(prefers-reduced-motion: reduce)',
|
|
581
|
+
}),
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
587
|
+
async function checkBackgroundElemForInertness(elems, expectedValue = true) {
|
|
588
|
+
return Promise.all(
|
|
589
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
590
|
+
elems.map(async (elem) => {
|
|
591
|
+
const elemType = await elem.getTagName();
|
|
592
|
+
const initAriaHiddenValue = await elem.getAttribute('aria-hidden');
|
|
593
|
+
const ariaHiddenValue =
|
|
594
|
+
initAriaHiddenValue === 'true' ? true : null;
|
|
595
|
+
// if the element isn't in the approved group, make sure it's hidden
|
|
596
|
+
if (
|
|
597
|
+
elemType !== 'lightning-overlay-container' &&
|
|
598
|
+
elemType !== 'lightning-primitive-bubble'
|
|
599
|
+
) {
|
|
600
|
+
expect(ariaHiddenValue).toEqual(expectedValue);
|
|
601
|
+
}
|
|
602
|
+
})
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// validateInertness checks two things:
|
|
607
|
+
// (a) aria-hidden is set correctly based on parameter 'on'
|
|
608
|
+
// (b) validates HEAD element doesn't receive aria-hidden
|
|
609
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
610
|
+
async function validateInertness(bgInert = true) {
|
|
611
|
+
// aria-hidden = true equivalent to ON
|
|
612
|
+
// aria-hidden not set (null) equivalent to OFF
|
|
613
|
+
const ariaHiddenValue = bgInert ? true : null;
|
|
614
|
+
// get elements
|
|
615
|
+
// eslint-disable-next-line no-undef
|
|
616
|
+
const htmlElem = await $('html');
|
|
617
|
+
const allRootElemsToCheck = await htmlElem.$$('body > *');
|
|
618
|
+
|
|
619
|
+
// get HEAD aria-hidden value
|
|
620
|
+
// eslint-disable-next-line no-undef
|
|
621
|
+
const headElem = await $('head');
|
|
622
|
+
const headAriaHiddenValueCheck = await headElem.getAttribute('aria-hidden');
|
|
623
|
+
// HEAD element should NEVER have inert applied
|
|
624
|
+
expect(headAriaHiddenValueCheck).toBeNull();
|
|
625
|
+
// check body elements to verify they have been set inert
|
|
626
|
+
await checkBackgroundElemForInertness(allRootElemsToCheck, ariaHiddenValue);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// function to enable forward and reverse tab navigation
|
|
630
|
+
// positive number = number of tabs to navigate forward
|
|
631
|
+
// negative number = number of tabs to navigate backward (shift + tab)
|
|
632
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
633
|
+
async function repeatTab(tabCount = 0) {
|
|
634
|
+
// check to see if reverse or forward tab direction
|
|
635
|
+
if (tabCount !== 0) {
|
|
636
|
+
const shouldShiftTab = tabCount < 0;
|
|
637
|
+
const numTabs = Math.abs(tabCount);
|
|
638
|
+
for (let count = numTabs; count > 0; count--) {
|
|
639
|
+
/* eslint-disable */
|
|
640
|
+
if (shouldShiftTab) {
|
|
641
|
+
await browser.keys([KEY.SHIFT, KEY.TAB, 'NULL']);
|
|
642
|
+
} else {
|
|
643
|
+
await browser.keys(KEY.TAB);
|
|
644
|
+
}
|
|
645
|
+
/* eslint-enable */
|
|
646
|
+
}
|
|
647
|
+
// unset 'Shift' key after multiple tabs applied
|
|
648
|
+
if (shouldShiftTab) {
|
|
649
|
+
// eslint-disable-next-line no-undef
|
|
650
|
+
await browser.keys(KEY.SHIFT);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// expects positive number of times to repeat
|
|
656
|
+
// and key to send
|
|
657
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
658
|
+
async function repeatKey(repeatCount = 0, keyName) {
|
|
659
|
+
// check to see if reverse or forward tab direction
|
|
660
|
+
if (repeatCount > 0 || !keyName) {
|
|
661
|
+
for (let count = Math.abs(repeatCount); count > 0; count--) {
|
|
662
|
+
/* eslint-disable */
|
|
663
|
+
await browser.keys(keyName);
|
|
664
|
+
// pause is needed for the browser to recognize all of the key clicks
|
|
665
|
+
await browser.pause(PAUSE_MICRO);
|
|
666
|
+
/* eslint-enable */
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// expects positive number of times to repeat
|
|
672
|
+
// and key to send
|
|
673
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
674
|
+
async function typeLetters(word) {
|
|
675
|
+
// check to see if reverse or forward tab direction
|
|
676
|
+
if (word && word.length > 0) {
|
|
677
|
+
const arrOfLetters = word.split('');
|
|
678
|
+
for (let count = 0; count < arrOfLetters.length; count++) {
|
|
679
|
+
const letterToType = arrOfLetters[count];
|
|
680
|
+
/* eslint-disable */
|
|
681
|
+
await browser.keys(letterToType);
|
|
682
|
+
// pause is needed for the browser to recognize all of the key clicks
|
|
683
|
+
await browser.pause(PAUSE_MICRO);
|
|
684
|
+
/* eslint-enable */
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Fills out a form using provided data
|
|
690
|
+
// Assumes the first item in the form is already focused
|
|
691
|
+
// Based on type of element executes different input strategies
|
|
692
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
693
|
+
async function fillForm({ data = [], tabToFormAction = 0 }) {
|
|
694
|
+
if (!data || data.length === 0) {
|
|
695
|
+
console.error('fillForm :: requires data array of fields');
|
|
696
|
+
}
|
|
697
|
+
for (let count = 0; count < data.length; count++) {
|
|
698
|
+
const { name, value, type, numTabsToNext } = data[count];
|
|
699
|
+
/* eslint-disable */
|
|
700
|
+
if (type === 'text') {
|
|
701
|
+
await browser.keys(value);
|
|
702
|
+
} else if (type === 'combobox') {
|
|
703
|
+
if (typeof value === 'number') {
|
|
704
|
+
await browser.keys('Enter');
|
|
705
|
+
await repeatKey(value, 'ArrowDown');
|
|
706
|
+
await browser.keys('Enter');
|
|
707
|
+
} else {
|
|
708
|
+
await browser.keys('Enter');
|
|
709
|
+
await typeLetters(value);
|
|
710
|
+
await browser.keys('Enter');
|
|
711
|
+
}
|
|
712
|
+
} else if (type === 'datepicker') {
|
|
713
|
+
await browser.keys(value);
|
|
714
|
+
await browser.keys('Enter');
|
|
715
|
+
} else if (type === 'timepicker') {
|
|
716
|
+
// timepicker is a combobox
|
|
717
|
+
await browser.keys('Enter');
|
|
718
|
+
await repeatKey(value, 'ArrowDown');
|
|
719
|
+
await browser.keys('Enter');
|
|
720
|
+
|
|
721
|
+
} else {
|
|
722
|
+
console.error(
|
|
723
|
+
"fillForm :: Ooops, shouldn't happen: ",
|
|
724
|
+
name,
|
|
725
|
+
' field'
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
if (numTabsToNext !== 0) {
|
|
729
|
+
await repeatTab(numTabsToNext);
|
|
730
|
+
}
|
|
731
|
+
/* eslint-enable */
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// finished filling out the form
|
|
735
|
+
// navigate to button to click and submit the form
|
|
736
|
+
if (tabToFormAction > 0) {
|
|
737
|
+
// number of tabs from last element to submit button
|
|
738
|
+
await repeatTab(tabToFormAction);
|
|
739
|
+
// submit the form
|
|
740
|
+
// eslint-disable-next-line
|
|
741
|
+
await browser.keys('Enter');
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// loop through received data, and validate
|
|
746
|
+
function validateData(verifyData, origData) {
|
|
747
|
+
if (!verifyData || !origData) {
|
|
748
|
+
console.error('validateData :: missing data');
|
|
749
|
+
}
|
|
750
|
+
const dataValidated = {};
|
|
751
|
+
let countInvalidData = 0;
|
|
752
|
+
let cleanedStr;
|
|
753
|
+
let cleanedData;
|
|
754
|
+
if (typeof verifyData === 'string') {
|
|
755
|
+
cleanedStr = verifyData.replace(/\\/g, '');
|
|
756
|
+
const unwantedChar = '"';
|
|
757
|
+
if (cleanedStr.charAt(0) === unwantedChar) {
|
|
758
|
+
cleanedStr = cleanedStr.substring(1, cleanedStr.length - 1);
|
|
759
|
+
}
|
|
760
|
+
if (cleanedStr.charAt(cleanedStr.length - 1) === unwantedChar) {
|
|
761
|
+
cleanedStr = cleanedStr.substring(0, cleanedStr.length);
|
|
762
|
+
}
|
|
763
|
+
cleanedData = JSON.parse(cleanedStr);
|
|
764
|
+
}
|
|
765
|
+
const data = cleanedData ? cleanedData : verifyData;
|
|
766
|
+
origData.forEach((item) => {
|
|
767
|
+
const { name, value, result } = item;
|
|
768
|
+
const hasKeys = name.indexOf('.') >= 0;
|
|
769
|
+
const keys = hasKeys ? name.split('.') : [name];
|
|
770
|
+
const originalValue = String(result ? result : value);
|
|
771
|
+
// this is simplified structure
|
|
772
|
+
// assumes only two object levels
|
|
773
|
+
const receivedValue =
|
|
774
|
+
keys.length === 2
|
|
775
|
+
? String(data[keys[0]][keys[1]])
|
|
776
|
+
: String(data[keys[0]]);
|
|
777
|
+
const valuesMatch = receivedValue === originalValue;
|
|
778
|
+
if (valuesMatch === false) {
|
|
779
|
+
countInvalidData++;
|
|
780
|
+
}
|
|
781
|
+
dataValidated[name] = receivedValue === originalValue;
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
return {
|
|
785
|
+
incorrectValues: countInvalidData,
|
|
786
|
+
dataValidated,
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// function to recurse .activeElement, since each
|
|
791
|
+
// shadow root can have it's own active element
|
|
792
|
+
// returns outerHTML and localName of each active element
|
|
793
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
794
|
+
async function getActiveShadowElements() {
|
|
795
|
+
// eslint-disable-next-line no-undef
|
|
796
|
+
return browser.execute(function () {
|
|
797
|
+
let activeElement = document.activeElement;
|
|
798
|
+
const activeElements = [];
|
|
799
|
+
while (
|
|
800
|
+
activeElement &&
|
|
801
|
+
activeElement.shadowRoot &&
|
|
802
|
+
activeElement.shadowRoot.activeElement
|
|
803
|
+
) {
|
|
804
|
+
const elem = {
|
|
805
|
+
type: activeElement.localName,
|
|
806
|
+
html: null,
|
|
807
|
+
};
|
|
808
|
+
// eslint-disable-next-line @lwc/lwc/no-inner-html
|
|
809
|
+
if (activeElement.outerHTML) {
|
|
810
|
+
// eslint-disable-next-line @lwc/lwc/no-inner-html
|
|
811
|
+
elem.html = activeElement.outerHTML;
|
|
812
|
+
}
|
|
813
|
+
activeElements.push(elem);
|
|
814
|
+
activeElement = activeElement.shadowRoot.activeElement;
|
|
815
|
+
}
|
|
816
|
+
if (activeElement) {
|
|
817
|
+
const elem = {
|
|
818
|
+
type: activeElement.localName,
|
|
819
|
+
html: null,
|
|
820
|
+
};
|
|
821
|
+
// eslint-disable-next-line @lwc/lwc/no-inner-html
|
|
822
|
+
if (activeElement.outerHTML) {
|
|
823
|
+
// eslint-disable-next-line @lwc/lwc/no-inner-html
|
|
824
|
+
elem.html = activeElement.outerHTML;
|
|
825
|
+
}
|
|
826
|
+
activeElements.push(elem);
|
|
827
|
+
}
|
|
828
|
+
return activeElements;
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
833
|
+
async function getActiveShadowElement() {
|
|
834
|
+
const allActiveShadowElements = await getActiveShadowElements();
|
|
835
|
+
if (allActiveShadowElements) {
|
|
836
|
+
const activeElemsLength = allActiveShadowElements.length;
|
|
837
|
+
return allActiveShadowElements[activeElemsLength - 1];
|
|
838
|
+
}
|
|
839
|
+
return [];
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// recurse using document.activeElement until reaching lightning-modal
|
|
843
|
+
// then get details
|
|
844
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
845
|
+
async function getCalendarDetail() {
|
|
846
|
+
// eslint-disable-next-line
|
|
847
|
+
return browser.execute(function () {
|
|
848
|
+
let activeElement = document.activeElement;
|
|
849
|
+
let details;
|
|
850
|
+
let nodes;
|
|
851
|
+
let calendarStyles = {};
|
|
852
|
+
let activeElements = [];
|
|
853
|
+
const CALENDAR = 'lightning-calendar';
|
|
854
|
+
|
|
855
|
+
function getStylesObj(str = '') {
|
|
856
|
+
let styleObj = {};
|
|
857
|
+
const stylesStr = str.replace(/ /g, '');
|
|
858
|
+
const styleArr = stylesStr.split(';');
|
|
859
|
+
styleArr.forEach((string) => {
|
|
860
|
+
const [key, value] = string.split(':');
|
|
861
|
+
if (key && value && !styleObj[key]) {
|
|
862
|
+
let num = null;
|
|
863
|
+
if (value.indexOf('px') > 0) {
|
|
864
|
+
num = value.substring(0, value.length - 2);
|
|
865
|
+
}
|
|
866
|
+
if (key === 'z-index') {
|
|
867
|
+
num = value;
|
|
868
|
+
}
|
|
869
|
+
styleObj[key] = num ? Number(num) : value;
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
return styleObj;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// loop through activeElement shadow until we find
|
|
876
|
+
// the lightning-calendar, then retrieve details
|
|
877
|
+
while (
|
|
878
|
+
activeElement &&
|
|
879
|
+
activeElement.shadowRoot &&
|
|
880
|
+
activeElement.shadowRoot.activeElement
|
|
881
|
+
) {
|
|
882
|
+
const type = activeElement.localName;
|
|
883
|
+
activeElements.push(type);
|
|
884
|
+
// eventually we reach lightning-calendar element
|
|
885
|
+
if (type === CALENDAR) {
|
|
886
|
+
const shadowRoot = activeElement.shadowRoot;
|
|
887
|
+
nodes = Array.from(shadowRoot.childNodes);
|
|
888
|
+
const nodeObj = nodes[0];
|
|
889
|
+
const nodeObjStyleAttr = nodeObj.getAttribute('style');
|
|
890
|
+
calendarStyles = getStylesObj(nodeObjStyleAttr);
|
|
891
|
+
details = {
|
|
892
|
+
calendarType: true,
|
|
893
|
+
calendarStyles,
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
activeElement = activeElement.shadowRoot.activeElement;
|
|
897
|
+
}
|
|
898
|
+
return details;
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
/*
|
|
903
|
+
* Function to parse the style attributes from an element
|
|
904
|
+
* in the case of px values, it converts the
|
|
905
|
+
* '101px' (string) to 101 (number)
|
|
906
|
+
* returning this as a style object
|
|
907
|
+
*/
|
|
908
|
+
function parseStyleAttributes(styleString) {
|
|
909
|
+
if (!styleString) {
|
|
910
|
+
return {};
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
let styleProps = {};
|
|
914
|
+
let stylesSplit = styleString.trim().split('; ');
|
|
915
|
+
if (stylesSplit && stylesSplit.length > 0) {
|
|
916
|
+
stylesSplit = stylesSplit.forEach((style) => {
|
|
917
|
+
const updatedStyle = style.replace(';', '').replace(':', '').trim();
|
|
918
|
+
const propSplit = updatedStyle.split(' ');
|
|
919
|
+
if (propSplit) {
|
|
920
|
+
const name = propSplit[0];
|
|
921
|
+
let value = propSplit[1];
|
|
922
|
+
// convert px values into numbers
|
|
923
|
+
if (value.includes('px')) {
|
|
924
|
+
value = Math.floor(Number(value.split('px')[0]));
|
|
925
|
+
}
|
|
926
|
+
styleProps[name] = value;
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
return styleProps;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// Validates what shadow element is active after
|
|
934
|
+
// either opening or closing the modal, which can be used
|
|
935
|
+
// to validate first element focused on modal open
|
|
936
|
+
// and last element focused after modal closed
|
|
937
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
938
|
+
async function validateModalFocus({
|
|
939
|
+
html = '',
|
|
940
|
+
type = '',
|
|
941
|
+
whichArrayElem = '',
|
|
942
|
+
}) {
|
|
943
|
+
// prevent null values from being passed immediately
|
|
944
|
+
expect(html).not.toBeFalsy();
|
|
945
|
+
expect(type).not.toBeFalsy();
|
|
946
|
+
|
|
947
|
+
// get active shadow elements for comparison
|
|
948
|
+
const activeElemArr = await getActiveShadowElements();
|
|
949
|
+
|
|
950
|
+
let penultimateElem;
|
|
951
|
+
const len = activeElemArr.length;
|
|
952
|
+
const lastElem = activeElemArr[len - 1];
|
|
953
|
+
// set default values for the html and type to use
|
|
954
|
+
let actualHtml = lastElem.html;
|
|
955
|
+
let actualType = lastElem.type;
|
|
956
|
+
|
|
957
|
+
// only concerned with <button> inside a few specific use cases
|
|
958
|
+
// where the data-focus attribute won't be passed down
|
|
959
|
+
if (
|
|
960
|
+
len > 1 &&
|
|
961
|
+
actualType &&
|
|
962
|
+
actualType === 'button' &&
|
|
963
|
+
whichArrayElem === 'penultimate'
|
|
964
|
+
) {
|
|
965
|
+
penultimateElem = activeElemArr[len - 2];
|
|
966
|
+
const { type: penultimateElemType, html: penultimateElemHtml } =
|
|
967
|
+
penultimateElem;
|
|
968
|
+
// cover case element is normal button, not using LWC lightning-button
|
|
969
|
+
if (
|
|
970
|
+
penultimateElemType &&
|
|
971
|
+
[
|
|
972
|
+
'lightning-button',
|
|
973
|
+
'lightning-button-icon',
|
|
974
|
+
'lightning-helptext',
|
|
975
|
+
].some((val) => penultimateElemType === val)
|
|
976
|
+
) {
|
|
977
|
+
actualHtml = penultimateElemHtml;
|
|
978
|
+
actualType = penultimateElemType;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
// validate the correct element is initially focused
|
|
982
|
+
expect(actualHtml).toContain(html);
|
|
983
|
+
expect(actualType).toEqual(type);
|
|
984
|
+
return { html: actualHtml, type: actualType };
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// Validates mumber of modals open at two separate
|
|
988
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
989
|
+
async function validateNumModals(values, checkNow = false) {
|
|
990
|
+
// prevent null values from being passed immediately
|
|
991
|
+
expect(values).not.toBeFalsy();
|
|
992
|
+
// in immediate use case scenario, get current num modals
|
|
993
|
+
let numCurrentModals = null;
|
|
994
|
+
if (checkNow) {
|
|
995
|
+
numCurrentModals = await getNumModals();
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// handles multiple value checks at once
|
|
999
|
+
// [{actual, expected}, {actual, expected}, {actual, expected}]
|
|
1000
|
+
if (Array.isArray(values)) {
|
|
1001
|
+
values.forEach(({ actual = null, expected = null }) => {
|
|
1002
|
+
if (actual === null || actual === undefined) {
|
|
1003
|
+
expect(numCurrentModals).toEqual(expected);
|
|
1004
|
+
} else {
|
|
1005
|
+
expect(actual).toEqual(expected);
|
|
1006
|
+
}
|
|
1007
|
+
});
|
|
1008
|
+
// covers the single value immediate check
|
|
1009
|
+
// { expected }
|
|
1010
|
+
} else if (typeof values === 'object') {
|
|
1011
|
+
const { actual, expected } = values;
|
|
1012
|
+
if (actual === null || actual === undefined) {
|
|
1013
|
+
expect(numCurrentModals).toEqual(expected);
|
|
1014
|
+
} else {
|
|
1015
|
+
expect(actual).toEqual(expected);
|
|
1016
|
+
}
|
|
1017
|
+
} else {
|
|
1018
|
+
console.error(
|
|
1019
|
+
'validateNumModals expects an array of objects, or an object'
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// the number of tab keys to hit is dependent
|
|
1025
|
+
// on the specific examples for a particular suite
|
|
1026
|
+
// of tests. have to look up value with this method
|
|
1027
|
+
function getNumTabsToCloseButton(spec = '', modalVariantType = '') {
|
|
1028
|
+
expect(spec).toBeTruthy();
|
|
1029
|
+
expect(modalVariantType).toBeTruthy();
|
|
1030
|
+
if (spec && modalVariantType) {
|
|
1031
|
+
return SPEC_TO_TABS_TO_CLOSE_BTN[spec][modalVariantType] || 0;
|
|
1032
|
+
}
|
|
1033
|
+
return 0;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// This function validates focus:
|
|
1037
|
+
// (a) the correct element is focused after modal opens
|
|
1038
|
+
// (b) the correct element is focused after modal closes
|
|
1039
|
+
// by doing this we are exercising focus code features:
|
|
1040
|
+
// 'focus first', 'focus after' in modalBase
|
|
1041
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
1042
|
+
async function validateModalOpenCloseFocusBehavior(
|
|
1043
|
+
config,
|
|
1044
|
+
expectedAfterOpen,
|
|
1045
|
+
expectedAfterClose,
|
|
1046
|
+
closeMethod = 'click',
|
|
1047
|
+
spec = 'accessibility'
|
|
1048
|
+
) {
|
|
1049
|
+
expect(config).not.toBeFalsy();
|
|
1050
|
+
expect(expectedAfterOpen).not.toBeFalsy();
|
|
1051
|
+
expect(expectedAfterClose).not.toBeFalsy();
|
|
1052
|
+
|
|
1053
|
+
// start by opening the modal
|
|
1054
|
+
await openModal(config);
|
|
1055
|
+
|
|
1056
|
+
// AFTER Modal open, test focus is on the correct element
|
|
1057
|
+
await validateModalFocus(expectedAfterOpen);
|
|
1058
|
+
|
|
1059
|
+
// get num modals before closing modal
|
|
1060
|
+
const numModalsAtStart = await getNumModals();
|
|
1061
|
+
|
|
1062
|
+
// if the desired method to close the modal
|
|
1063
|
+
// is ENTER or SPACE, then we first need to
|
|
1064
|
+
// tab navigate to the close button
|
|
1065
|
+
// if method to close is 'click' or ESC key, we
|
|
1066
|
+
// can skip this entirely
|
|
1067
|
+
if (closeMethod === KEY.ENTER || closeMethod === KEY.SPACE) {
|
|
1068
|
+
const { modalVariantType } = config;
|
|
1069
|
+
let tabNavValue = getNumTabsToCloseButton(spec, modalVariantType);
|
|
1070
|
+
if (tabNavValue !== 0 && typeof tabNavValue === 'number') {
|
|
1071
|
+
await repeatTab(tabNavValue);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// Initiate Modal Close
|
|
1076
|
+
await closeModal(config, 0, closeMethod);
|
|
1077
|
+
|
|
1078
|
+
// get num modals after closing modal
|
|
1079
|
+
const numModalsAfterClose = await getNumModals();
|
|
1080
|
+
|
|
1081
|
+
// AFTER modal is closed complete, retest what has focus
|
|
1082
|
+
await validateModalFocus(expectedAfterClose);
|
|
1083
|
+
|
|
1084
|
+
// verify modal was added and removed
|
|
1085
|
+
// this handles simple cases where a single modal is
|
|
1086
|
+
// opened, and it is later closed
|
|
1087
|
+
const expectedNumModals = [
|
|
1088
|
+
{ actual: numModalsAtStart, expected: 1 },
|
|
1089
|
+
{ actual: numModalsAfterClose, expected: 0 },
|
|
1090
|
+
];
|
|
1091
|
+
validateNumModals(expectedNumModals);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// validate that focus trap is working correctly
|
|
1095
|
+
// in this test, shift tab or reverse tab navigation is utilized
|
|
1096
|
+
// should end up on the 1st button in the modalFooter element
|
|
1097
|
+
// eslint-disable-next-line @lwc/lwc/no-async-await
|
|
1098
|
+
async function validateModalOpenTabNavFocusBehavior(
|
|
1099
|
+
config,
|
|
1100
|
+
expectedAfterOpen,
|
|
1101
|
+
expectedAfterTab,
|
|
1102
|
+
tabAction
|
|
1103
|
+
) {
|
|
1104
|
+
// make sure values are passed in
|
|
1105
|
+
expect(config).not.toBeFalsy();
|
|
1106
|
+
expect(expectedAfterOpen).not.toBeFalsy();
|
|
1107
|
+
expect(expectedAfterTab).not.toBeFalsy();
|
|
1108
|
+
expect(tabAction).not.toBeFalsy();
|
|
1109
|
+
|
|
1110
|
+
// open modal with config
|
|
1111
|
+
await openModal(config);
|
|
1112
|
+
|
|
1113
|
+
// AFTER Modal open, test what has focus
|
|
1114
|
+
await validateModalFocus(expectedAfterOpen);
|
|
1115
|
+
|
|
1116
|
+
// tab navigate backwards
|
|
1117
|
+
// should end up on 'Option 1' button
|
|
1118
|
+
await repeatTab(tabAction);
|
|
1119
|
+
|
|
1120
|
+
// AFTER tab navigation complete, retest what has focus
|
|
1121
|
+
await validateModalFocus(expectedAfterTab);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
module.exports = {
|
|
1125
|
+
setupStrategy,
|
|
1126
|
+
isModalOfVariantType,
|
|
1127
|
+
parseStyleAttributes,
|
|
1128
|
+
mockMatchMedia,
|
|
1129
|
+
// general usage across all modal spec
|
|
1130
|
+
openModal,
|
|
1131
|
+
closeModal,
|
|
1132
|
+
clickButton,
|
|
1133
|
+
// next method only used in modalAccessibility
|
|
1134
|
+
getModalInternals,
|
|
1135
|
+
getOverlayContainer,
|
|
1136
|
+
getScreenSizeValues,
|
|
1137
|
+
getModalSizeFromName,
|
|
1138
|
+
getModalHeaderInternals,
|
|
1139
|
+
getModalBodyInternals,
|
|
1140
|
+
getModalFooterInternals,
|
|
1141
|
+
// modalFullScreen
|
|
1142
|
+
setWindowSize,
|
|
1143
|
+
validateModalCloseButtonAttributes,
|
|
1144
|
+
validateModalHeightBehavior,
|
|
1145
|
+
validateModalWidthBehavior,
|
|
1146
|
+
// modalFunctionality
|
|
1147
|
+
validateInertness,
|
|
1148
|
+
checkBackgroundElemForInertness,
|
|
1149
|
+
repeatTab,
|
|
1150
|
+
repeatKey,
|
|
1151
|
+
typeLetters,
|
|
1152
|
+
fillForm,
|
|
1153
|
+
validateData,
|
|
1154
|
+
getActiveShadowElements,
|
|
1155
|
+
getActiveShadowElement,
|
|
1156
|
+
getNumModals,
|
|
1157
|
+
getElementInModalFooter,
|
|
1158
|
+
getCalendarDetail,
|
|
1159
|
+
// modalAccessibility
|
|
1160
|
+
validateNumModals,
|
|
1161
|
+
validateModalOpenCloseFocusBehavior,
|
|
1162
|
+
validateModalOpenTabNavFocusBehavior,
|
|
1163
|
+
// modalAccessibility && modalFocus
|
|
1164
|
+
validateModalFocus,
|
|
1165
|
+
};
|