lightning-base-components 1.18.1-alpha → 1.18.3-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.
Files changed (298) hide show
  1. package/metadata/raptor.json +9 -0
  2. package/package.json +57 -1
  3. package/src/lightning/accordion/__docs__/accordion.md +2 -2
  4. package/src/lightning/accordion/accordion.css +12 -0
  5. package/src/lightning/accordion/accordion.html +3 -1
  6. package/src/lightning/accordion/accordion.js +4 -2
  7. package/src/lightning/accordion/accordion.slds.css +671 -0
  8. package/src/lightning/accordionSection/accordion-section.slds.css +647 -0
  9. package/src/lightning/accordionSection/accordionSection.css +14 -0
  10. package/src/lightning/accordionSection/accordionSection.html +23 -19
  11. package/src/lightning/accordionSection/accordionSection.js +29 -2
  12. package/src/lightning/ariaObserver/__docs__/ariaObserver.md +21 -9
  13. package/src/lightning/ariaObserver/ariaObserver.js +185 -154
  14. package/src/lightning/ariaObserver/polyfill.js +639 -0
  15. package/src/lightning/avatar/__docs__/avatar.md +7 -7
  16. package/src/lightning/avatar/avatar.css +2 -0
  17. package/src/lightning/avatar/avatar.html +2 -0
  18. package/src/lightning/avatar/avatar.js +18 -15
  19. package/src/lightning/avatar/avatar.slds.css +272 -0
  20. package/src/lightning/badge/__docs__/badge.md +2 -2
  21. package/src/lightning/baseCombobox/base-combobox.slds.css +1585 -0
  22. package/src/lightning/baseCombobox/baseCombobox.css +11 -1
  23. package/src/lightning/baseCombobox/baseCombobox.html +154 -146
  24. package/src/lightning/baseCombobox/baseCombobox.js +122 -46
  25. package/src/lightning/baseCombobox/spinner.slds.css +438 -0
  26. package/src/lightning/baseComboboxItem/baseComboboxItem.js +4 -2
  27. package/src/lightning/baseComboboxItem/inline.css +2 -0
  28. package/src/lightning/breadcrumb/breadcrumb.css +2 -2
  29. package/src/lightning/breadcrumb/breadcrumb.js +4 -2
  30. package/src/lightning/breadcrumb/breadcrumb.slds.css +2 -7
  31. package/src/lightning/breadcrumbs/__docs__/breadcrumbs.md +3 -3
  32. package/src/lightning/breadcrumbs/breadcrumbs.css +2 -2
  33. package/src/lightning/breadcrumbs/breadcrumbs.js +3 -2
  34. package/src/lightning/breadcrumbs/breadcrumbs.slds.css +7 -1
  35. package/src/lightning/button/__docs__/button.md +15 -15
  36. package/src/lightning/button/__examples__/inverse/inverse.css +8 -0
  37. package/src/lightning/button/__examples__/inverse/inverse.html +3 -2
  38. package/src/lightning/button/button.css +2 -0
  39. package/src/lightning/button/button.html +4 -2
  40. package/src/lightning/button/button.js +21 -0
  41. package/src/lightning/button/button.slds.css +527 -0
  42. package/src/lightning/buttonGroup/buttonGroup.css +2 -2
  43. package/src/lightning/buttonGroup/buttonGroup.js +3 -2
  44. package/src/lightning/buttonIcon/__docs__/buttonIcon.md +9 -9
  45. package/src/lightning/buttonIcon/button-icon.slds.css +215 -453
  46. package/src/lightning/buttonIcon/buttonIcon.css +2 -2
  47. package/src/lightning/buttonIcon/buttonIcon.js +4 -0
  48. package/src/lightning/buttonIconStateful/__docs__/buttonIconStateful.md +9 -9
  49. package/src/lightning/buttonIconStateful/button-icon-stateful.slds.css +215 -453
  50. package/src/lightning/buttonIconStateful/buttonIconStateful.css +2 -2
  51. package/src/lightning/buttonMenu/__docs__/buttonMenu.md +8 -8
  52. package/src/lightning/buttonMenu/{dropdown.slds.css → button-menu.slds.css} +853 -217
  53. package/src/lightning/buttonMenu/buttonMenu.css +2 -2
  54. package/src/lightning/buttonMenu/buttonMenu.html +2 -2
  55. package/src/lightning/buttonMenu/buttonMenu.js +10 -14
  56. package/src/lightning/buttonStateful/__docs__/buttonStateful.md +12 -12
  57. package/src/lightning/buttonStateful/button-stateful.slds.css +225 -457
  58. package/src/lightning/buttonStateful/buttonStateful.css +2 -2
  59. package/src/lightning/buttonStateful/buttonStateful.js +3 -2
  60. package/src/lightning/calendar/__examples__/basic/basic.html +7 -0
  61. package/src/lightning/calendar/__examples__/basic/basic.js +3 -0
  62. package/src/lightning/calendar/calendar.css +3 -0
  63. package/src/lightning/calendar/calendar.html +12 -9
  64. package/src/lightning/calendar/calendar.js +18 -2
  65. package/src/lightning/calendar/calendar.slds.css +2048 -0
  66. package/src/lightning/card/__docs__/card.md +3 -3
  67. package/src/lightning/card/card.css +2 -2
  68. package/src/lightning/card/card.js +3 -2
  69. package/src/lightning/card/card.slds.css +141 -88
  70. package/src/lightning/checkboxGroup/__docs__/checkboxGroup.md +2 -2
  71. package/src/lightning/colorPickerCustom/colorPickerCustom.css +2 -2
  72. package/src/lightning/colorPickerCustom/colorPickerCustom.js +3 -2
  73. package/src/lightning/colorPickerPanel/color-picker-panel.slds.css +11 -38
  74. package/src/lightning/colorPickerPanel/colorPickerPanel.css +3 -2
  75. package/src/lightning/colorPickerPanel/colorPickerPanel.js +4 -2
  76. package/src/lightning/colorPickerPanel/popover.slds.css +121 -0
  77. package/src/lightning/combobox/combobox.css +4 -0
  78. package/src/lightning/combobox/combobox.html +31 -29
  79. package/src/lightning/combobox/combobox.js +21 -4
  80. package/src/lightning/combobox/combobox.slds.css +13 -0
  81. package/src/lightning/combobox/form-element.slds.css +281 -0
  82. package/src/lightning/configProvider/defaultConfig.js +2 -1
  83. package/src/lightning/datatable/__docs__/datatable.md +45 -35
  84. package/src/lightning/datatable/autoWidthStrategy.js +3 -0
  85. package/src/lightning/datatable/columnWidthManager.js +1 -1
  86. package/src/lightning/datatable/datatable.js +8 -7
  87. package/src/lightning/datatable/rowSelection.js +7 -4
  88. package/src/lightning/datatable/templates/table/table.html +1 -0
  89. package/src/lightning/datepicker/datepicker.css +3 -0
  90. package/src/lightning/datepicker/datepicker.html +7 -4
  91. package/src/lightning/datepicker/datepicker.js +76 -20
  92. package/src/lightning/datepicker/form-element.slds.css +281 -0
  93. package/src/lightning/datepicker/input-text.slds.css +398 -0
  94. package/src/lightning/datetimepicker/datetimepicker.css +3 -0
  95. package/src/lightning/datetimepicker/datetimepicker.html +9 -3
  96. package/src/lightning/datetimepicker/datetimepicker.js +42 -36
  97. package/src/lightning/datetimepicker/form-element.slds.css +281 -0
  98. package/src/lightning/datetimepicker/input-text.slds.css +398 -0
  99. package/src/lightning/dualListbox/dualListbox.css +2 -2
  100. package/src/lightning/dualListbox/dualListbox.html +3 -3
  101. package/src/lightning/dualListbox/dualListbox.js +47 -13
  102. package/src/lightning/dualListbox/form-element.slds.css +83 -34
  103. package/src/lightning/dualListbox/keyboard.js +20 -1
  104. package/src/lightning/dynamicIcon/dynamicIcon.js +3 -2
  105. package/src/lightning/dynamicIcon/ellie.css +1 -1
  106. package/src/lightning/dynamicIcon/eq.css +1 -1
  107. package/src/lightning/dynamicIcon/score.css +1 -1
  108. package/src/lightning/dynamicIcon/strength.css +1 -1
  109. package/src/lightning/dynamicIcon/trend.css +1 -1
  110. package/src/lightning/dynamicIcon/waffle.css +1 -1
  111. package/src/lightning/formattedRichText/formatted-rich-text.slds.css +230 -0
  112. package/src/lightning/formattedRichText/formattedRichText.css +2 -0
  113. package/src/lightning/formattedRichText/formattedRichText.js +4 -2
  114. package/src/lightning/formattedRichText/linkify.js +2 -2
  115. package/src/lightning/formattedText/formattedText.css +1 -0
  116. package/src/lightning/formattedText/formattedText.js +3 -2
  117. package/src/lightning/helptext/__docs__/helptext.md +2 -2
  118. package/src/lightning/helptext/form-element.slds.css +83 -34
  119. package/src/lightning/helptext/help-text.slds.css +215 -453
  120. package/src/lightning/helptext/helptext.css +2 -2
  121. package/src/lightning/helptext/helptext.js +3 -2
  122. package/src/lightning/i18nCldrOptions/README.md +5 -0
  123. package/src/lightning/i18nService/README.md +5 -0
  124. package/src/lightning/icon/__docs__/icon.md +5 -5
  125. package/src/lightning/icon/icon.css +2 -2
  126. package/src/lightning/icon/icon.js +21 -2
  127. package/src/lightning/icon/icon.slds.css +29 -17
  128. package/src/lightning/icon/iconColors.js +1 -0
  129. package/src/lightning/iconUtils/iconUtils.js +0 -12
  130. package/src/lightning/iconUtils/polyfill.js +5 -90
  131. package/src/lightning/input/__docs__/input.md +7 -7
  132. package/src/lightning/input/__examples__/checkboxbutton/checkboxbutton.css +6 -0
  133. package/src/lightning/input/__examples__/checkboxbutton/checkboxbutton.html +2 -1
  134. package/src/lightning/input/__examples__/checkboxbutton/checkboxbutton.js +1 -1
  135. package/src/lightning/input/__examples__/number/number.html +0 -5
  136. package/src/lightning/input/__examples__/text/text.html +0 -1
  137. package/src/lightning/input/form-element.slds.css +281 -0
  138. package/src/lightning/input/input.css +2 -3
  139. package/src/lightning/input/input.html +154 -244
  140. package/src/lightning/input/input.js +306 -595
  141. package/src/lightning/inputAddress/__docs__/inputAddress.md +3 -3
  142. package/src/lightning/inputUtils/inputUtils.js +15 -20
  143. package/src/lightning/inputUtils/normalize.js +7 -0
  144. package/src/lightning/{input/numberUtil.js → inputUtils/number.js} +1 -1
  145. package/src/lightning/inputUtils/utils.js +18 -0
  146. package/src/lightning/internationalizationLibrary/README.md +24 -0
  147. package/src/lightning/internationalizationLibrary/utils.js +4 -1
  148. package/src/lightning/layout/__docs__/layout.md +1 -1
  149. package/src/lightning/layout/__examples__/simple/simple.css +1 -1
  150. package/src/lightning/layout/layout.css +5 -1
  151. package/src/lightning/layout/layout.js +4 -2
  152. package/src/lightning/layoutItem/__examples__/alignmentBump/alignmentBump.css +1 -1
  153. package/src/lightning/layoutItem/__examples__/sizePerDevice/sizePerDevice.css +0 -1
  154. package/src/lightning/layoutItem/layoutItem.css +5 -0
  155. package/src/lightning/layoutItem/layoutItem.js +4 -2
  156. package/src/lightning/menuDivider/menu-divider.slds.css +15 -0
  157. package/src/lightning/menuDivider/menuDivider.css +3 -0
  158. package/src/lightning/menuDivider/menuDivider.html +1 -1
  159. package/src/lightning/menuDivider/menuDivider.js +4 -2
  160. package/src/lightning/menuItem/menu-item.slds.css +140 -0
  161. package/src/lightning/menuItem/menuItem.css +3 -0
  162. package/src/lightning/menuItem/menuItem.html +43 -41
  163. package/src/lightning/menuItem/menuItem.js +4 -4
  164. package/src/lightning/menuSubheader/menu-subheader.slds.css +22 -0
  165. package/src/lightning/menuSubheader/menuSubheader.css +3 -0
  166. package/src/lightning/menuSubheader/menuSubheader.html +3 -1
  167. package/src/lightning/menuSubheader/menuSubheader.js +4 -6
  168. package/src/lightning/modal/__docs__/modal.md +3 -1
  169. package/src/lightning/modal/__modalUtils__/modalContainerTestConstants.js +267 -0
  170. package/src/lightning/modal/__modalUtils__/modalContainerTestMethods.js +1165 -0
  171. package/src/lightning/modal/__modalUtils__/modalContainerTestMockData.js +131 -0
  172. package/src/lightning/modal/modal.js +1 -1
  173. package/src/lightning/modalBody/__docs__/modalBody.md +9 -9
  174. package/src/lightning/modalFooter/__docs__/modalFooter.md +9 -9
  175. package/src/lightning/modalHeader/__docs__/modalHeader.md +9 -9
  176. package/src/lightning/overlayContainer/overlayContainer.js +4 -2
  177. package/src/lightning/pill/__docs__/pill.md +3 -3
  178. package/src/lightning/pill/avatar.slds.css +272 -0
  179. package/src/lightning/pill/link.css +3 -0
  180. package/src/lightning/pill/link.html +1 -1
  181. package/src/lightning/pill/pill.js +29 -9
  182. package/src/lightning/pill/pill.slds.css +168 -0
  183. package/src/lightning/pill/plain.css +3 -0
  184. package/src/lightning/pill/plain.html +1 -1
  185. package/src/lightning/pill/plainLink.css +3 -0
  186. package/src/lightning/pill/plainLink.html +1 -1
  187. package/src/lightning/pillContainer/__docs__/pillContainer.md +14 -14
  188. package/src/lightning/pillContainer/barePillContainer.css +3 -0
  189. package/src/lightning/pillContainer/barePillContainer.html +1 -2
  190. package/src/lightning/pillContainer/listbox.slds.css +267 -0
  191. package/src/lightning/pillContainer/pill-container.slds.css +22 -0
  192. package/src/lightning/pillContainer/pill.slds.css +168 -0
  193. package/src/lightning/pillContainer/pillContainer.js +7 -3
  194. package/src/lightning/pillContainer/standardPillContainer.css +4 -0
  195. package/src/lightning/pillContainer/standardPillContainer.html +2 -2
  196. package/src/lightning/popup/popover.slds.css +119 -119
  197. package/src/lightning/popup/popup.css +1 -2
  198. package/src/lightning/popup/popup.js +3 -2
  199. package/src/lightning/positionLibrary/elementProxy.js +7 -2
  200. package/src/lightning/positionLibrary/util.js +8 -0
  201. package/src/lightning/primitiveBubble/primitiveBubble.css +2 -2
  202. package/src/lightning/primitiveBubble/primitiveBubble.js +4 -2
  203. package/src/lightning/primitiveButton/primitiveButton.js +5 -4
  204. package/src/lightning/primitiveCellFactory/cellWithStandardLayout.html +29 -21
  205. package/src/lightning/primitiveCellFactory/primitiveCellFactory.js +4 -0
  206. package/src/lightning/primitiveColorpickerButton/color-picker-button.slds.css +31 -19
  207. package/src/lightning/primitiveColorpickerButton/primitiveColorpickerButton.css +2 -2
  208. package/src/lightning/primitiveColorpickerButton/primitiveColorpickerButton.js +5 -3
  209. package/src/lightning/primitiveIcon/icon.slds.css +209 -0
  210. package/src/lightning/primitiveIcon/primitiveIcon.css +2 -1
  211. package/src/lightning/primitiveIcon/primitiveIcon.html +1 -1
  212. package/src/lightning/primitiveIcon/primitiveIcon.js +26 -16
  213. package/src/lightning/progressStep/progressStep.js +10 -13
  214. package/src/lightning/radioGroup/__docs__/radioGroup.md +4 -4
  215. package/src/lightning/radioGroup/radioGroup.css +2 -1
  216. package/src/lightning/radioGroup/radioGroup.js +4 -2
  217. package/src/lightning/select/__docs__/select.md +2 -2
  218. package/src/lightning/select/form-element.slds.css +83 -34
  219. package/src/lightning/select/select.css +2 -2
  220. package/src/lightning/select/select.js +4 -2
  221. package/src/lightning/select/select.slds.css +86 -34
  222. package/src/lightning/shadowBaseClassPrivate/shadowBaseClassPrivate.js +1 -1
  223. package/src/lightning/sldsCommon/sldsCommon.css +251 -89
  224. package/src/lightning/sldsUtilsAlignment/sldsUtilsAlignment.css +10 -0
  225. package/src/lightning/sldsUtilsAlignment/sldsUtilsAlignment.js-meta.xml +4 -0
  226. package/src/lightning/sldsUtilsBorders/sldsUtilsBorders.css +18 -0
  227. package/src/lightning/sldsUtilsBorders/sldsUtilsBorders.js-meta.xml +4 -0
  228. package/src/lightning/sldsUtilsBox/sldsUtilsBox.css +24 -0
  229. package/src/lightning/sldsUtilsBox/sldsUtilsBox.js-meta.xml +4 -0
  230. package/src/lightning/sldsUtilsFloats/sldsUtilsFloats.css +20 -0
  231. package/src/lightning/sldsUtilsFloats/sldsUtilsFloats.js-meta.xml +4 -0
  232. package/src/lightning/sldsUtilsGrid/sldsUtilsGrid.css +259 -0
  233. package/src/lightning/sldsUtilsGrid/sldsUtilsGrid.js-meta.xml +4 -0
  234. package/src/lightning/sldsUtilsHyphenation/sldsUtilsHyphenation.css +8 -0
  235. package/src/lightning/sldsUtilsHyphenation/sldsUtilsHyphenation.js-meta.xml +4 -0
  236. package/src/lightning/sldsUtilsLineClamp/sldsUtilsLineClamp.css +57 -0
  237. package/src/lightning/sldsUtilsLineClamp/sldsUtilsLineClamp.js-meta.xml +4 -0
  238. package/src/lightning/sldsUtilsMargin/sldsUtilsMargin.css +313 -0
  239. package/src/lightning/sldsUtilsMargin/sldsUtilsMargin.js-meta.xml +4 -0
  240. package/src/lightning/sldsUtilsPadding/sldsUtilsPadding.css +308 -0
  241. package/src/lightning/sldsUtilsPadding/sldsUtilsPadding.js-meta.xml +4 -0
  242. package/src/lightning/sldsUtilsPosition/sldsUtilsPosition.css +18 -0
  243. package/src/lightning/sldsUtilsPosition/sldsUtilsPosition.js-meta.xml +4 -0
  244. package/src/lightning/sldsUtilsSizing/sldsUtilsSizing.css +1408 -0
  245. package/src/lightning/sldsUtilsSizing/sldsUtilsSizing.js-meta.xml +4 -0
  246. package/src/lightning/sldsUtilsThemes/sldsUtilsThemes.css +295 -0
  247. package/src/lightning/sldsUtilsThemes/sldsUtilsThemes.js-meta.xml +4 -0
  248. package/src/lightning/sldsUtilsTruncation/sldsUtilsTruncation.css +14 -0
  249. package/src/lightning/sldsUtilsTruncation/sldsUtilsTruncation.js-meta.xml +4 -0
  250. package/src/lightning/slider/__docs__/slider.md +2 -2
  251. package/src/lightning/spinner/spinner.css +2 -2
  252. package/src/lightning/spinner/spinner.js +4 -2
  253. package/src/lightning/tabBar/tab-bar.slds.css +334 -0
  254. package/src/lightning/tabBar/tabBar.css +2 -0
  255. package/src/lightning/tabBar/tabBar.html +4 -3
  256. package/src/lightning/tabBar/tabBar.js +30 -3
  257. package/src/lightning/tabset/__docs__/tabset.md +2 -2
  258. package/src/lightning/tabset/tabset.html +5 -4
  259. package/src/lightning/tabset/tabset.js +29 -11
  260. package/src/lightning/textarea/__docs__/textarea.md +2 -2
  261. package/src/lightning/timepicker/form-element.slds.css +281 -0
  262. package/src/lightning/timepicker/timepicker.css +3 -0
  263. package/src/lightning/timepicker/timepicker.html +5 -1
  264. package/src/lightning/timepicker/timepicker.js +22 -17
  265. package/src/lightning/timepicker/timepicker.slds.css +18 -0
  266. package/src/lightning/toast/toast.js-meta.xml +2 -0
  267. package/src/lightning/toastContainer/__docs__/toastContainer.md +14 -34
  268. package/src/lightning/toastContainer/toastContainer.js +10 -15
  269. package/src/lightning/tooltipLibrary/tooltipLibrary.js +32 -23
  270. package/src/lightning/utilsPrivate/browser.js +5 -3
  271. package/src/lightning/utilsPrivate/os.js +6 -4
  272. package/src/lightning/utilsPrivate/ssr.js +4 -0
  273. package/src/lightning/utilsPrivate/utilsPrivate.js +2 -0
  274. package/src/lightning/verticalNavigation/verticalNavigation.css +2 -1
  275. package/src/lightning/verticalNavigation/verticalNavigation.js +3 -2
  276. package/src/lightning/verticalNavigationSection/verticalNavigationSection.css +2 -1
  277. package/src/lightning/verticalNavigationSection/verticalNavigationSection.js +3 -2
  278. package/src/lightning/accordion/__perf__DISABLED/accordion-perf-utils.js +0 -76
  279. package/src/lightning/accordion/__perf__DISABLED/accordion10Multiple25SectionEach.perf.js +0 -57
  280. package/src/lightning/accordion/__perf__DISABLED/accordion10Simple25SectionEach.perf.js +0 -37
  281. package/src/lightning/accordion/__perf__DISABLED/accordionMultiple50Section.perf.js +0 -45
  282. package/src/lightning/accordion/__perf__DISABLED/accordionSimple50Section.perf.js +0 -35
  283. package/src/lightning/accordion/__perf__DISABLED/container/container.html +0 -15
  284. package/src/lightning/accordion/__perf__DISABLED/container/container.js +0 -7
  285. package/src/lightning/iconUtils/isIframeInEdge.js +0 -7
  286. package/src/lightning/iconUtils/supportsSvg.js +0 -16
  287. package/src/lightning/input/input-checkbox.slds.css +0 -404
  288. package/src/lightning/input/input-text.slds.css +0 -287
  289. package/src/lightning/input/normalize.js +0 -6
  290. package/src/lightning/input/selection.js +0 -131
  291. package/src/lightning/positionLibrary/__component__/positionLibraryBounding.spec.js +0 -319
  292. package/src/lightning/positionLibrary/__component__/x/bounding/bounding.css +0 -16
  293. package/src/lightning/positionLibrary/__component__/x/bounding/bounding.html +0 -36
  294. package/src/lightning/positionLibrary/__component__/x/bounding/bounding.js +0 -122
  295. /package/src/lightning/{baseCombobox → baseComboboxItem}/listbox.slds.css +0 -0
  296. /package/src/lightning/formattedRichText/{__examples__disabled → __examples__}/basic/basic.html +0 -0
  297. /package/src/lightning/formattedRichText/{__examples__disabled → __examples__}/basic/basic.js +0 -0
  298. /package/src/lightning/{input/emailUtil.js → inputUtils/email.js} +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
+ };