dimsum-e2e-tests 3.70.0-next.48 → 3.70.0-next.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/ds-controlled-form/ds-controlled-radio/aria-disabled/DSControlledRadio.aria-disabled.axe-core.func.spec.js +16 -2
- package/ds-controlled-form/ds-controlled-radio/aria-disabled/DSControlledRadio.aria-disabled.func.spec.js +37 -0
- package/ds-data-table-async/DSDataTableCO.js +2 -0
- package/ds-data-table-async/components/HeaderCO.js +15 -0
- package/ds-data-table-async/filter-menu-close/DSDataTable.filter-menu-close.func.spec.js +39 -0
- package/ds-data-table-async/header-affordances/DSDataTable.header-affordances.func.spec.js +92 -0
- package/ds-data-table-async/resizable/DSDataTable.resize-min-clamp.func.spec.js +72 -0
- package/ds-data-table-async/toggle-cell-focus/DSDataTable.toggle-cell-enter-focus.func.spec.js +31 -0
- package/ds-form-date-range-picker/DateRangePicker.axe-core.spec.js +5 -0
- package/ds-form-date-range-picker/DateRangePicker.functional.spec.js +80 -18
- package/ds-form-date-range-picker/DateRangePickerCO.js +6 -0
- package/ds-form-date-time-picker/partial-date-open-focus/DateTimePicker.partial-date-open-focus.func.spec.js +45 -0
- package/ds-form-toggle/DSToggle.func.spec.js +20 -23
- package/ds-form-toggle/DSToggleCO.js +11 -2
- package/ds-global-header/GlobalHeader.announcements.func.spec.js +27 -0
- package/ds-global-header/GlobalHeader.axe-core.func.spec.js +15 -15
- package/ds-global-header/GlobalHeader.func.spec.js +70 -1
- package/ds-global-header/GlobalHeaderCO.js +25 -1
- package/ds-hooks/useFocusStack/DSHook.useFocusStack.func.spec.js +83 -0
- package/ds-shuttle-v2/DSShuttleV2CO.js +4 -0
- package/ds-shuttle-v2/breadcrumb-a11y/DSShuttleV2.breadcrumb-a11y.func.spec.js +43 -0
- package/ds-shuttle-v2/items/DSShuttleV2.items.func.spec.js +4 -2
- package/package.json +149 -150
- package/paths.js +0 -3
- package/ds-hooks/useFocusStack/DSHook.useFocusStack.skipped.js +0 -57
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## 3.70.0-next.50 (2026-07-24)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package dimsum-e2e-tests
|
|
9
|
+
|
|
10
|
+
## 3.70.0-next.49 (2026-07-21)
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
- ds-form-checkbox:: update to latest spec [PUI-18112](https://jira.elliemae.io/browse/PUI-18112) ([#8201](https://git.elliemae.io/platform-ui/dimsum/issues/8201)) ([ed9660f](https://git.elliemae.io/platform-ui/dimsum/commit/ed9660fa98038dd1a9fc9c591b6c0980cfba102f))
|
|
15
|
+
|
|
6
16
|
## 3.70.0-next.48 (2026-07-20)
|
|
7
17
|
|
|
8
18
|
### Bug Fixes
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Key } from 'webdriverio';
|
|
1
2
|
import { axeCoreCheck } from '../../../helpers';
|
|
2
3
|
import DSControlledRadioCO from '../DSControlledRadioCO';
|
|
3
4
|
|
|
@@ -13,10 +14,23 @@ if (
|
|
|
13
14
|
if (errorOnGo) throw errorOnGo;
|
|
14
15
|
});
|
|
15
16
|
it('01: should display radio with aria-disabled and pass axe-core scann', async () => {
|
|
16
|
-
const
|
|
17
|
+
const result = await axeCoreCheck();
|
|
18
|
+
await expect(result).toHaveLength(0);
|
|
19
|
+
});
|
|
20
|
+
it('02: should focus and select first radio with aria-disabled and pass axe-core scann', async () => {
|
|
21
|
+
const radio = await DSControlledRadioCO.getRadioByIndex(0);
|
|
17
22
|
await radio.click();
|
|
23
|
+
await expect(radio).toBeFocused();
|
|
24
|
+
await expect(radio).toBeSelected();
|
|
25
|
+
const result = await axeCoreCheck();
|
|
26
|
+
await expect(result).toHaveLength(0);
|
|
27
|
+
});
|
|
28
|
+
it('03: should focus second radio with aria-disabled and pass axe-core scann', async () => {
|
|
29
|
+
await browser.keys(Key.ArrowDown);
|
|
30
|
+
const radio = await DSControlledRadioCO.getRadioByIndex(1);
|
|
31
|
+
await expect(radio).toBeFocused();
|
|
18
32
|
const result = await axeCoreCheck();
|
|
19
|
-
expect(result
|
|
33
|
+
await expect(result).toHaveLength(0);
|
|
20
34
|
});
|
|
21
35
|
});
|
|
22
36
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Key } from 'webdriverio';
|
|
2
|
+
import DSControlledRadioCO from '../DSControlledRadioCO';
|
|
3
|
+
|
|
4
|
+
if (
|
|
5
|
+
!browser.capabilities['ice:options'].isPhone &&
|
|
6
|
+
!browser.capabilities['ice:options'].isTablet &&
|
|
7
|
+
(browser.capabilities.browserName === 'Chrome' || browser.capabilities.browserName === 'chrome')
|
|
8
|
+
) {
|
|
9
|
+
describe('PUI-18777 - DSControlledRadio:: aria-disabled group Tab navigation - Func', () => {
|
|
10
|
+
before('loading page', async () => {
|
|
11
|
+
const errorOnGo = await DSControlledRadioCO.applyAriaDisabled.go();
|
|
12
|
+
if (errorOnGo) throw errorOnGo;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('01: should focus the checked option when tabbing into the aria-disabled group', async () => {
|
|
16
|
+
const firstRadio = await DSControlledRadioCO.getRadioByIndex(0);
|
|
17
|
+
await browser.keys(Key.Tab);
|
|
18
|
+
await expect(firstRadio).toBeFocused();
|
|
19
|
+
await expect(firstRadio).toBeSelected();
|
|
20
|
+
});
|
|
21
|
+
it('02: should focus the second option when arrow down is pressed', async () => {
|
|
22
|
+
const secondRadio = await DSControlledRadioCO.getRadioByIndex(1);
|
|
23
|
+
await browser.keys(Key.ArrowDown);
|
|
24
|
+
await expect(secondRadio).toBeFocused();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('03: should tab out of the group instead of moving to the next option', async () => {
|
|
28
|
+
const firstRadio = await DSControlledRadioCO.getRadioByIndex(0);
|
|
29
|
+
const secondRadio = await DSControlledRadioCO.getRadioByIndex(1);
|
|
30
|
+
const thirdRadio = await DSControlledRadioCO.getRadioByIndex(2);
|
|
31
|
+
await browser.keys(Key.Tab);
|
|
32
|
+
await expect(firstRadio).not.toBeFocused();
|
|
33
|
+
await expect(secondRadio).not.toBeFocused();
|
|
34
|
+
await expect(thirdRadio).not.toBeFocused();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -265,6 +265,8 @@ export default class DataTableCO extends PageObject {
|
|
|
265
265
|
|
|
266
266
|
static customCellRowAndHeaderTest = new Urlbuilder(PATH_E2E_DATATABLE_CELL, 'custom-cell-row-and-header');
|
|
267
267
|
|
|
268
|
+
static toggleCellEnterFocus = new Urlbuilder(PATH_E2E_DATATABLE_CELL, 'toggle-cell-enter-focus-test');
|
|
269
|
+
|
|
268
270
|
// Use Cases
|
|
269
271
|
|
|
270
272
|
static integratedExampleWithFilters = new Urlbuilder(PATH_E2E_DATATABLE_USE_CASES, 'integrated-example-with-filters');
|
|
@@ -57,6 +57,13 @@ export default class HeaderCO extends PageObject {
|
|
|
57
57
|
// that fix.
|
|
58
58
|
static getResizeHandler = async (index) => $$('[data-testid="ds-datatable-resizer"]')[index];
|
|
59
59
|
|
|
60
|
+
static getResizeHandlers = async () => $$('[data-testid="ds-datatable-resizer"]');
|
|
61
|
+
|
|
62
|
+
static getLastResizeHandler = async () => {
|
|
63
|
+
const all = await this.getResizeHandlers();
|
|
64
|
+
return all[all.length - 1];
|
|
65
|
+
};
|
|
66
|
+
|
|
60
67
|
static getFilterBtnByIndex = async (index) => $$('[data-testid="data-table-filter-menu-button"] button')[index];
|
|
61
68
|
|
|
62
69
|
static getSortBtnByIndex = async (index) => {
|
|
@@ -64,6 +71,14 @@ export default class HeaderCO extends PageObject {
|
|
|
64
71
|
return headerCell.$('[data-testid="data-table-sort-button"]');
|
|
65
72
|
};
|
|
66
73
|
|
|
74
|
+
// Filter icon wrapper scoped to a single header cell. The wrapper span carries the
|
|
75
|
+
// display:none/opacity:0 "hide" styling driven by the header's hover/focus state (PUI-15154),
|
|
76
|
+
// so its isDisplayed() reflects filter-icon visibility for that specific column.
|
|
77
|
+
static getFilterButtonInHeader = async (index) => {
|
|
78
|
+
const headerCell = await this.getHeaderCellByIndex(index);
|
|
79
|
+
return headerCell.$('[data-testid="data-table-filter-menu-button"]');
|
|
80
|
+
};
|
|
81
|
+
|
|
67
82
|
// PUI-17786: a11y accessors for the resize handle. Targets the same [data-testid="ds-datatable-resizer"]
|
|
68
83
|
// as the getResizeHandler selector fix (separate PR); kept distinct so these a11y specs run standalone.
|
|
69
84
|
static getResizer = async (index) => $$('[data-testid="ds-datatable-resizer"]')[index];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { mouseOver } from '../../helpers';
|
|
2
|
+
import DSDataTableCO from '../DSDataTableCO';
|
|
3
|
+
import { HeaderCO, FiltersCO } from '../components';
|
|
4
|
+
|
|
5
|
+
// SKIPPED pending fix for defect PUI-18785: an open per-column filter menu does not close when the
|
|
6
|
+
// user clicks the column header cell or its drag-and-drop handle — the column can even be dragged
|
|
7
|
+
// with the menu open, and the menu drops alongside the column and stays open. These tests capture
|
|
8
|
+
// the EXPECTED behavior (the menu must close on those interactions). Un-skip them once PUI-18785
|
|
9
|
+
// lands, then create the Test ticket and replace PUI-XXXXX.
|
|
10
|
+
if (!browser.capabilities['ice:options'].isPhone && !browser.capabilities['ice:options'].isTablet) {
|
|
11
|
+
describe.skip('PUI-XXXXX - DSDataTable:: filter menu closes on column header or drag interaction - Func', () => {
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
const errorOnGo = await DSDataTableCO.sortByColumn.go();
|
|
14
|
+
if (errorOnGo) throw errorOnGo;
|
|
15
|
+
await DSDataTableCO.waitForDataTable();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('01: clicking the column drag handle closes an open filter menu', async () => {
|
|
19
|
+
const idHeader = await HeaderCO.getHeaderCellByIndex(0);
|
|
20
|
+
await mouseOver(idHeader);
|
|
21
|
+
await (await HeaderCO.getFilterBtnByIndex(0)).click();
|
|
22
|
+
await expect(await FiltersCO.getFilterMenuContent()).toBeDisplayed();
|
|
23
|
+
|
|
24
|
+
const dragHandle = await HeaderCO.getHeaderCellDragHandler(idHeader);
|
|
25
|
+
await dragHandle.click();
|
|
26
|
+
await expect(await FiltersCO.getFilterMenuContent()).not.toBeDisplayed();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('02: clicking the column header cell closes an open filter menu', async () => {
|
|
30
|
+
const idHeader = await HeaderCO.getHeaderCellByIndex(0);
|
|
31
|
+
await mouseOver(idHeader);
|
|
32
|
+
await (await HeaderCO.getFilterBtnByIndex(0)).click();
|
|
33
|
+
await expect(await FiltersCO.getFilterMenuContent()).toBeDisplayed();
|
|
34
|
+
|
|
35
|
+
await idHeader.click();
|
|
36
|
+
await expect(await FiltersCO.getFilterMenuContent()).not.toBeDisplayed();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { mouseOver } from '../../helpers';
|
|
2
|
+
import DSDataTableCO from '../DSDataTableCO';
|
|
3
|
+
import { HeaderCO, RowCO } from '../components';
|
|
4
|
+
|
|
5
|
+
// Coverage for PUI-15154 (fixed): the column-header affordances — DnD drag handle, neutral sort
|
|
6
|
+
// caret and filter icon — stayed visibly stuck after focus/hover left the header (null-vs-null
|
|
7
|
+
// false match in the onBlur guard; missing resetAllHeadersExcept between columns). They must only
|
|
8
|
+
// show while the header has focus or hover. jsdom can't move real focus or hover, so this is
|
|
9
|
+
// verified in a real browser here (the shipped unit test fakes it with synthetic relatedTarget).
|
|
10
|
+
//
|
|
11
|
+
// SortByColumn columns: id(0) name(1, actively sorted) position(2) country(3) salary(4) date(5).
|
|
12
|
+
// position = drag handle + neutral sort caret + filter icon; date = the reported-bug column
|
|
13
|
+
// (drag handle + neutral sort caret, no filter). All columns are draggable.
|
|
14
|
+
const POSITION_COL = 2;
|
|
15
|
+
const DATE_COL = 5;
|
|
16
|
+
|
|
17
|
+
const expectAffordances = async (colIndex, shown, hasFilter) => {
|
|
18
|
+
const cell = await HeaderCO.getHeaderCellByIndex(colIndex);
|
|
19
|
+
const dragHandle = await HeaderCO.getHeaderCellDragHandler(cell);
|
|
20
|
+
const sortCaret = await HeaderCO.getSortBtnByIndex(colIndex);
|
|
21
|
+
if (shown) {
|
|
22
|
+
await expect(dragHandle).toBeExisting();
|
|
23
|
+
await expect(sortCaret).toBeDisplayed();
|
|
24
|
+
if (hasFilter) await expect(await HeaderCO.getFilterButtonInHeader(colIndex)).toBeDisplayed();
|
|
25
|
+
} else {
|
|
26
|
+
await expect(dragHandle).not.toBeExisting();
|
|
27
|
+
await expect(sortCaret).not.toBeDisplayed();
|
|
28
|
+
if (hasFilter) await expect(await HeaderCO.getFilterButtonInHeader(colIndex)).not.toBeDisplayed();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
if (!browser.capabilities['ice:options'].isPhone && !browser.capabilities['ice:options'].isTablet) {
|
|
33
|
+
describe('PUI-18778 - DSDataTable:: header affordance visibility on focus and hover - Func', () => {
|
|
34
|
+
before(async () => {
|
|
35
|
+
const errorOnGo = await DSDataTableCO.sortByColumn.go();
|
|
36
|
+
if (errorOnGo) throw errorOnGo;
|
|
37
|
+
await DSDataTableCO.waitForDataTable();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('01: focusing a column header shows its drag handle, sort caret and filter icon', async () => {
|
|
41
|
+
// Focus programmatically: the header cell has onClick=handleSort, so clicking it would also
|
|
42
|
+
// sort the column, and an actively-sorted column's caret then stays visible regardless of
|
|
43
|
+
// focus — masking the hide-on-blur behavior under test. Focusing fires onFocus without sorting.
|
|
44
|
+
await browser.execute((el) => el.focus(), await HeaderCO.getHeaderCellByIndex(POSITION_COL));
|
|
45
|
+
await expectAffordances(POSITION_COL, true, true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('02: clicking another column drag handle hides the first column drag handle', async () => {
|
|
49
|
+
// Marcos #2 (handle-to-handle): reveal + click column A's drag handle, then column B's.
|
|
50
|
+
// Focusing B's handle clears A via resetAllHeadersExcept, so A's handle no longer stays stuck.
|
|
51
|
+
const positionHeader = await HeaderCO.getHeaderCellByIndex(POSITION_COL);
|
|
52
|
+
await mouseOver(positionHeader);
|
|
53
|
+
const positionHandle = await HeaderCO.getHeaderCellDragHandler(positionHeader);
|
|
54
|
+
await positionHandle.waitForDisplayed({ timeout: 3000 });
|
|
55
|
+
await positionHandle.click();
|
|
56
|
+
|
|
57
|
+
const dateHeader = await HeaderCO.getHeaderCellByIndex(DATE_COL);
|
|
58
|
+
await mouseOver(dateHeader);
|
|
59
|
+
const dateHandle = await HeaderCO.getHeaderCellDragHandler(dateHeader);
|
|
60
|
+
await dateHandle.waitForDisplayed({ timeout: 3000 });
|
|
61
|
+
await dateHandle.click();
|
|
62
|
+
|
|
63
|
+
// Column A's (position) drag handle is gone; column B's (date) handle is present.
|
|
64
|
+
const positionCell = await HeaderCO.getHeaderCellByIndex(POSITION_COL);
|
|
65
|
+
await expect(await HeaderCO.getHeaderCellDragHandler(positionCell)).not.toBeExisting();
|
|
66
|
+
const dateCell = await HeaderCO.getHeaderCellByIndex(DATE_COL);
|
|
67
|
+
await expect(await HeaderCO.getHeaderCellDragHandler(dateCell)).toBeExisting();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('03: blurring focus away entirely hides the affordances', async () => {
|
|
71
|
+
// Reported bug: focus leaves with nowhere to land. Ensure the pointer is off any header (so
|
|
72
|
+
// hover isn't keeping the column visible), then drop focus. onBlur fires with relatedTarget
|
|
73
|
+
// null and, since date has no filter, filterIconRef is null too: the null-vs-null false match
|
|
74
|
+
// that used to skip the reset (PUI-15154) must no longer keep the affordances stuck visible.
|
|
75
|
+
await mouseOver(await RowCO.getRowByIndex(3));
|
|
76
|
+
await browser.execute(() => {
|
|
77
|
+
if (document.activeElement instanceof HTMLElement) document.activeElement.blur();
|
|
78
|
+
});
|
|
79
|
+
await expectAffordances(DATE_COL, false, false);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('04: hovering a column header shows its drag handle, sort caret and filter icon', async () => {
|
|
83
|
+
await mouseOver(await HeaderCO.getHeaderCellByIndex(POSITION_COL));
|
|
84
|
+
await expectAffordances(POSITION_COL, true, true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('05: moving the mouse away from the header hides the affordances', async () => {
|
|
88
|
+
await mouseOver(await RowCO.getRowByIndex(3));
|
|
89
|
+
await expectAffordances(POSITION_COL, false, true);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Key } from 'webdriverio';
|
|
2
|
+
import { sendKeyRepeat } from '../../helpers';
|
|
3
|
+
import DSDataTableCO from '../DSDataTableCO';
|
|
4
|
+
import { HeaderCO } from '../components';
|
|
5
|
+
|
|
6
|
+
// Coverage for PUI-12696 (fixed): a keyboard double-delta commit in useHeaderResizer let the last
|
|
7
|
+
// column resize below its declared minWidth, so the resize handle overlapped the header title.
|
|
8
|
+
// The resizableWithConstrains story sets minWidth: 100 / maxWidth: 300 on all 7 columns, so the
|
|
9
|
+
// last column (dateRange, index 6) must clamp at 100 no matter how hard it is shrunk.
|
|
10
|
+
const LAST_COLUMN_INDEX = 6;
|
|
11
|
+
|
|
12
|
+
if (!browser.capabilities['ice:options'].isPhone && !browser.capabilities['ice:options'].isTablet) {
|
|
13
|
+
describe('PUI-18764 - DSDataTable:: last column resize clamps at minWidth - Func', () => {
|
|
14
|
+
before(async () => {
|
|
15
|
+
const errorOnGo = await DSDataTableCO.resizableWithConstrains.go();
|
|
16
|
+
if (errorOnGo) throw errorOnGo;
|
|
17
|
+
await DSDataTableCO.waitForDataTable();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('01: keyboard Shift+Alt+ArrowLeft should not shrink last column below minWidth', async () => {
|
|
21
|
+
// Focus the last column's resize handle: click its header, then Return focuses the handle
|
|
22
|
+
// directly (no drag/filter/sort child precedes it on this story) — same flow as PUI-11345.
|
|
23
|
+
const lastHeader = await HeaderCO.getHeaderCellByIndex(LAST_COLUMN_INDEX);
|
|
24
|
+
await lastHeader.click();
|
|
25
|
+
await browser.keys(Key.Return);
|
|
26
|
+
await expect(await HeaderCO.getLastResizeHandler()).toBeFocused();
|
|
27
|
+
|
|
28
|
+
// Each Shift+Alt+ArrowLeft is -50px; 6 presses drives well past the 100px floor.
|
|
29
|
+
await sendKeyRepeat([Key.Shift, Key.Alt, Key.ArrowLeft], 6);
|
|
30
|
+
|
|
31
|
+
await browser.waitUntil(
|
|
32
|
+
async () => {
|
|
33
|
+
const resizer = await HeaderCO.getLastResizeHandler();
|
|
34
|
+
const valueNow = Number(await resizer.getAttribute('aria-valuenow'));
|
|
35
|
+
const valueMin = Number(await resizer.getAttribute('aria-valuemin'));
|
|
36
|
+
return valueNow === valueMin;
|
|
37
|
+
},
|
|
38
|
+
{ timeout: 5000, timeoutMsg: 'last column did not clamp at minWidth after keyboard shrink' },
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const resizer = await HeaderCO.getLastResizeHandler();
|
|
42
|
+
const valueNow = Number(await resizer.getAttribute('aria-valuenow'));
|
|
43
|
+
const valueMin = Number(await resizer.getAttribute('aria-valuemin'));
|
|
44
|
+
// aria-valuenow mirrors the committed column width, so clamping at aria-valuemin (100) is the
|
|
45
|
+
// deterministic guard that the width was NOT reduced beyond its minimum limit (PUI-12696).
|
|
46
|
+
expect(valueNow).toBe(valueMin); // committed width clamped exactly at the floor
|
|
47
|
+
expect(valueNow).toBeGreaterThanOrEqual(100); // never below the declared minWidth
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('02: mouse drag should not shrink last column below minWidth', async () => {
|
|
51
|
+
// Widen first so the following shrink is a real reduction, then drag well past the floor.
|
|
52
|
+
await (await HeaderCO.getLastResizeHandler()).dragAndDrop({ x: 120, y: 0 });
|
|
53
|
+
await (await HeaderCO.getLastResizeHandler()).dragAndDrop({ x: -400, y: 0 });
|
|
54
|
+
|
|
55
|
+
await browser.waitUntil(
|
|
56
|
+
async () => {
|
|
57
|
+
const resizer = await HeaderCO.getLastResizeHandler();
|
|
58
|
+
const valueNow = Number(await resizer.getAttribute('aria-valuenow'));
|
|
59
|
+
const valueMin = Number(await resizer.getAttribute('aria-valuemin'));
|
|
60
|
+
return valueNow === valueMin;
|
|
61
|
+
},
|
|
62
|
+
{ timeout: 5000, timeoutMsg: 'last column did not clamp at minWidth after mouse shrink' },
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const resizer = await HeaderCO.getLastResizeHandler();
|
|
66
|
+
const valueNow = Number(await resizer.getAttribute('aria-valuenow'));
|
|
67
|
+
const valueMin = Number(await resizer.getAttribute('aria-valuemin'));
|
|
68
|
+
expect(valueNow).toBe(valueMin);
|
|
69
|
+
expect(valueNow).toBeGreaterThanOrEqual(100);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
package/ds-data-table-async/toggle-cell-focus/DSDataTable.toggle-cell-enter-focus.func.spec.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Key } from 'webdriverio';
|
|
2
|
+
import DSDataTableCO from '../DSDataTableCO';
|
|
3
|
+
import { RowCO, CellCO } from '../components';
|
|
4
|
+
import DSToggleCO from '../../ds-form-toggle/DSToggleCO';
|
|
5
|
+
|
|
6
|
+
// Coverage for PUI-14730 (fixed): a toggle (DSControlledToggle) used as a custom cell did not
|
|
7
|
+
// receive focus on the first Enter — it dropped its ref (containerProps only reached the wrapper
|
|
8
|
+
// div; innerRef was discarded). Fixed in ds-form-toggle by forwarding innerRef to the underlying
|
|
9
|
+
// <button role="switch">. The story renders the toggle as the FIRST focusable cell, so Enter on a
|
|
10
|
+
// focused row must land focus on the toggle button (row.cells.find(c => c.ref.current).focus()).
|
|
11
|
+
if (!browser.capabilities['ice:options'].isPhone && !browser.capabilities['ice:options'].isTablet) {
|
|
12
|
+
describe('PUI-18783 - DSDataTable:: toggle custom cell focus on row Enter - Func', () => {
|
|
13
|
+
before(async () => {
|
|
14
|
+
const errorOnGo = await DSDataTableCO.toggleCellEnterFocus.go();
|
|
15
|
+
if (errorOnGo) throw errorOnGo;
|
|
16
|
+
await DSDataTableCO.waitForDataTable();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('01: pressing Enter on a focused row moves focus to the toggle custom cell', async () => {
|
|
20
|
+
// Focus the row by clicking its first cell (the focusable row-content, role="row", receives
|
|
21
|
+
// focus), then press Enter — matches the editable-cell Enter-to-first-cell pattern.
|
|
22
|
+
const row = await RowCO.getRowByIndex(0);
|
|
23
|
+
const firstCell = await CellCO.getRowCellByIndex(row, 0);
|
|
24
|
+
await firstCell.click();
|
|
25
|
+
await browser.keys(Key.Return);
|
|
26
|
+
|
|
27
|
+
const toggle = await DSToggleCO.getToggleFocuseableByIndex(0);
|
|
28
|
+
await expect(toggle).toBeFocused();
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -17,6 +17,11 @@ if (!browser.capabilities['ice:options'].isPhone && !browser.capabilities['ice:o
|
|
|
17
17
|
it('02: should have expanded calendar, empty input and pass axe-core scan', async () => {
|
|
18
18
|
const calendarTrigger = await DateRangePickerCO.getPickerBtn();
|
|
19
19
|
await calendarTrigger.click();
|
|
20
|
+
// PUI-15571: the calendar popover must be announced as a dialog (it used to expose
|
|
21
|
+
// role="region"), with an accessible name (WCAG 4.1.2 Name, Role, Value).
|
|
22
|
+
const dialog = await DateRangePickerCO.getCalendarDialog();
|
|
23
|
+
await expect(dialog).toHaveAttribute('role', 'dialog');
|
|
24
|
+
await expect(dialog).toHaveAttribute('aria-label', 'calendar header');
|
|
20
25
|
const result = await axeCoreCheck();
|
|
21
26
|
expect(result.length).toBe(0);
|
|
22
27
|
});
|
|
@@ -34,8 +34,16 @@ if (!browser.capabilities['ice:options'].isPhone && !browser.capabilities['ice:o
|
|
|
34
34
|
await openPickerBtn.click();
|
|
35
35
|
const startDate = await DateRangePickerCO.getRangeStartDay();
|
|
36
36
|
const endDate = await DateRangePickerCO.getRangeEndDay();
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
// PUI-18279 (a11y): days inside the selected range announce the whole range instead of a
|
|
38
|
+
// bare weekday label, so screen-reader users hear the full selection on every range day.
|
|
39
|
+
await expect(startDate).toHaveAttribute(
|
|
40
|
+
'aria-label',
|
|
41
|
+
'February third 2020, range from February third 2020 to February eighth 2020 selected.',
|
|
42
|
+
);
|
|
43
|
+
await expect(endDate).toHaveAttribute(
|
|
44
|
+
'aria-label',
|
|
45
|
+
'February eighth 2020, range from February third 2020 to February eighth 2020 selected.',
|
|
46
|
+
);
|
|
39
47
|
});
|
|
40
48
|
it('03: should change the text input to the picked date (mouse)', async () => {
|
|
41
49
|
const exampleDate1 = await DateRangePickerCO.getDayOnCalendarByIndex(16);
|
|
@@ -55,24 +63,78 @@ if (!browser.capabilities['ice:options'].isPhone && !browser.capabilities['ice:o
|
|
|
55
63
|
await expect(endingDay).toHaveValue(expect.stringContaining('09'));
|
|
56
64
|
await expect(endingYear).toHaveValue(expect.stringContaining('2020'));
|
|
57
65
|
});
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
66
|
+
// Keyboard range selection previously lived here as a 4th step, but it re-picked over the range
|
|
67
|
+
// typed in test 01 (a stateful re-pick whose result is hard to reason about). It now has
|
|
68
|
+
// dedicated, deterministic, fresh-state coverage in the PUI-15572 describe below (mouse +
|
|
69
|
+
// keyboard + auto-ordering).
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// PUI-15572 (WCAG 3.2.2 On Input): completing the range must close the calendar automatically and
|
|
73
|
+
// return focus to the trigger so the user knows the selection succeeded. beforeEach reloads a
|
|
74
|
+
// fresh empty picker so each scenario is independent (the stateful Basic flow above re-picks over
|
|
75
|
+
// an existing range and is intentionally not used to assert closure). The story fixes
|
|
76
|
+
// onCalendarOpenFocusedDay to 12/27/2000, so keyboard navigation lands on deterministic days.
|
|
77
|
+
describe('PUI-18775 - DSControlledDateRangePicker:: calendar closes on completed range -Func', () => {
|
|
78
|
+
beforeEach('loading page', async () => {
|
|
79
|
+
const errorOnGo = await DateRangePickerCO.basic.go();
|
|
80
|
+
if (errorOnGo) throw errorOnGo;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('01: should stay open on start pick, then close and refocus trigger on end pick (mouse)', async () => {
|
|
84
|
+
const openPickerBtn = await DateRangePickerCO.getPickerBtn();
|
|
85
|
+
await openPickerBtn.click();
|
|
86
|
+
const dialog = await DateRangePickerCO.getCalendarDialog();
|
|
87
|
+
await expect(dialog).toBeDisplayedInViewport();
|
|
88
|
+
|
|
89
|
+
// first pick = start date -> range incomplete, calendar must stay open
|
|
90
|
+
await (await DateRangePickerCO.getDayOnCalendarByIndex(16)).click();
|
|
91
|
+
await expect(dialog).toBeDisplayedInViewport();
|
|
92
|
+
|
|
93
|
+
// second pick = end date -> range complete, calendar must close and focus returns to trigger
|
|
94
|
+
await (await DateRangePickerCO.getDayOnCalendarByIndex(20)).click();
|
|
95
|
+
await expect(dialog).not.toBeDisplayedInViewport();
|
|
96
|
+
await expect(openPickerBtn).toBeFocused();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('02: should complete the range via keyboard, closing the calendar and refocusing the trigger', async () => {
|
|
100
|
+
const openPickerBtn = await DateRangePickerCO.getPickerBtn();
|
|
101
|
+
await openPickerBtn.click();
|
|
102
|
+
const dialog = await DateRangePickerCO.getCalendarDialog();
|
|
103
|
+
await expect(dialog).toBeDisplayedInViewport();
|
|
104
|
+
|
|
105
|
+
// focus opens on Dec 27 2000; ArrowRight -> Dec 28, Enter picks the start (range incomplete)
|
|
61
106
|
await browser.keys(Key.ArrowRight);
|
|
107
|
+
await browser.keys(Key.Enter);
|
|
108
|
+
await expect(dialog).toBeDisplayedInViewport();
|
|
109
|
+
|
|
110
|
+
// ArrowRight x2 -> Dec 30, Enter completes the range -> calendar closes and focus returns
|
|
62
111
|
await browser.keys(Key.ArrowRight);
|
|
63
|
-
await browser.keys(Key.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
await expect(
|
|
71
|
-
await expect(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
await
|
|
112
|
+
await browser.keys(Key.ArrowRight);
|
|
113
|
+
await browser.keys(Key.Enter);
|
|
114
|
+
await expect(dialog).not.toBeDisplayedInViewport();
|
|
115
|
+
await expect(openPickerBtn).toBeFocused();
|
|
116
|
+
|
|
117
|
+
await expect(await DateRangePickerCO.getMonthInputByIndex(0)).toHaveValue(expect.stringContaining('12'));
|
|
118
|
+
await expect(await DateRangePickerCO.getDayInputByIndex(0)).toHaveValue(expect.stringContaining('28'));
|
|
119
|
+
await expect(await DateRangePickerCO.getMonthInputByIndex(1)).toHaveValue(expect.stringContaining('12'));
|
|
120
|
+
await expect(await DateRangePickerCO.getDayInputByIndex(1)).toHaveValue(expect.stringContaining('30'));
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('03: should auto-order the range when the completing pick precedes the start pick', async () => {
|
|
124
|
+
const openPickerBtn = await DateRangePickerCO.getPickerBtn();
|
|
125
|
+
await openPickerBtn.click();
|
|
126
|
+
const dialog = await DateRangePickerCO.getCalendarDialog();
|
|
127
|
+
|
|
128
|
+
// pick the start (Dec 28), then pick an earlier day (ArrowUp = -1 week -> Dec 21)
|
|
129
|
+
await browser.keys(Key.ArrowRight);
|
|
130
|
+
await browser.keys(Key.Enter);
|
|
131
|
+
await browser.keys(Key.ArrowUp);
|
|
132
|
+
await browser.keys(Key.Enter);
|
|
133
|
+
|
|
134
|
+
// the earlier day becomes the start (from < to) and the range completes -> calendar closes
|
|
135
|
+
await expect(dialog).not.toBeDisplayedInViewport();
|
|
136
|
+
await expect(await DateRangePickerCO.getDayInputByIndex(0)).toHaveValue(expect.stringContaining('21'));
|
|
137
|
+
await expect(await DateRangePickerCO.getDayInputByIndex(1)).toHaveValue(expect.stringContaining('28'));
|
|
76
138
|
});
|
|
77
139
|
});
|
|
78
140
|
|
|
@@ -118,6 +118,12 @@ export default class DateRangePickerCO extends PageObject {
|
|
|
118
118
|
return $(`[data-testid="ds-controlled-date-time-picker-calendar-context-content-wrapper"]`);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
// The open calendar popover is the modal dialog (role="dialog"); it is the same DOM node as
|
|
122
|
+
// the contextual-menu wrapper above, exposed here under an accessibility-semantic name.
|
|
123
|
+
static async getCalendarDialog() {
|
|
124
|
+
return $(`[data-testid="ds-controlled-date-time-picker-calendar-context-content-wrapper"]`);
|
|
125
|
+
}
|
|
126
|
+
|
|
121
127
|
static async getCalendarHeaderLabel() {
|
|
122
128
|
return $(`[data-testid="ds-controlled-date-time-picker-calendar-header-label"]`);
|
|
123
129
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import DateTimePickerCO from '../DSControlledDateTimePickerCO';
|
|
2
|
+
import { type } from '../../helpers';
|
|
3
|
+
|
|
4
|
+
// Tests for the fix introduced in PUI-15900 (focus lost when the input holds a partial date
|
|
5
|
+
// while onCalendarOpenFocusedDay is set — the displayed month must follow the focused day so
|
|
6
|
+
// the prop date is rendered and receives focus on open).
|
|
7
|
+
if (!browser.capabilities['ice:options'].isPhone && !browser.capabilities['ice:options'].isTablet) {
|
|
8
|
+
describe('PUI-18784 - DSControlledDateTimePicker:: focus on calendar open with partial date -Func', () => {
|
|
9
|
+
beforeEach('loading page', async () => {
|
|
10
|
+
const errorOnGo = await DateTimePickerCO.emptyDatePickerStartingMonth.go();
|
|
11
|
+
if (errorOnGo) throw errorOnGo;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('01: empty input focuses the onCalendarOpenFocusedDay day (Dec 27) on open', async () => {
|
|
15
|
+
const trigger = await DateTimePickerCO.getTrigger();
|
|
16
|
+
await trigger.click();
|
|
17
|
+
|
|
18
|
+
const focusedDay = await DateTimePickerCO.getCalendarFocusedDay();
|
|
19
|
+
await expect(focusedDay).toHaveText('27');
|
|
20
|
+
await expect(focusedDay).toBeFocused();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('02: partial month still focuses the onCalendarOpenFocusedDay day (Dec 27) on open', async () => {
|
|
24
|
+
const month = await DateTimePickerCO.getMonthInput();
|
|
25
|
+
await month.click();
|
|
26
|
+
await type('1'); // partial date — month typed, no day/year
|
|
27
|
+
|
|
28
|
+
const trigger = await DateTimePickerCO.getTrigger();
|
|
29
|
+
await trigger.click();
|
|
30
|
+
|
|
31
|
+
// grid follows the focused day's month (December) so the prop date is visible + focused
|
|
32
|
+
const focusedDay = await DateTimePickerCO.getCalendarFocusedDay();
|
|
33
|
+
await expect(focusedDay).toHaveText('27');
|
|
34
|
+
await expect(focusedDay).toBeFocused();
|
|
35
|
+
|
|
36
|
+
// the displayed month must be December 2000 (the prop's month), not the typed January
|
|
37
|
+
const headerLabel = await DateTimePickerCO.getCalendarHeaderLabel();
|
|
38
|
+
await expect(headerLabel).toHaveText('December 2000');
|
|
39
|
+
|
|
40
|
+
// focus must NOT have landed on the previous-month navigation button
|
|
41
|
+
const prevMonthBtn = await DateTimePickerCO.getCalendarPrevMonthBtn();
|
|
42
|
+
await expect(prevMonthBtn).not.toBeFocused();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -11,14 +11,16 @@ if (!browser.capabilities['ice:options'].isPhone && !browser.capabilities['ice:o
|
|
|
11
11
|
await (await DSToggleCO.getToggle()).waitForExist();
|
|
12
12
|
});
|
|
13
13
|
it('01: should change state to ON', async () => {
|
|
14
|
-
await (await DSToggleCO.
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
await (await DSToggleCO.getSwitch()).click();
|
|
15
|
+
await expect(await DSToggleCO.getSwitch()).toHaveAttribute('aria-checked', 'true');
|
|
16
|
+
// the visible (presentational) label should track the state
|
|
17
|
+
await expect(await DSToggleCO.getStateLabel()).toHaveText('ON');
|
|
17
18
|
});
|
|
18
19
|
it('02: should change state to OFF', async () => {
|
|
19
|
-
await (await DSToggleCO.
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
await (await DSToggleCO.getSwitch()).click();
|
|
21
|
+
await expect(await DSToggleCO.getSwitch()).toHaveAttribute('aria-checked', 'false');
|
|
22
|
+
// the visible (presentational) label should track the state
|
|
23
|
+
await expect(await DSToggleCO.getStateLabel()).toHaveText('OFF');
|
|
22
24
|
});
|
|
23
25
|
});
|
|
24
26
|
describe('PUI-6760 - DSControlledToggle:: states on/off - keyboard navigation', () => {
|
|
@@ -34,18 +36,15 @@ if (!browser.capabilities['ice:options'].isPhone && !browser.capabilities['ice:o
|
|
|
34
36
|
});
|
|
35
37
|
it('02: should change state to ON (Enter)', async () => {
|
|
36
38
|
await browser.keys(Key.Enter);
|
|
37
|
-
|
|
38
|
-
await expect(toggleState).toBeDisplayed();
|
|
39
|
+
await expect(await DSToggleCO.getSwitch()).toHaveAttribute('aria-checked', 'true');
|
|
39
40
|
});
|
|
40
41
|
it('03: should change state to OFF (Space)', async () => {
|
|
41
42
|
await browser.keys(Key.Space);
|
|
42
|
-
|
|
43
|
-
await expect(toggleState).toBeDisplayed();
|
|
43
|
+
await expect(await DSToggleCO.getSwitch()).toHaveAttribute('aria-checked', 'false');
|
|
44
44
|
});
|
|
45
45
|
it('04: should change state to ON (Space)', async () => {
|
|
46
46
|
await browser.keys(Key.Space);
|
|
47
|
-
|
|
48
|
-
await expect(toggleState).toBeDisplayed();
|
|
47
|
+
await expect(await DSToggleCO.getSwitch()).toHaveAttribute('aria-checked', 'true');
|
|
49
48
|
});
|
|
50
49
|
});
|
|
51
50
|
|
|
@@ -56,10 +55,11 @@ if (!browser.capabilities['ice:options'].isPhone && !browser.capabilities['ice:o
|
|
|
56
55
|
await (await DSToggleCO.getToggle()).waitForExist();
|
|
57
56
|
});
|
|
58
57
|
it('01: should not change state in disabled toggle', async () => {
|
|
59
|
-
const
|
|
60
|
-
await
|
|
61
|
-
|
|
62
|
-
await
|
|
58
|
+
const disabledSwitch = await DSToggleCO.getSwitch(0);
|
|
59
|
+
const initialState = await disabledSwitch.getAttribute('aria-checked');
|
|
60
|
+
// click the container (the button itself is disabled and not interactable)
|
|
61
|
+
await (await DSToggleCO.getToggleByIndex(0)).click();
|
|
62
|
+
await expect(await DSToggleCO.getSwitch(0)).toHaveAttribute('aria-checked', initialState);
|
|
63
63
|
});
|
|
64
64
|
});
|
|
65
65
|
|
|
@@ -84,18 +84,15 @@ if (!browser.capabilities['ice:options'].isPhone && !browser.capabilities['ice:o
|
|
|
84
84
|
});
|
|
85
85
|
it('03: should not change state on [Enter] key press', async () => {
|
|
86
86
|
await browser.keys(Key.Enter);
|
|
87
|
-
|
|
88
|
-
await expect(toggleState).toBeDisplayed();
|
|
87
|
+
await expect(await DSToggleCO.getSwitch()).toHaveAttribute('aria-checked', 'false');
|
|
89
88
|
});
|
|
90
89
|
it('04: should not change state on [Space] key press', async () => {
|
|
91
90
|
await browser.keys(Key.Space);
|
|
92
|
-
|
|
93
|
-
await expect(toggleState).toBeDisplayed();
|
|
91
|
+
await expect(await DSToggleCO.getSwitch()).toHaveAttribute('aria-checked', 'false');
|
|
94
92
|
});
|
|
95
93
|
it('05: should not change state on click', async () => {
|
|
96
|
-
await (await DSToggleCO.
|
|
97
|
-
|
|
98
|
-
await expect(toggleState).toBeDisplayed();
|
|
94
|
+
await (await DSToggleCO.getSwitch()).click();
|
|
95
|
+
await expect(await DSToggleCO.getSwitch()).toHaveAttribute('aria-checked', 'false');
|
|
99
96
|
});
|
|
100
97
|
});
|
|
101
98
|
describe('PUI-8814 - DSControlledToggle:: Tabbing', () => {
|