dimsum-e2e-tests 3.80.0-next.4 → 3.80.0-next.40

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 (134) hide show
  1. package/CHANGELOG.md +164 -0
  2. package/DSStorybook/baseline.js +0 -186
  3. package/ds-accordion/DSAccordion.visual.spec.js +0 -14
  4. package/ds-accordion/DSAccordionCO.js +0 -2
  5. package/ds-apppicker/aria-disabled/DSAppPicker.aria-disabled.visual.spec.js +14 -1
  6. package/ds-apppicker/basic/DSAppPicker.basic.visual.spec.js +12 -7
  7. package/ds-banner/DSBanner.text-resize.visual.spec.js +2 -0
  8. package/ds-banner/DSBanner.text-wrapping.visual.spec.js +3 -6
  9. package/ds-banner/DSBanner.visual.spec.js +7 -8
  10. package/ds-banner/DSBannerCO.js +189 -4
  11. package/ds-banner/aria-and-role/DSBanner.aria-and-role.func.spec.js +113 -2
  12. package/ds-banner/ids/DSBanner.ids.func.spec.js +66 -0
  13. package/ds-banner/live-region-lang-dir/DSBanner.live-region-lang-dir.func.spec.js +54 -0
  14. package/ds-banner/slide-animation/DSBanner.slide-animation.func.spec.js +105 -0
  15. package/ds-banner/slots/DSBanner.slots.axe-core.func.spec.js +24 -0
  16. package/ds-banner/slots/DSBanner.slots.func.spec.js +13 -0
  17. package/ds-banner/slots/DSBanner.slots.visual.spec.js +3 -2
  18. package/ds-banner/title-element/DSBanner.title-element.func.spec.js +29 -0
  19. package/ds-chip/DSChip.axe-core.func.spec.js +0 -17
  20. package/ds-chip/DSChip.visual.spec.js +0 -22
  21. package/ds-chip/DSChipCO.js +0 -6
  22. package/ds-fast-list/DSFastListCO.js +37 -0
  23. package/ds-fast-list/basic/DSFastList.basic.axe-core.func.spec.js +28 -0
  24. package/ds-fast-list/basic/DSFastList.basic.visual.spec.js +23 -0
  25. package/ds-fast-list/scroll-to-index/DSFastList.scroll-to-index.func.spec.js +45 -0
  26. package/ds-indeterminate-progress-indicator/DSIndeterminateProgressIndicatorCO.js +4 -0
  27. package/ds-indeterminate-progress-indicator/accessible-name/DSIndeterminateProgressIndicator.accessible-name.func.spec.js +51 -0
  28. package/ds-leftnavigation/LeftNavigation.fullFeatures.axe-core.func.spec.js +37 -0
  29. package/ds-leftnavigation/LeftNavigation.isSkeleton.axe-core.func.spec.js +23 -0
  30. package/ds-leftnavigation/LeftNavigation.keepingItems.axe-core.func.spec.js +34 -0
  31. package/ds-leftnavigation/LeftNavigation.loadingIndicator.axe-core.func.spec.js +41 -0
  32. package/ds-leftnavigation/LeftNavigation.notificationBadge.axe-core.func.spec.js +21 -0
  33. package/ds-leftnavigation/LeftNavigation.searchBar.axe-core.func.spec.js +26 -0
  34. package/ds-leftnavigation/LeftNavigation.showCase.axe-core.func.spec.js +23 -0
  35. package/ds-leftnavigation/LeftNavigation.tooltip.axe-core.func.spec.js +28 -0
  36. package/ds-leftnavigation/LeftNavigation.wrappedText.axe-core.func.spec.js +27 -0
  37. package/ds-modal-slide/inside-dialog/ModalSlide.insideDialog.func.spec.js +45 -0
  38. package/ds-panel-header/DSPanelHeader.func.spec.js +24 -0
  39. package/ds-panel-header/DSPanelHeaderCO.js +17 -0
  40. package/ds-pills-v2/DSPillsV2CO.js +53 -1
  41. package/ds-pills-v2/pitfalls/DSPills.inputPill.func.spec.js +26 -0
  42. package/ds-pills-v2/pitfalls/DSPills.inputWithFormItemBlockURL.axe-core.func.spec.js +38 -0
  43. package/ds-pills-v2/pitfalls/DSPills.inputWithFormItemBlockURL.func.spec.js +72 -0
  44. package/ds-progress-indicator/DSProgressIndicator.axe-core.func.spec.js +8 -5
  45. package/ds-progress-indicator/DSProgressIndicator.func.spec.js +31 -4
  46. package/ds-progress-indicator/DSProgressIndicator.visual.spec.js +14 -6
  47. package/ds-progress-indicator/DSProgressIndicatorCO.js +87 -9
  48. package/ds-progress-indicator/aria-contract/DSProgressIndicator.aria-contract.func.spec.js +104 -0
  49. package/ds-progress-indicator/slots/DSProgressIndicator.slots.axe-core.func.spec.js +31 -0
  50. package/ds-progress-indicator/slots/DSProgressIndicator.slots.func.spec.js +87 -0
  51. package/ds-progress-indicator/slots/DSProgressIndicator.slots.visual.spec.js +27 -0
  52. package/ds-progress-indicator/variant/DSProgressIndicator.single-bar-variant.func.spec.js +44 -0
  53. package/ds-progress-indicator/variant/DSProgressIndicator.single-bar-variant.visual.spec.js +19 -0
  54. package/ds-ribbon/DSRibbonCO.js +21 -0
  55. package/ds-ribbon/keyboard-accessible-row/DSRibbon.keyboard-accessible-row.visual.spec.js +40 -0
  56. package/ds-ribbon/wrap-columns/DSRibbon.wrap-columns.visual.spec.js +18 -0
  57. package/ds-shuttle-v2/load-more/DSShuttleV2.load-more.keyboard.func.spec.js +46 -17
  58. package/ds-slider-v2/DSSliderV2.axe-core.func.spec.js +21 -0
  59. package/ds-slider-v2/DSSliderV2.visual.spec.js +19 -1
  60. package/ds-slider-v2/DSSliderV2CO.js +6 -1
  61. package/ds-slider-v2/has-error/DSSliderV2.has-error.visual.spec.js +44 -0
  62. package/ds-slider-v2/interaction-states/DSSliderV2.interaction-states.visual.spec.js +53 -0
  63. package/ds-virtuallist/DSVirtualList.visual.spec.js +4 -5
  64. package/ds-virtuallist/DSVirtualListCO.js +28 -9
  65. package/ds-virtuallist/basic/DSVirtualList.basic.axe-core.func.spec.js +28 -0
  66. package/ds-virtuallist/scroll-to-index/DSVirtualList.scroll-to-index.func.spec.js +45 -0
  67. package/package.json +149 -149
  68. package/paths.js +3 -49
  69. package/ds-leftnavigation/LeftNavigation.axe-core.func.spec.js +0 -172
  70. package/ds-mobile/Accordion/DSAccordion.visual.spec.js +0 -33
  71. package/ds-mobile/Accordion/DSAccordionCO.js +0 -37
  72. package/ds-mobile/CategoryBox/CategoryBox.visual.spec.js +0 -18
  73. package/ds-mobile/CategoryBox/CategoryBoxCO.js +0 -17
  74. package/ds-mobile/CollectionBox/CollectionBox.visual.spec.js +0 -16
  75. package/ds-mobile/CollectionBox/CollectionBoxCO.js +0 -15
  76. package/ds-mobile/GlobalHeader/DSGlobalHeader.visual.spec.js +0 -55
  77. package/ds-mobile/GlobalHeader/DSGlobalHeaderCO.js +0 -36
  78. package/ds-mobile/GroupBox/GroupBox.visual.spec.js +0 -15
  79. package/ds-mobile/GroupBox/GroupBoxCO.js +0 -15
  80. package/ds-mobile/InfiniteLoader/InfiniteLoader.func.spec.js +0 -90
  81. package/ds-mobile/InfiniteLoader/InfiniteLoaderCO.js +0 -29
  82. package/ds-mobile/MobileActionToolbar/MobileActionToolbar.visual.spec.js +0 -39
  83. package/ds-mobile/MobileActionToolbar/MobileActionToolbarCO.js +0 -19
  84. package/ds-mobile/MobileBanner/MobileBanner.visual.spec.js +0 -23
  85. package/ds-mobile/MobileBanner/MobileBannerCO.js +0 -23
  86. package/ds-mobile/MobileCard/Card.func.spec.js +0 -38
  87. package/ds-mobile/MobileCard/Card.visual.spec.js +0 -103
  88. package/ds-mobile/MobileCard/MobileCardCO.js +0 -42
  89. package/ds-mobile/MobileContextMenu/MobileContextMenu.func.spec.js +0 -88
  90. package/ds-mobile/MobileContextMenu/MobileContextMenu.visual.spec.js +0 -31
  91. package/ds-mobile/MobileContextMenu/MobileContextMenuCO.js +0 -72
  92. package/ds-mobile/MobileDatePicker/MobileDatePicker.func.spec.js +0 -15
  93. package/ds-mobile/MobileDatePicker/MobileDatePicker.visual.spec.js +0 -15
  94. package/ds-mobile/MobileDatePicker/MobileDatePickerCO.js +0 -35
  95. package/ds-mobile/MobileDropdownMenu/MobileDropdownMenu.func.spec.js +0 -38
  96. package/ds-mobile/MobileDropdownMenu/MobileDropdownMenu.visual.spec.js +0 -16
  97. package/ds-mobile/MobileDropdownMenu/MobileDropdownMenuCO.js +0 -37
  98. package/ds-mobile/MobileEmptyState/MobileEmptyState.visual.spec.js +0 -15
  99. package/ds-mobile/MobileEmptyState/MobileEmptyStateCO.js +0 -14
  100. package/ds-mobile/MobileFilterbar/MobileFilterbar.visual.spec.js +0 -39
  101. package/ds-mobile/MobileFilterbar/MobileFilterbarCO.js +0 -19
  102. package/ds-mobile/MobileFooter/MobileFooter.func.spec.js +0 -26
  103. package/ds-mobile/MobileFooter/MobileFooter.visual.spec.js +0 -51
  104. package/ds-mobile/MobileFooter/MobileFooterCO.js +0 -27
  105. package/ds-mobile/MobileListItem/MobileListItem.visual.spec.js +0 -56
  106. package/ds-mobile/MobileListItem/MobileListItemCO.js +0 -19
  107. package/ds-mobile/MobileLoadingPage/MobileLoadingPage.visual.spec.js +0 -37
  108. package/ds-mobile/MobileLoadingPage/MobileLoadingPageCO.js +0 -23
  109. package/ds-mobile/MobilePageHeader/MobilePageHeader.func.spec.js +0 -22
  110. package/ds-mobile/MobilePageHeader/MobilePageHeader.visual.spec.js +0 -135
  111. package/ds-mobile/MobilePageHeader/MobilePageHeaderCO.js +0 -67
  112. package/ds-mobile/MobileSelectList/MobileSelectList.visual.spec.js +0 -56
  113. package/ds-mobile/MobileSelectList/MobileSelectListCO.js +0 -21
  114. package/ds-mobile/MobileSeparator/MobileSeparator.visual.spec.js +0 -28
  115. package/ds-mobile/MobileSeparator/MobileSeparatorCO.js +0 -17
  116. package/ds-mobile/MobileTimePicker/MobileTimePickerCO.js +0 -41
  117. package/ds-mobile/Modal/MobileModal.visual.spec.js +0 -56
  118. package/ds-mobile/Modal/MobileModalCO.js +0 -28
  119. package/ds-mobile/PageFilter/PageFilter.visual.spec.js +0 -46
  120. package/ds-mobile/PageFilter/PageFilterCO.js +0 -19
  121. package/ds-mobile/PageForm/PageForm.visual.spec.js +0 -14
  122. package/ds-mobile/PageForm/PageFormCO.js +0 -11
  123. package/ds-mobile/PageList/PageList.visual.spec.js +0 -31
  124. package/ds-mobile/PageList/PageListCO.js +0 -19
  125. package/ds-mobile/PageSummary/PageSummary.visual.spec.js +0 -20
  126. package/ds-mobile/PageSummary/PageSummaryCO.js +0 -11
  127. package/ds-mobile/SwipeToRefresh/SwipeToRefresh.func.spec.js +0 -26
  128. package/ds-mobile/SwipeToRefresh/SwipeToRefreshCO.js +0 -20
  129. package/ds-mobile/Tabs/MobileTabs.visual.spec.js +0 -91
  130. package/ds-mobile/Tabs/TabsCO.js +0 -37
  131. package/ds-mobile/Tooltip/MobileTooltip.func.spec.js +0 -17
  132. package/ds-mobile/Tooltip/MobileTooltip.visual.spec.js +0 -16
  133. package/ds-mobile/Tooltip/TooltipCO.js +0 -23
  134. package/ds-mobile/helpers.js +0 -7
@@ -0,0 +1,45 @@
1
+ import { Key } from 'webdriverio';
2
+ import ModalSlideCO from '../ModalSlideCO';
3
+ import DSDialogCO from '../../ds-dialog/DSDialogCO';
4
+
5
+ if (!browser.capabilities['ice:options'].isPhone && !browser.capabilities['ice:options'].isTablet) {
6
+ describe('PUI-19268 - ModalSlide:: inside Dialog, focus and Escape', () => {
7
+ beforeEach(async () => {
8
+ const errorOnGo = await ModalSlideCO.insideDialogURL.go();
9
+ if (errorOnGo) throw errorOnGo;
10
+ await ModalSlideCO.openDialog();
11
+ await ModalSlideCO.openModalSlide();
12
+ });
13
+ it('01: Cancel should close only the slide and focus the button that opened it', async () => {
14
+ await (await ModalSlideCO.getRejectedBtn()).click();
15
+ await (await ModalSlideCO.getModalSlide()).waitForDisplayed({ reverse: true });
16
+ await expect(await DSDialogCO.getDialog()).toBeDisplayed();
17
+ await expect(await ModalSlideCO.getOpenModalSlideBtn()).toBeFocused();
18
+ });
19
+ it('02: Confirm should close only the slide and focus the button that opened it', async () => {
20
+ await (await ModalSlideCO.getModalButton()).click();
21
+ await (await ModalSlideCO.getModalSlide()).waitForDisplayed({ reverse: true });
22
+ await expect(await DSDialogCO.getDialog()).toBeDisplayed();
23
+ await expect(await ModalSlideCO.getOpenModalSlideBtn()).toBeFocused();
24
+ });
25
+ it('03: Escape should close only the slide and focus the button that opened the slide', async () => {
26
+ // the story moves the focus into the slide 500ms after it opens
27
+ await browser.waitUntil(async () => (await $('[data-testid="back-id"]')).isFocused(), {
28
+ timeoutMsg: 'the focus should move into the slide',
29
+ });
30
+ await browser.keys(Key.Escape);
31
+ await (await ModalSlideCO.getModalSlide()).waitForDisplayed({ reverse: true });
32
+ await expect(await DSDialogCO.getDialog()).toBeDisplayed();
33
+ await expect(await ModalSlideCO.getOpenModalSlideBtn()).toBeFocused();
34
+ });
35
+ it('04: a second Escape, with the slide already closed, should close the dialog', async () => {
36
+ await browser.waitUntil(async () => (await $('[data-testid="back-id"]')).isFocused(), {
37
+ timeoutMsg: 'the focus should move into the slide',
38
+ });
39
+ await browser.keys(Key.Escape);
40
+ await (await ModalSlideCO.getModalSlide()).waitForDisplayed({ reverse: true });
41
+ await browser.keys(Key.Escape);
42
+ await (await DSDialogCO.getDialog()).waitForDisplayed({ reverse: true });
43
+ });
44
+ });
45
+ }
@@ -0,0 +1,24 @@
1
+ import DSPanelHeaderCO from './DSPanelHeaderCO';
2
+ /* this is the syntax for only enabling test for desktop only
3
+ if (!browser.capabilities['ice:options'].isPhone && !browser.capabilities['ice:options'].isTablet) {
4
+ describe('PUI-XXXX - PanelHeader:: example test - data-testid exists', () => {
5
+ before('loading page', async () => {
6
+ // your code goes here
7
+ });
8
+ it('01: wrapper should be displayed', async () => {
9
+ // your code goes here
10
+ });
11
+
12
+ });
13
+ }
14
+ */
15
+ describe('PUI-XXXX - PanelHeader:: example test - data-testid exists', () => {
16
+ before('loading page', async () => {
17
+ const errorOnGo = await DSPanelHeaderCO.basic.go();
18
+ if (errorOnGo) throw errorOnGo;
19
+ });
20
+ it('01: wrapper should be displayed', async () => {
21
+ const wrapper = await DSPanelHeaderCO.getWrapper();
22
+ await expect(wrapper).toBeDisplayedInViewport();
23
+ });
24
+ });
@@ -0,0 +1,17 @@
1
+ import { PANEL_HEADER_DATA_TESTID } from '@elliemae/ds-panel-header';
2
+ import { PATH_PANEL_HEADER } from '../../environments/storybook/paths';
3
+ import { PageObject, Urlbuilder } from '../helpers';
4
+
5
+ export default class DSPanelHeader extends PageObject {
6
+ static basic = new Urlbuilder(PATH_PANEL_HEADER, 'basic');
7
+
8
+ // Selectors
9
+ static async getWrapper() {
10
+ return $(`[data-testid="${PANEL_HEADER_DATA_TESTID.ROOT}"]`);
11
+ }
12
+
13
+ // Snapshots
14
+ static snapshotPath(example = 'basic') {
15
+ return PageObject.getSnapshotPathBuilder('DSPanelHeader', example, 'ds-panel-header');
16
+ }
17
+ }
@@ -1,7 +1,21 @@
1
- import { PATH_E2E_PILLS_V2 } from '../paths';
1
+ import { PATH_E2E_PILLS_V2, PATH_E2E_PILLS_V2_PITFALLS } from '../paths';
2
2
  import { PageObject, Urlbuilder, getElementByIndex } from '../helpers';
3
3
 
4
4
  export default class DSPillsV2CO extends PageObject {
5
+ /**
6
+ * Every id in the InputWithFormItemBlockTest story is derived by DSFormLayoutBlockItem from the
7
+ * `inputID` it is given. Derived once here so neither the selectors below nor the spec rebuild
8
+ * the `_feedback_message` / `_error_message` suffixes by hand.
9
+ */
10
+ static IDS = {
11
+ input: 'normal',
12
+ feedback: 'normal_feedback_message',
13
+ error: 'normal_error_message',
14
+ };
15
+
16
+ /** What the input's aria-describedby must equal, in the order the story supplies the messages. */
17
+ static DESCRIBED_BY = `${this.IDS.feedback} ${this.IDS.error}`;
18
+
5
19
  // Desktop
6
20
  static removableURL = new Urlbuilder(PATH_E2E_PILLS_V2, 'removable-pill-test');
7
21
 
@@ -27,6 +41,15 @@ export default class DSPillsV2CO extends PageObject {
27
41
 
28
42
  static multiSelectMB = new Urlbuilder(PATH_E2E_PILLS_V2, 'multi-select-menu-button-test');
29
43
 
44
+ // Pitfalls
45
+ static inputWithFormItemBlockURL = new Urlbuilder(PATH_E2E_PILLS_V2_PITFALLS, 'input-with-form-item-block-test');
46
+
47
+ // Distinct from `inputURL` above: same story name, different path. Both are hand-copies of the
48
+ // Input Pill pitfall story's 12 pills; this one is the Pitfalls-scoped twin used by the attribute
49
+ // spec, `inputURL` is the older copy carrying the axe (PUI-11496) and Percy (PUI-8356) baselines.
50
+ // The E2E copy renders PendingReviewDisclaimer; the canonical component pitfall story additionally renders InputPillLabelRequirementDisclaimer.
51
+ static inputPillPitfallURL = new Urlbuilder(PATH_E2E_PILLS_V2_PITFALLS, 'input-pill-test');
52
+
30
53
  static getUrl(component = 'basic') {
31
54
  return PageObject.getUrl(PATH_E2E_PILLS_V2, component);
32
55
  }
@@ -93,6 +116,35 @@ export default class DSPillsV2CO extends PageObject {
93
116
  return $('[data-testid="dropdown-menu-listbox"]');
94
117
  }
95
118
 
119
+ // --- InputWithFormItemBlockTest: label / error association selectors ---
120
+
121
+ /** The pill's inner <input>, reached by the id the story overrides onto it. */
122
+ static async getAssociatedInput() {
123
+ return $(`#${this.IDS.input}`);
124
+ }
125
+
126
+ /** `$$` on purpose — the count is what proves the override did not duplicate the id. */
127
+ static async getNodesWithInputId() {
128
+ return $$(`[id="${this.IDS.input}"]`);
129
+ }
130
+
131
+ /** The DSFormLayoutBlockItem label, which must point at the input via `for`. */
132
+ static async getAssociatedInputLabel() {
133
+ return $(`label[for="${this.IDS.input}"]`);
134
+ }
135
+
136
+ static async getFeedbackMessage() {
137
+ return $(`#${this.IDS.feedback}`);
138
+ }
139
+
140
+ static async getErrorMessage() {
141
+ return $(`#${this.IDS.error}`);
142
+ }
143
+
144
+ static async getClearIcon() {
145
+ return $('[data-testid="ds-pill-clear-icon"]');
146
+ }
147
+
96
148
  // Snapshots
97
149
  static snapshotPath(example = 'basic') {
98
150
  return PageObject.getSnapshotPathBuilder('Pills', example);
@@ -0,0 +1,26 @@
1
+ import DSPillsV2CO from '../DSPillsV2CO';
2
+
3
+ if (
4
+ (!browser.capabilities['ice:options'].isPhone &&
5
+ !browser.capabilities['ice:options'].isTablet &&
6
+ browser.capabilities.browserName === 'chrome') ||
7
+ browser.capabilities.browserName === 'Chrome'
8
+ ) {
9
+ describe('PUI-19148 - DSPillsV2:: input pill attribute contract - Func', () => {
10
+ before('loading page', async () => {
11
+ const errorOnGo = await DSPillsV2CO.inputPillPitfallURL.go();
12
+ if (errorOnGo) throw errorOnGo;
13
+ });
14
+
15
+ it('01: should render the input with the attributes', async () => {
16
+ const input = await DSPillsV2CO.getInputByIndex(0);
17
+ await input.waitForDisplayed();
18
+ await expect(await input.getTagName()).toBe('input');
19
+ await expect(input).toHaveAttribute('data-testid', 'ds-pill-input');
20
+ const generatedId = await input.getAttribute('id');
21
+ await expect(generatedId).toMatch(/^ds-pill-/);
22
+ await expect(input).not.toHaveAttribute('aria-describedby');
23
+ await expect(input).toHaveAttribute('aria-invalid', 'false');
24
+ });
25
+ });
26
+ }
@@ -0,0 +1,38 @@
1
+ import DSPillsV2CO from '../DSPillsV2CO';
2
+ import { axeCoreCheck, type } from '../../helpers';
3
+
4
+ // Accessibility scan for the fix introduced in PUI-18617 ([PillsV2] Pitfalls enhancements, item 2).
5
+ //
6
+ // Note on what this does and does not prove: axe has no rule that requires an input to be described by
7
+ // an error message, so a clean scan here is a regression guard against the association *breaking the
8
+ // markup* (orphaned aria-describedby ids, duplicate ids, an unlabelled input) — not evidence that the
9
+ // label and error are announced. The announcement itself is covered by the functional spec and by
10
+ // manual screen reader verification on the ticket.
11
+ if (
12
+ (!browser.capabilities['ice:options'].isPhone &&
13
+ !browser.capabilities['ice:options'].isTablet &&
14
+ browser.capabilities.browserName === 'chrome') ||
15
+ browser.capabilities.browserName === 'Chrome'
16
+ ) {
17
+ describe('PUI-19150 - DSPillsV2:: input pill label and error association - Axe-Core', () => {
18
+ before('loading page', async () => {
19
+ const errorOnGo = await DSPillsV2CO.inputWithFormItemBlockURL.go();
20
+ if (errorOnGo) throw errorOnGo;
21
+ });
22
+
23
+ it('01: should have no accessibility violations, empty and filled', async () => {
24
+ const input = await DSPillsV2CO.getAssociatedInput();
25
+ await input.waitForDisplayed();
26
+ const emptyResult = await axeCoreCheck();
27
+ expect(emptyResult.length).toBe(0);
28
+ // Filling the input mounts the clear button, which adds an extra focusable control inside the
29
+ // associated form item — worth scanning in that state too.
30
+ await input.click();
31
+ await type('abcd');
32
+ const clearIcon = await DSPillsV2CO.getClearIcon();
33
+ await clearIcon.waitForDisplayed();
34
+ const filledResult = await axeCoreCheck();
35
+ expect(filledResult.length).toBe(0);
36
+ });
37
+ });
38
+ }
@@ -0,0 +1,72 @@
1
+ import DSPillsV2CO from '../DSPillsV2CO';
2
+ import { type } from '../../helpers';
3
+
4
+ // Tests for the fix introduced in PUI-18617 ([PillsV2] Pitfalls enhancements, item 2): the input pill
5
+ // inside a DSFormLayoutBlockItem announced neither its label nor its error message. The fix exposed
6
+ // `dsPillInput` as a real slot on the default (non-inputRender) input, so the association is now made
7
+ // by passing id / aria-describedby through the nested `dsInputtextInput` slot.
8
+ //
9
+ // Assertions are pure DOM attributes, so this is scoped to Chrome — see the skill's
10
+ // "DOM-Attribute Tests Run Chrome-Only" rule. The `|| 'Chrome'` fallback is mandatory: BrowserStack
11
+ // reports the capitalized name and a lowercase-only guard registers 0 tests (false green).
12
+ if (
13
+ (!browser.capabilities['ice:options'].isPhone &&
14
+ !browser.capabilities['ice:options'].isTablet &&
15
+ browser.capabilities.browserName === 'chrome') ||
16
+ browser.capabilities.browserName === 'Chrome'
17
+ ) {
18
+ describe('PUI-19149 - DSPillsV2:: input pill label and error association - Func', () => {
19
+ before('loading page', async () => {
20
+ const errorOnGo = await DSPillsV2CO.inputWithFormItemBlockURL.go();
21
+ if (errorOnGo) throw errorOnGo;
22
+ });
23
+
24
+ it('01: should associate the input with its label and its feedback/error messages', async () => {
25
+ const input = await DSPillsV2CO.getAssociatedInput();
26
+ await input.waitForDisplayed();
27
+
28
+ // The overridden id must land on the pill's real <input>, not on a wrapper. Checking the tag and
29
+ // the pill's own data-testid on the same element is what distinguishes the two.
30
+ await expect(await input.getTagName()).toBe('input');
31
+ await expect(input).toHaveAttribute('data-testid', 'ds-pill-input');
32
+
33
+ // hasError on the pill is forwarded to DSInputText as aria-invalid.
34
+ await expect(input).toHaveAttribute('aria-invalid', 'true');
35
+
36
+ // Both message ids, in the order the story supplies them.
37
+ await expect(input).toHaveAttribute('aria-describedby', DSPillsV2CO.DESCRIBED_BY);
38
+
39
+ // aria-describedby is only meaningful if the referenced nodes exist.
40
+ const feedbackMessage = await DSPillsV2CO.getFeedbackMessage();
41
+ await expect(feedbackMessage).toBeExisting();
42
+ const errorMessage = await DSPillsV2CO.getErrorMessage();
43
+ await expect(errorMessage).toBeExisting();
44
+
45
+ // The label side of the association: DSFormLayoutBlockItem renders label[for] from inputID.
46
+ const label = await DSPillsV2CO.getAssociatedInputLabel();
47
+ await expect(label).toBeExisting();
48
+ await expect(await label.getText()).toContain('Item');
49
+
50
+ // Duplicate ids would make the label and aria-describedby resolve to the wrong node.
51
+ const elementsWithId = await DSPillsV2CO.getNodesWithInputId();
52
+ await expect(elementsWithId.length).toBe(1);
53
+ });
54
+
55
+ it('02: should keep the association intact once the clear button mounts', async () => {
56
+ const input = await DSPillsV2CO.getAssociatedInput();
57
+ await input.click();
58
+ await type('abcd');
59
+
60
+ // The clear button only renders once the input has a value, and it must not reuse the input's id.
61
+ const clearIcon = await DSPillsV2CO.getClearIcon();
62
+ await clearIcon.waitForDisplayed();
63
+ await expect(clearIcon).not.toHaveAttribute('id', DSPillsV2CO.IDS.input);
64
+
65
+ await expect(input).toHaveAttribute('aria-describedby', DSPillsV2CO.DESCRIBED_BY);
66
+ await expect(input).toHaveAttribute('aria-invalid', 'true');
67
+
68
+ const elementsWithId = await DSPillsV2CO.getNodesWithInputId();
69
+ await expect(elementsWithId.length).toBe(1);
70
+ });
71
+ });
72
+ }
@@ -1,4 +1,3 @@
1
- /* eslint-disable wdio/no-pause */
2
1
  import DSProgressIndicatorCO from './DSProgressIndicatorCO';
3
2
  import { axeCoreCheck } from '../helpers';
4
3
 
@@ -8,24 +7,28 @@ if (
8
7
  browser.capabilities.browserName === 'chrome') ||
9
8
  browser.capabilities.browserName === 'Chrome'
10
9
  ) {
11
- describe('PUI-17521: Progress Indicator:: Basic - Axe Core', () => {
10
+ describe('PUI-17521 - DSProgressIndicator:: Basic - Axe-Core', () => {
12
11
  before('loading page', async () => {
13
12
  const errorOnGo = await DSProgressIndicatorCO.basicURL.go();
14
13
  if (errorOnGo) throw errorOnGo;
15
14
  });
16
15
  it('01: should display basic progress indicator and pass axe-core', async () => {
17
- await (await DSProgressIndicatorCO.getUploadLabel()).waitForDisplayed();
18
- await browser.pause(500);
16
+ await (await DSProgressIndicatorCO.getTitle()).waitForDisplayed();
17
+
19
18
  const result = await axeCoreCheck();
20
19
  expect(result.length).toBe(0);
21
20
  });
22
21
  });
23
- describe('PUI-17524: Progress Indicator:: Others Test - Axe Core', () => {
22
+
23
+ describe('PUI-17524 - DSProgressIndicator:: Others Test - Axe-Core', () => {
24
24
  before('loading page', async () => {
25
25
  const errorOnGo = await DSProgressIndicatorCO.othersURL.go();
26
26
  if (errorOnGo) throw errorOnGo;
27
27
  });
28
28
  it('01: should display progress indicator and pass axe-core', async () => {
29
+ // Replaces a blind browser.pause(500): the story animates, so wait on the real end state.
30
+ await DSProgressIndicatorCO.waitForAnimatedCountersToSettle();
31
+
29
32
  const result = await axeCoreCheck();
30
33
  expect(result.length).toBe(0);
31
34
  });
@@ -6,14 +6,41 @@ if (
6
6
  browser.capabilities.browserName === 'chrome') ||
7
7
  browser.capabilities.browserName === 'Chrome'
8
8
  ) {
9
- describe('PUI-17522 - Progress Indicator:: Basic - Func', () => {
9
+ describe('PUI-17522 - DSProgressIndicator:: Basic - Func', () => {
10
10
  before('loading page', async () => {
11
11
  const errorOnGo = await DSProgressIndicatorCO.basicURL.go();
12
12
  if (errorOnGo) throw errorOnGo;
13
13
  });
14
- it('01: should show basic progress indicator', async () => {
15
- const progressIndicator = await DSProgressIndicatorCO.getUploadLabel();
16
- await expect(progressIndicator).toBeDisplayed();
14
+
15
+ // The naming, aria-valuetext and invalid-range contracts live in the aria-contract spec; this
16
+ // one stays on the Basic story's own value contract.
17
+ it('01: should show basic progress indicator with a valid progressbar value range', async () => {
18
+ const title = await DSProgressIndicatorCO.getTitle();
19
+ await expect(title).toBeDisplayed();
20
+ await expect(title).toHaveText('upload');
21
+
22
+ const root = await DSProgressIndicatorCO.getRoot();
23
+ await expect(root).toHaveAttribute('aria-disabled', 'false');
24
+
25
+ const progress = await DSProgressIndicatorCO.getProgress();
26
+ await expect(progress).toHaveAttribute('role', 'progressbar');
27
+ await expect(progress).toHaveAttribute('aria-valuenow', '50');
28
+ await expect(progress).toHaveAttribute('aria-valuemin', '0');
29
+ await expect(progress).toHaveAttribute('aria-valuemax', '100');
30
+ await expect(await progress.getAttribute('style')).toContain('width: 50%');
31
+ });
32
+
33
+ // PUI-18942: title and counter text used to be raw text inside plain spans.
34
+ it('02: should render title and counter text through DSTypography', async () => {
35
+ const { TITLE, COUNTER } = DSProgressIndicatorCO.SLOT_TESTID;
36
+
37
+ const titleText = await DSProgressIndicatorCO.getTypographyIn(TITLE);
38
+ await expect(titleText).toBeExisting();
39
+ await expect(titleText).toHaveText('upload');
40
+
41
+ const counterText = await DSProgressIndicatorCO.getTypographyIn(COUNTER);
42
+ await expect(counterText).toBeExisting();
43
+ await expect(counterText).toHaveText('50 of 100');
17
44
  });
18
45
  });
19
46
  }
@@ -1,24 +1,32 @@
1
1
  import DSProgressIndicatorCO from './DSProgressIndicatorCO';
2
2
 
3
- if (!browser.capabilities['ice:options'].isPhone) {
4
- describe('PUI-17520 - Progress Indicator:: Basic - Visual', () => {
3
+ // Migrated off the pre-Percy browser.checkSnapshot API as part of the PUI-18856 compliance work,
4
+ // which rewrote the class system these baselines were taken against.
5
+ if (!browser.capabilities['ice:options'].isPhone && !browser.capabilities['ice:options'].isTablet) {
6
+ describe('PUI-17520 - DSProgressIndicator:: Basic - Visual', () => {
5
7
  before('loading page', async () => {
6
8
  const errorOnGo = await DSProgressIndicatorCO.basicURL.go();
7
9
  if (errorOnGo) throw errorOnGo;
8
10
  });
9
11
  it('01: should display basic progress indicator', async () => {
10
- await (await DSProgressIndicatorCO.getUploadLabel()).waitForDisplayed();
11
- const snapshot = await browser.checkSnapshot(DSProgressIndicatorCO.snapshotPath('progressindicator-basic'));
12
+ await (await DSProgressIndicatorCO.getTitle()).waitForDisplayed();
13
+
14
+ const snapshot = await browser.percyCheckScreenshot();
12
15
  await expect(snapshot).toEqual(0);
13
16
  });
14
17
  });
15
- describe('PUI-17523 - Progress Indicator:: Others Test - Visual', () => {
18
+
19
+ describe('PUI-17523 - DSProgressIndicator:: Others Test - Visual', () => {
16
20
  before('loading page', async () => {
17
21
  const errorOnGo = await DSProgressIndicatorCO.othersURL.go();
18
22
  if (errorOnGo) throw errorOnGo;
19
23
  });
20
24
  it('01: should display progress indicator', async () => {
21
- const snapshot = await browser.checkSnapshot(DSProgressIndicatorCO.snapshotPath('progressindicator-others'));
25
+ // Two of the four indicators are driven by setInterval and only stop once their counters
26
+ // reach the maximum. Snapshotting before that lands produces a different image every run.
27
+ await DSProgressIndicatorCO.waitForAnimatedCountersToSettle();
28
+
29
+ const snapshot = await browser.percyCheckScreenshot();
22
30
  await expect(snapshot).toEqual(0);
23
31
  });
24
32
  });
@@ -1,26 +1,104 @@
1
1
  import { PATH_E2E_PROGRESS_INDICATOR } from '../paths';
2
2
  import { PageObject, Urlbuilder } from '../helpers';
3
3
 
4
+ // Slot testids owned by the component (packages/atom/ds-progress-indicator/src/constants/index.ts).
5
+ // Note the lowercase `i` in "progressindicator": it comes from DSProgressIndicatorName.
6
+ const SLOT_TESTID = {
7
+ ROOT: 'ds-progressindicator-root',
8
+ TITLE: 'ds-progressindicator-title',
9
+ COUNTER: 'ds-progressindicator-counter',
10
+ PROGRESS_BAR: 'ds-progressindicator-progress-bar',
11
+ PROGRESS: 'ds-progressindicator-progress',
12
+ };
13
+
14
+ // In segmented mode every segment reuses the PROGRESS testid, and the stories render several
15
+ // indicators per page, so selectors are scoped by the story's per-scenario `*-case` wrapper.
16
+ // Omit `caseTestId` for single-indicator stories such as BasicTest.
17
+ const scoped = (testId, caseTestId) =>
18
+ caseTestId ? `[data-testid="${caseTestId}"] [data-testid="${testId}"]` : `[data-testid="${testId}"]`;
19
+
4
20
  export default class DSProgressIndicatorCO extends PageObject {
5
21
  // Desktop
6
22
  static basicURL = new Urlbuilder(PATH_E2E_PROGRESS_INDICATOR, 'basic-test');
7
23
 
8
24
  static othersURL = new Urlbuilder(PATH_E2E_PROGRESS_INDICATOR, 'others-test');
9
25
 
10
- static getUrl(component = 'basic') {
11
- return PageObject.getUrl(PATH_E2E_PROGRESS_INDICATOR, component);
26
+ static ariaContractURL = new Urlbuilder(PATH_E2E_PROGRESS_INDICATOR, 'aria-contract-test');
27
+
28
+ static slotsURL = new Urlbuilder(PATH_E2E_PROGRESS_INDICATOR, 'slots-test');
29
+
30
+ static singleBarVariantURL = new Urlbuilder(PATH_E2E_PROGRESS_INDICATOR, 'single-bar-variant-test');
31
+
32
+ static SLOT_TESTID = SLOT_TESTID;
33
+
34
+ // selectors
35
+ static async getRoot(caseTestId) {
36
+ return $(scoped(SLOT_TESTID.ROOT, caseTestId));
12
37
  }
13
38
 
14
- static async getUploadLabel() {
15
- return $('//span[contains(text(), "upload")]');
39
+ static async getTitle(caseTestId) {
40
+ return $(scoped(SLOT_TESTID.TITLE, caseTestId));
16
41
  }
17
42
 
18
- // Snapshots
19
- static snapshotPath(example = 'basic') {
20
- return PageObject.getSnapshotPathBuilder('ProgressIndicator', example);
43
+ static async getCounter(caseTestId) {
44
+ return $(scoped(SLOT_TESTID.COUNTER, caseTestId));
45
+ }
46
+
47
+ static async getProgressBar(caseTestId) {
48
+ return $(scoped(SLOT_TESTID.PROGRESS_BAR, caseTestId));
49
+ }
50
+
51
+ // First (or only) progressbar of a case. Use getProgressBars for the segmented ones.
52
+ static async getProgress(caseTestId) {
53
+ return $(scoped(SLOT_TESTID.PROGRESS, caseTestId));
54
+ }
55
+
56
+ static async getProgressBars(caseTestId) {
57
+ return $$(scoped(SLOT_TESTID.PROGRESS, caseTestId));
58
+ }
59
+
60
+ // Text node DSTypography renders inside a slot, e.g. getTypographyIn(SLOT_TESTID.TITLE).
61
+ static async getTypographyIn(slotTestId, caseTestId) {
62
+ return $(`${scoped(slotTestId, caseTestId)} [data-dimsum-slot="dsTypographyRoot"]`);
63
+ }
64
+
65
+ // Slot-level lookups, for asserting the data-dimsum-slot contract independently of the testids.
66
+ static async getSlot(slotName, caseTestId) {
67
+ const prefix = caseTestId ? `[data-testid="${caseTestId}"] ` : '';
68
+ return $(`${prefix}[data-dimsum-slot="${slotName}"]`);
69
+ }
70
+
71
+ static async getSlots(slotName, caseTestId) {
72
+ const prefix = caseTestId ? `[data-testid="${caseTestId}"] ` : '';
73
+ return $$(`${prefix}[data-dimsum-slot="${slotName}"]`);
74
+ }
75
+
76
+ // aria-labelledby points at a React.useId() value containing ":", which is not a valid CSS id
77
+ // selector, so the target has to be resolved through the attribute selector instead of "#id".
78
+ static async getByIdAttribute(id) {
79
+ return $(`[id="${id}"]`);
21
80
  }
22
81
 
23
- // selectors
24
- // actions
25
82
  // utils
83
+ // OthersTest drives two indicators with setInterval counters. They stop once each counter reaches
84
+ // its max, so the story is only deterministic after both have landed on their final totals.
85
+ //
86
+ // The texts are read in a single browser.execute rather than by mapping over $$: WebdriverIO's
87
+ // element array overrides .map() to return a Promise, so Promise.all(list.map(...)) throws
88
+ // "object is not iterable". One round trip per poll is also cheaper than four.
89
+ static async waitForAnimatedCountersToSettle() {
90
+ await browser.waitUntil(
91
+ async () => {
92
+ const texts = await browser.execute(
93
+ (testId) => Array.from(document.querySelectorAll(`[data-testid="${testId}"]`), (el) => el.textContent),
94
+ SLOT_TESTID.COUNTER,
95
+ );
96
+ return texts.length >= 4 && texts[1] === '400 of 400' && texts[2] === '330 of 330';
97
+ },
98
+ {
99
+ timeout: 15000,
100
+ timeoutMsg: 'animated progress indicators never reached their final value',
101
+ },
102
+ );
103
+ }
26
104
  }
@@ -0,0 +1,104 @@
1
+ import DSProgressIndicatorCO from '../DSProgressIndicatorCO';
2
+
3
+ // Coverage for the accessibility remediation shipped in PUI-18856 (PR #8353):
4
+ // PUI-18671 the progressbar used to carry a hardcoded aria-label="progress bar". It is now named
5
+ // by the visible title through aria-labelledby, and that string survives only as the
6
+ // fallback for an untitled indicator.
7
+ // PUI-18673 aria-valuetext now mirrors whatever the visible counter renders, default or custom.
8
+ // PUI-18675 each segment accepts a `label` that becomes its own accessible name. Only the Dimsum
9
+ // half of that defect is assertable here; the visible non-colour alternative is the
10
+ // consumer's, see stories/docs/principles/a11y/tradesoff.
11
+ // PUI-18672 an invalid `max` no longer produces width: NaN% and an inverted ARIA range. `max` was
12
+ // deliberately NOT made required (KNOWN_INTENTIONAL_DEVIATIONS.md, deviation 6), so what
13
+ // is asserted below is the shipped fail-safe: an indeterminate progressbar.
14
+ //
15
+ // Every assertion is a DOM attribute, which is identical across engines, so this runs Chrome only.
16
+ // The `|| 'Chrome'` branch is mandatory: BrowserStack reports the capitalised name and a
17
+ // lowercase-only guard would register zero tests while still exiting 0.
18
+ if (
19
+ (!browser.capabilities['ice:options'].isPhone &&
20
+ !browser.capabilities['ice:options'].isTablet &&
21
+ browser.capabilities.browserName === 'chrome') ||
22
+ browser.capabilities.browserName === 'Chrome'
23
+ ) {
24
+ describe('PUI-19209 - DSProgressIndicator:: progressbar ARIA contract - Func', () => {
25
+ before('loading page', async () => {
26
+ const errorOnGo = await DSProgressIndicatorCO.ariaContractURL.go();
27
+ if (errorOnGo) throw errorOnGo;
28
+ });
29
+
30
+ it('01: should name the progressbar from the visible title and fall back when untitled', async () => {
31
+ const titled = await DSProgressIndicatorCO.getProgress('titled-case');
32
+ await titled.waitForDisplayed();
33
+ await expect(titled).not.toHaveAttribute('aria-label');
34
+
35
+ // The id is generated with React.useId(), so it is resolved at runtime rather than hardcoded.
36
+ const labelledBy = await titled.getAttribute('aria-labelledby');
37
+ const title = await DSProgressIndicatorCO.getTitle('titled-case');
38
+ await expect(await title.getAttribute('id')).toEqual(labelledBy);
39
+ await expect(await DSProgressIndicatorCO.getByIdAttribute(labelledBy)).toHaveText('Uploading documents');
40
+
41
+ const titleless = await DSProgressIndicatorCO.getProgress('titleless-case');
42
+ await expect(titleless).toHaveAttribute('aria-label', 'progress bar');
43
+ await expect(titleless).not.toHaveAttribute('aria-labelledby');
44
+ });
45
+
46
+ it('02: should mirror the visible counter in aria-valuetext for default and custom renderers', async () => {
47
+ const defaultCounter = await DSProgressIndicatorCO.getCounter('titled-case');
48
+ await expect(defaultCounter).toHaveText('50 of 100');
49
+ const defaultBar = await DSProgressIndicatorCO.getProgress('titled-case');
50
+ await expect(defaultBar).toHaveAttribute('aria-valuetext', '50 of 100');
51
+
52
+ const customCounter = await DSProgressIndicatorCO.getCounter('custom-counter-case');
53
+ await expect(customCounter).toHaveText('3 files remaining');
54
+ const customBar = await DSProgressIndicatorCO.getProgress('custom-counter-case');
55
+ await expect(customBar).toHaveAttribute('aria-valuetext', '3 files remaining');
56
+ });
57
+
58
+ it('03: should give each labelled segment its own accessible name', async () => {
59
+ const labelled = await DSProgressIndicatorCO.getProgressBars('labelled-segments-case');
60
+ await expect(labelled).toBeElementsArrayOfSize(2);
61
+
62
+ // A segment label wins over the indicator title, so aria-labelledby is dropped entirely.
63
+ await expect(labelled[0]).toHaveAttribute('aria-label', 'Approved documents');
64
+ await expect(labelled[0]).not.toHaveAttribute('aria-labelledby');
65
+ await expect(labelled[1]).toHaveAttribute('aria-label', 'Rejected documents');
66
+ await expect(labelled[1]).not.toHaveAttribute('aria-labelledby');
67
+
68
+ // Each segment reports its own range rather than the aggregate shown by the counter.
69
+ await expect(labelled[0]).toHaveAttribute('aria-valuetext', '50 of 100');
70
+ await expect(labelled[1]).toHaveAttribute('aria-valuetext', '100 of 200');
71
+
72
+ // An unlabelled segment still falls back to the indicator title.
73
+ const mixed = await DSProgressIndicatorCO.getProgressBars('mixed-segments-case');
74
+ await expect(mixed).toBeElementsArrayOfSize(2);
75
+ await expect(mixed[0]).toHaveAttribute('aria-label', 'Completed');
76
+ await expect(mixed[1]).not.toHaveAttribute('aria-label');
77
+ const fallbackId = await mixed[1].getAttribute('aria-labelledby');
78
+ await expect(await DSProgressIndicatorCO.getByIdAttribute(fallbackId)).toHaveText('Batch upload');
79
+ });
80
+
81
+ it('04: should render indeterminate for an invalid max and clamp an overflowing value', async () => {
82
+ const expectIndeterminate = async (caseId) => {
83
+ const bar = await DSProgressIndicatorCO.getProgress(caseId);
84
+ await expect(bar).toHaveAttribute('role', 'progressbar');
85
+ await expect(bar).not.toHaveAttribute('aria-valuenow');
86
+ await expect(bar).not.toHaveAttribute('aria-valuemin');
87
+ await expect(bar).not.toHaveAttribute('aria-valuemax');
88
+ await expect(bar).not.toHaveAttribute('aria-valuetext');
89
+ // The pre-fix behaviour was width: NaN%; the guard now yields a valid zero-length fill.
90
+ await expect(await bar.getAttribute('style')).toContain('width: 0%');
91
+ // The indicator is still named, so the invalid range degrades without losing the label.
92
+ await expect(bar).toHaveAttribute('aria-labelledby');
93
+ };
94
+
95
+ await expectIndeterminate('omitted-max-case');
96
+ await expectIndeterminate('zero-max-case');
97
+
98
+ const overflow = await DSProgressIndicatorCO.getProgress('overflow-case');
99
+ await expect(overflow).toHaveAttribute('aria-valuenow', '100');
100
+ await expect(overflow).toHaveAttribute('aria-valuemax', '100');
101
+ await expect(await overflow.getAttribute('style')).toContain('width: 100%');
102
+ });
103
+ });
104
+ }