@axinom/mosaic-ui 0.75.0-rc.5 → 0.77.0
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/dist/THIRD-PARTY-LICENSES.txt +1296 -0
- package/dist/cjs/components/ActionBar/ActionBar.d.ts +26 -0
- package/dist/cjs/components/ActionBar/ActionBar.d.ts.map +1 -0
- package/dist/cjs/components/ActionBar/ActionBar.model.d.ts +49 -0
- package/dist/cjs/components/ActionBar/ActionBar.model.d.ts.map +1 -0
- package/dist/cjs/components/ActionBar/ActionBarButton/ActionBarButton.d.ts +22 -0
- package/dist/cjs/components/ActionBar/ActionBarButton/ActionBarButton.d.ts.map +1 -0
- package/dist/cjs/components/ActionBar/helpers.d.ts +22 -0
- package/dist/cjs/components/ActionBar/helpers.d.ts.map +1 -0
- package/dist/cjs/components/ActionBar/index.d.ts +3 -0
- package/dist/cjs/components/ActionBar/index.d.ts.map +1 -0
- package/dist/cjs/components/Actions/Action/Action.d.ts.map +1 -1
- package/dist/cjs/components/ConfirmDialog/index.d.ts +1 -0
- package/dist/cjs/components/ConfirmDialog/index.d.ts.map +1 -1
- package/dist/cjs/components/ConfirmDialog/useConfirmableAction.d.ts +55 -0
- package/dist/cjs/components/ConfirmDialog/useConfirmableAction.d.ts.map +1 -0
- package/dist/cjs/components/Explorer/BulkEdit/useBulkEdit.d.ts.map +1 -1
- package/dist/cjs/components/Explorer/Explorer.d.ts.map +1 -1
- package/dist/cjs/components/Explorer/QuickEdit/useQuickEdit.d.ts +14 -3
- package/dist/cjs/components/Explorer/QuickEdit/useQuickEdit.d.ts.map +1 -1
- package/dist/cjs/components/Explorer/helpers/useActions.d.ts +15 -4
- package/dist/cjs/components/Explorer/helpers/useActions.d.ts.map +1 -1
- package/dist/cjs/components/Explorer/helpers/useSubtitle.d.ts.map +1 -1
- package/dist/cjs/components/List/List.d.ts +17 -3
- package/dist/cjs/components/List/List.d.ts.map +1 -1
- package/dist/cjs/components/List/List.model.d.ts +5 -1
- package/dist/cjs/components/List/List.model.d.ts.map +1 -1
- package/dist/cjs/components/List/ListHeader/ListHeader.d.ts +10 -1
- package/dist/cjs/components/List/ListHeader/ListHeader.d.ts.map +1 -1
- package/dist/cjs/components/List/ListRow/ListRow.d.ts +16 -5
- package/dist/cjs/components/List/ListRow/ListRow.d.ts.map +1 -1
- package/dist/cjs/components/List/ListRowRenderer/ListRowRenderer.d.ts.map +1 -1
- package/dist/cjs/components/List/helpers.d.ts +11 -3
- package/dist/cjs/components/List/helpers.d.ts.map +1 -1
- package/dist/cjs/components/List/useColumnsSize.d.ts +4 -2
- package/dist/cjs/components/List/useColumnsSize.d.ts.map +1 -1
- package/dist/cjs/components/PageHeader/PageHeader.d.ts +4 -1
- package/dist/cjs/components/PageHeader/PageHeader.d.ts.map +1 -1
- package/dist/cjs/components/PageHeader/PageHeader.model.d.ts +37 -1
- package/dist/cjs/components/PageHeader/PageHeader.model.d.ts.map +1 -1
- package/dist/cjs/components/PageHeader/PageHeaderAction/PageHeaderAction.d.ts +3 -0
- package/dist/cjs/components/PageHeader/PageHeaderAction/PageHeaderAction.d.ts.map +1 -1
- package/dist/cjs/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts +8 -0
- package/dist/cjs/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts.map +1 -1
- package/dist/cjs/components/PageHeader/helpers.d.ts +9 -0
- package/dist/cjs/components/PageHeader/helpers.d.ts.map +1 -0
- package/dist/cjs/components/index.d.ts +1 -0
- package/dist/cjs/components/index.d.ts.map +1 -1
- package/dist/cjs/hooks/index.d.ts +1 -0
- package/dist/cjs/hooks/index.d.ts.map +1 -1
- package/dist/cjs/hooks/useElementWidthObserver/useElementWidthObserver.d.ts.map +1 -0
- package/dist/cjs/index.js +4 -4
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/components/ActionBar/ActionBar.d.ts +26 -0
- package/dist/esm/components/ActionBar/ActionBar.d.ts.map +1 -0
- package/dist/esm/components/ActionBar/ActionBar.model.d.ts +49 -0
- package/dist/esm/components/ActionBar/ActionBar.model.d.ts.map +1 -0
- package/dist/esm/components/ActionBar/ActionBarButton/ActionBarButton.d.ts +22 -0
- package/dist/esm/components/ActionBar/ActionBarButton/ActionBarButton.d.ts.map +1 -0
- package/dist/esm/components/ActionBar/helpers.d.ts +22 -0
- package/dist/esm/components/ActionBar/helpers.d.ts.map +1 -0
- package/dist/esm/components/ActionBar/index.d.ts +3 -0
- package/dist/esm/components/ActionBar/index.d.ts.map +1 -0
- package/dist/esm/components/Actions/Action/Action.d.ts.map +1 -1
- package/dist/esm/components/ConfirmDialog/index.d.ts +1 -0
- package/dist/esm/components/ConfirmDialog/index.d.ts.map +1 -1
- package/dist/esm/components/ConfirmDialog/useConfirmableAction.d.ts +55 -0
- package/dist/esm/components/ConfirmDialog/useConfirmableAction.d.ts.map +1 -0
- package/dist/esm/components/Explorer/BulkEdit/useBulkEdit.d.ts.map +1 -1
- package/dist/esm/components/Explorer/Explorer.d.ts.map +1 -1
- package/dist/esm/components/Explorer/QuickEdit/useQuickEdit.d.ts +14 -3
- package/dist/esm/components/Explorer/QuickEdit/useQuickEdit.d.ts.map +1 -1
- package/dist/esm/components/Explorer/helpers/useActions.d.ts +15 -4
- package/dist/esm/components/Explorer/helpers/useActions.d.ts.map +1 -1
- package/dist/esm/components/Explorer/helpers/useSubtitle.d.ts.map +1 -1
- package/dist/esm/components/List/List.d.ts +17 -3
- package/dist/esm/components/List/List.d.ts.map +1 -1
- package/dist/esm/components/List/List.model.d.ts +5 -1
- package/dist/esm/components/List/List.model.d.ts.map +1 -1
- package/dist/esm/components/List/ListHeader/ListHeader.d.ts +10 -1
- package/dist/esm/components/List/ListHeader/ListHeader.d.ts.map +1 -1
- package/dist/esm/components/List/ListRow/ListRow.d.ts +16 -5
- package/dist/esm/components/List/ListRow/ListRow.d.ts.map +1 -1
- package/dist/esm/components/List/ListRowRenderer/ListRowRenderer.d.ts.map +1 -1
- package/dist/esm/components/List/helpers.d.ts +11 -3
- package/dist/esm/components/List/helpers.d.ts.map +1 -1
- package/dist/esm/components/List/useColumnsSize.d.ts +4 -2
- package/dist/esm/components/List/useColumnsSize.d.ts.map +1 -1
- package/dist/esm/components/PageHeader/PageHeader.d.ts +4 -1
- package/dist/esm/components/PageHeader/PageHeader.d.ts.map +1 -1
- package/dist/esm/components/PageHeader/PageHeader.model.d.ts +37 -1
- package/dist/esm/components/PageHeader/PageHeader.model.d.ts.map +1 -1
- package/dist/esm/components/PageHeader/PageHeaderAction/PageHeaderAction.d.ts +3 -0
- package/dist/esm/components/PageHeader/PageHeaderAction/PageHeaderAction.d.ts.map +1 -1
- package/dist/esm/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts +8 -0
- package/dist/esm/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts.map +1 -1
- package/dist/esm/components/PageHeader/helpers.d.ts +9 -0
- package/dist/esm/components/PageHeader/helpers.d.ts.map +1 -0
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/components/index.d.ts.map +1 -1
- package/dist/esm/hooks/index.d.ts +1 -0
- package/dist/esm/hooks/index.d.ts.map +1 -1
- package/dist/esm/hooks/useElementWidthObserver/useElementWidthObserver.d.ts.map +1 -0
- package/dist/esm/index.js +4 -4
- package/dist/esm/index.js.map +1 -1
- package/package.json +7 -3
- package/src/components/ActionBar/ActionBar.model.ts +61 -0
- package/src/components/ActionBar/ActionBar.module.scss +64 -0
- package/src/components/ActionBar/ActionBar.spec.tsx +398 -0
- package/src/components/ActionBar/ActionBar.stories.tsx +205 -0
- package/src/components/ActionBar/ActionBar.tsx +170 -0
- package/src/components/ActionBar/ActionBarButton/ActionBarButton.module.scss +140 -0
- package/src/components/ActionBar/ActionBarButton/ActionBarButton.tsx +193 -0
- package/src/components/ActionBar/helpers.spec.ts +41 -0
- package/src/components/ActionBar/helpers.ts +54 -0
- package/src/components/ActionBar/index.ts +2 -0
- package/src/components/Actions/Action/Action.spec.tsx +64 -2
- package/src/components/Actions/Action/Action.tsx +6 -1
- package/src/components/ConfirmDialog/index.ts +1 -0
- package/src/components/ConfirmDialog/useConfirmableAction.tsx +159 -0
- package/src/components/EmptyStation/EmptyStation.stories.tsx +97 -0
- package/src/components/Explorer/BulkEdit/useBulkEdit.tsx +2 -0
- package/src/components/Explorer/Explorer.spec.tsx +97 -8
- package/src/components/Explorer/Explorer.tsx +26 -13
- package/src/components/Explorer/NavigationExplorer/NavigationExplorer.stories.tsx +124 -1
- package/src/components/Explorer/QuickEdit/useQuickEdit.spec.tsx +94 -50
- package/src/components/Explorer/QuickEdit/useQuickEdit.tsx +101 -42
- package/src/components/Explorer/SelectionExplorer/SelectionExplorer.spec.tsx +46 -13
- package/src/components/Explorer/helpers/useActions.ts +125 -73
- package/src/components/Explorer/helpers/useSubtitle.spec.tsx +33 -23
- package/src/components/Explorer/helpers/useSubtitle.tsx +18 -26
- package/src/components/List/List.model.ts +5 -1
- package/src/components/List/List.module.scss +25 -6
- package/src/components/List/List.spec.tsx +498 -1
- package/src/components/List/List.stories.tsx +137 -8
- package/src/components/List/List.tsx +305 -194
- package/src/components/List/ListHeader/ListHeader.module.scss +14 -0
- package/src/components/List/ListHeader/ListHeader.spec.tsx +1 -0
- package/src/components/List/ListHeader/ListHeader.tsx +25 -8
- package/src/components/List/ListRow/ListRow.module.scss +1 -1
- package/src/components/List/ListRow/ListRow.spec.tsx +109 -22
- package/src/components/List/ListRow/ListRow.tsx +52 -13
- package/src/components/List/ListRowRenderer/ListRowRenderer.tsx +28 -22
- package/src/components/List/helpers.spec.ts +29 -0
- package/src/components/List/helpers.ts +17 -9
- package/src/components/List/useColumnsSize.ts +17 -7
- package/src/components/PageHeader/PageHeader.model.ts +38 -1
- package/src/components/PageHeader/PageHeader.module.scss +39 -18
- package/src/components/PageHeader/PageHeader.spec.tsx +407 -18
- package/src/components/PageHeader/PageHeader.stories.tsx +95 -12
- package/src/components/PageHeader/PageHeader.tsx +89 -38
- package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.model.ts +8 -0
- package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.module.scss +50 -42
- package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.spec.tsx +193 -1
- package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.stories.tsx +36 -0
- package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.tsx +81 -130
- package/src/components/PageHeader/helpers.spec.ts +60 -0
- package/src/components/PageHeader/helpers.ts +36 -0
- package/src/components/index.ts +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/styles/variables.scss +41 -7
- package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts +0 -35
- package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts.map +0 -1
- package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts +0 -6
- package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts.map +0 -1
- package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts +0 -3
- package/dist/cjs/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts.map +0 -1
- package/dist/cjs/components/PageHeader/helpers/useElementWidthObserver.d.ts.map +0 -1
- package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts +0 -35
- package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts.map +0 -1
- package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts +0 -6
- package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.d.ts.map +0 -1
- package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts +0 -3
- package/dist/esm/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.d.ts.map +0 -1
- package/dist/esm/components/PageHeader/helpers/useElementWidthObserver.d.ts.map +0 -1
- package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.module.scss +0 -77
- package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.spec.tsx +0 -122
- package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.tsx +0 -235
- package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContext.ts +0 -15
- package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroupsContextProvider.tsx +0 -37
- /package/dist/cjs/{components/PageHeader/helpers → hooks/useElementWidthObserver}/useElementWidthObserver.d.ts +0 -0
- /package/dist/esm/{components/PageHeader/helpers → hooks/useElementWidthObserver}/useElementWidthObserver.d.ts +0 -0
- /package/src/{components/PageHeader/helpers → hooks/useElementWidthObserver}/useElementWidthObserver.tsx +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { getVisibleActionCount, isActionBarNavigationAction } from './helpers';
|
|
3
|
+
|
|
4
|
+
describe('getVisibleActionCount', () => {
|
|
5
|
+
it('keeps every action when they all fit', () => {
|
|
6
|
+
expect(getVisibleActionCount([100, 100, 100], 300, 50)).toBe(3);
|
|
7
|
+
expect(getVisibleActionCount([100, 100, 100], 500, 50)).toBe(3);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('reserves room for the overflow menu once an action does not fit', () => {
|
|
11
|
+
// 299 available, 50 of it goes to the menu, which leaves room for two
|
|
12
|
+
// actions (200) but not for the third (300).
|
|
13
|
+
expect(getVisibleActionCount([100, 100, 100], 299, 50)).toBe(2);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('moves every action into the overflow menu when not even one fits', () => {
|
|
17
|
+
expect(getVisibleActionCount([100, 100], 120, 50)).toBe(0);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('handles an empty set of actions', () => {
|
|
21
|
+
expect(getVisibleActionCount([], 300, 50)).toBe(0);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('keeps a single action that fits exactly', () => {
|
|
25
|
+
expect(getVisibleActionCount([100], 100, 50)).toBe(1);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('isActionBarNavigationAction', () => {
|
|
30
|
+
it('is true for an action with a path', () => {
|
|
31
|
+
expect(isActionBarNavigationAction({ label: 'Open', path: '/open' })).toBe(
|
|
32
|
+
true,
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('is false for an action with a handler', () => {
|
|
37
|
+
expect(
|
|
38
|
+
isActionBarNavigationAction({ label: 'Run', onClick: vi.fn() }),
|
|
39
|
+
).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ActionBarAction,
|
|
3
|
+
ActionBarNavigationAction,
|
|
4
|
+
} from './ActionBar.model';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Determines how many actions of the action bar can be rendered next to each
|
|
8
|
+
* other, given the space that is available to them.
|
|
9
|
+
*
|
|
10
|
+
* Whenever not every action fits, room for the overflow menu is taken off the
|
|
11
|
+
* budget first, so that the menu holding the remaining actions is always
|
|
12
|
+
* reachable. A return value of 0 means that every action has to move into the
|
|
13
|
+
* overflow menu.
|
|
14
|
+
*
|
|
15
|
+
* @param actionWidths the width of each action, in the order they are rendered
|
|
16
|
+
* @param availableWidth the width the actions may occupy
|
|
17
|
+
* @param overflowMenuWidth the width of the overflow menu trigger
|
|
18
|
+
*/
|
|
19
|
+
export const getVisibleActionCount = (
|
|
20
|
+
actionWidths: number[],
|
|
21
|
+
availableWidth: number,
|
|
22
|
+
overflowMenuWidth: number,
|
|
23
|
+
): number => {
|
|
24
|
+
const totalWidth = actionWidths.reduce((total, width) => total + width, 0);
|
|
25
|
+
|
|
26
|
+
if (totalWidth <= availableWidth) {
|
|
27
|
+
return actionWidths.length;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const budget = availableWidth - overflowMenuWidth;
|
|
31
|
+
|
|
32
|
+
let usedWidth = 0;
|
|
33
|
+
let count = 0;
|
|
34
|
+
|
|
35
|
+
for (const width of actionWidths) {
|
|
36
|
+
if (usedWidth + width > budget) {
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
usedWidth += width;
|
|
41
|
+
count++;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return count;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Determines whether an action of the action bar navigates rather than runs a
|
|
49
|
+
* handler.
|
|
50
|
+
* @param action the action to check
|
|
51
|
+
*/
|
|
52
|
+
export const isActionBarNavigationAction = (
|
|
53
|
+
action: ActionBarAction,
|
|
54
|
+
): action is ActionBarNavigationAction => 'path' in action && !!action.path;
|
|
@@ -6,11 +6,11 @@ import {
|
|
|
6
6
|
waitFor,
|
|
7
7
|
} from '@testing-library/react';
|
|
8
8
|
import React from 'react';
|
|
9
|
-
import { BrowserRouter as Router } from 'react-router-dom';
|
|
9
|
+
import { MemoryRouter, Route, BrowserRouter as Router } from 'react-router-dom';
|
|
10
10
|
import { afterEach, describe, expect, it, test, vi } from 'vitest';
|
|
11
11
|
import { noop } from '../../../helpers/utils';
|
|
12
12
|
import type { ConfirmationConfig } from '../../ConfirmDialog';
|
|
13
|
-
import { IconName } from '../../Icons';
|
|
13
|
+
import { IconName, Icons } from '../../Icons';
|
|
14
14
|
import {
|
|
15
15
|
Action,
|
|
16
16
|
type ActionProps,
|
|
@@ -99,6 +99,68 @@ describe('Action', () => {
|
|
|
99
99
|
expect(actionContainer?.classList.contains('isDisabled')).toBe(true);
|
|
100
100
|
});
|
|
101
101
|
|
|
102
|
+
it('keeps a disabled link from navigating', () => {
|
|
103
|
+
const { getByTestId, queryByTestId } = render(
|
|
104
|
+
<MemoryRouter initialEntries={['/']}>
|
|
105
|
+
<Action
|
|
106
|
+
action={{ label: 'Export', path: '/export', isDisabled: true }}
|
|
107
|
+
/>
|
|
108
|
+
<Route path="/export">
|
|
109
|
+
<span data-test-id="export-page" />
|
|
110
|
+
</Route>
|
|
111
|
+
</MemoryRouter>,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const link = getByTestId('action');
|
|
115
|
+
|
|
116
|
+
// Out of reach of the keyboard, and inert if it is activated anyway.
|
|
117
|
+
expect(link).toHaveAttribute('aria-disabled', 'true');
|
|
118
|
+
expect(link).toHaveAttribute('tabindex', '-1');
|
|
119
|
+
|
|
120
|
+
fireEvent.click(link);
|
|
121
|
+
|
|
122
|
+
expect(queryByTestId('export-page')).toBeNull();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('navigates with a link that is not disabled', () => {
|
|
126
|
+
const { getByTestId } = render(
|
|
127
|
+
<MemoryRouter initialEntries={['/']}>
|
|
128
|
+
<Action action={{ label: 'Export', path: '/export' }} />
|
|
129
|
+
<Route path="/export">
|
|
130
|
+
<span data-test-id="export-page" />
|
|
131
|
+
</Route>
|
|
132
|
+
</MemoryRouter>,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const link = getByTestId('action');
|
|
136
|
+
|
|
137
|
+
expect(link).not.toHaveAttribute('aria-disabled');
|
|
138
|
+
expect(link).not.toHaveAttribute('tabindex');
|
|
139
|
+
|
|
140
|
+
fireEvent.click(link);
|
|
141
|
+
|
|
142
|
+
expect(getByTestId('export-page')).toBeInTheDocument();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it(`uses the icon with the enum value 0 rather than the default icon`, () => {
|
|
146
|
+
const { container } = render(
|
|
147
|
+
<Router>
|
|
148
|
+
<Action
|
|
149
|
+
action={{
|
|
150
|
+
...defaultNavigationProps.action,
|
|
151
|
+
icon: IconName.Archive,
|
|
152
|
+
}}
|
|
153
|
+
/>
|
|
154
|
+
</Router>,
|
|
155
|
+
);
|
|
156
|
+
const { container: archive } = render(<Icons icon={IconName.Archive} />);
|
|
157
|
+
|
|
158
|
+
expect(IconName.Archive).toBe(0);
|
|
159
|
+
expect(container.querySelector('svg')?.innerHTML).toBe(
|
|
160
|
+
archive.querySelector('svg')?.innerHTML,
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
|
|
102
164
|
it(`changes the icon if one provided by props`, () => {
|
|
103
165
|
const iconName = IconName.External;
|
|
104
166
|
const props = {
|
|
@@ -204,9 +204,14 @@ const NavigationAction: React.FC<{ action: NavigationActionData }> = ({
|
|
|
204
204
|
)}
|
|
205
205
|
data-test-id="action"
|
|
206
206
|
target={openInNewTab ? '_blank' : undefined}
|
|
207
|
+
// The styles keep the pointer off a disabled link. This keeps it out of
|
|
208
|
+
// reach of the keyboard, and from navigating if it is activated anyway.
|
|
209
|
+
aria-disabled={isDisabled || undefined}
|
|
210
|
+
tabIndex={isDisabled ? -1 : undefined}
|
|
211
|
+
onClick={isDisabled ? (event) => event.preventDefault() : undefined}
|
|
207
212
|
>
|
|
208
213
|
<span data-test-id="label">{label}</span>
|
|
209
|
-
<Icons icon={icon
|
|
214
|
+
<Icons icon={icon ?? defaultIcon} className={classes.icon} />
|
|
210
215
|
</Link>
|
|
211
216
|
);
|
|
212
217
|
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { ConfirmDialog, useConfirmationDelay } from './ConfirmDialog';
|
|
3
|
+
import type {
|
|
4
|
+
ConfirmationConfig,
|
|
5
|
+
ConfirmationMode,
|
|
6
|
+
} from './ConfirmDialog.models';
|
|
7
|
+
|
|
8
|
+
interface UseConfirmableActionProps {
|
|
9
|
+
/** How the action confirms before it runs. (default: 'None') */
|
|
10
|
+
confirmationMode?: ConfirmationMode;
|
|
11
|
+
/** Optional text overrides for the confirmation. */
|
|
12
|
+
confirmationConfig?: ConfirmationConfig;
|
|
13
|
+
/** Called once the action is confirmed, or immediately when no confirmation is required. */
|
|
14
|
+
onConfirmed: () => void;
|
|
15
|
+
/** Move the 'Advanced' dialog along the trigger element. (default: 0) */
|
|
16
|
+
offsetSkidding?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface UseConfirmableActionResult<T extends HTMLElement> {
|
|
20
|
+
/** Whether a confirmation is currently pending. */
|
|
21
|
+
isConfirming: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Whether the trigger should render `simpleConfirmation` in place of its
|
|
24
|
+
* label. Only ever true in 'Simple' mode.
|
|
25
|
+
*/
|
|
26
|
+
isSimpleConfirmation: boolean;
|
|
27
|
+
/** Click handler for the trigger element. */
|
|
28
|
+
onTriggerClicked: () => void;
|
|
29
|
+
/** Props that have to be spread on the trigger element. */
|
|
30
|
+
triggerProps: {
|
|
31
|
+
ref: React.LegacyRef<T>;
|
|
32
|
+
onMouseEnter: () => void;
|
|
33
|
+
onMouseLeave: () => void;
|
|
34
|
+
onMouseOver: () => void;
|
|
35
|
+
};
|
|
36
|
+
/** 'Click again to confirm' message, to be rendered instead of the label. */
|
|
37
|
+
simpleConfirmation: JSX.Element | null;
|
|
38
|
+
/** The confirmation dialog of the 'Advanced' mode, anchored to the trigger. */
|
|
39
|
+
advancedConfirmation: JSX.Element | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Covers the concern of confirming an action before it is executed, shared by
|
|
44
|
+
* the components that render actions.
|
|
45
|
+
*
|
|
46
|
+
* In 'Simple' mode the first click arms the confirmation and the trigger swaps
|
|
47
|
+
* its label for `simpleConfirmation`, the second click executes the action. In
|
|
48
|
+
* 'Advanced' mode the click opens `advancedConfirmation`, which executes the
|
|
49
|
+
* action once confirmed.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* const { onTriggerClicked, triggerProps, isSimpleConfirmation, simpleConfirmation, advancedConfirmation } =
|
|
53
|
+
* useConfirmableAction<HTMLButtonElement>({ confirmationMode, confirmationConfig, onConfirmed: onClick });
|
|
54
|
+
*
|
|
55
|
+
* <button {...triggerProps} onClick={onTriggerClicked}>
|
|
56
|
+
* {isSimpleConfirmation ? simpleConfirmation : label}
|
|
57
|
+
* </button>
|
|
58
|
+
* {advancedConfirmation}
|
|
59
|
+
*/
|
|
60
|
+
export const useConfirmableAction = <T extends HTMLElement>({
|
|
61
|
+
confirmationMode = 'None',
|
|
62
|
+
confirmationConfig = {},
|
|
63
|
+
onConfirmed,
|
|
64
|
+
offsetSkidding,
|
|
65
|
+
}: UseConfirmableActionProps): UseConfirmableActionResult<T> => {
|
|
66
|
+
const [confirmation, setConfirmation] = useState<boolean>(false);
|
|
67
|
+
// Popper needs the element in state, not in a ref, to know when it changes.
|
|
68
|
+
const [referenceElement, setReferenceElement] = useState<HTMLElement>();
|
|
69
|
+
const { isMouseOver, setIsMouseOver } = useConfirmationDelay({
|
|
70
|
+
confirmationMode,
|
|
71
|
+
confirmation,
|
|
72
|
+
setConfirmation,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const { title, body, cancelButtonText, confirmButtonText, onConfirmOpen } =
|
|
76
|
+
confirmationConfig;
|
|
77
|
+
|
|
78
|
+
const onTriggerClicked = (): void => {
|
|
79
|
+
switch (confirmationMode) {
|
|
80
|
+
case 'Simple':
|
|
81
|
+
!confirmation
|
|
82
|
+
? setConfirmation(true)
|
|
83
|
+
: (onConfirmed(), setConfirmation(false));
|
|
84
|
+
break;
|
|
85
|
+
case 'Advanced':
|
|
86
|
+
setConfirmation(true);
|
|
87
|
+
break;
|
|
88
|
+
default:
|
|
89
|
+
onConfirmed();
|
|
90
|
+
setConfirmation(false);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const isSimpleConfirmation = confirmation && confirmationMode === 'Simple';
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
isConfirming: confirmation,
|
|
99
|
+
isSimpleConfirmation,
|
|
100
|
+
onTriggerClicked,
|
|
101
|
+
triggerProps: {
|
|
102
|
+
ref: setReferenceElement as React.LegacyRef<T>,
|
|
103
|
+
onMouseEnter: () => setIsMouseOver(true),
|
|
104
|
+
onMouseLeave: () => setIsMouseOver(false),
|
|
105
|
+
onMouseOver: () => {
|
|
106
|
+
// Needed in cases where the mouse enters the component after another
|
|
107
|
+
// confirmation dialog was dismissed
|
|
108
|
+
if (!isMouseOver) {
|
|
109
|
+
setIsMouseOver(true);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
simpleConfirmation: isSimpleConfirmation ? (
|
|
114
|
+
<SimpleConfirmation
|
|
115
|
+
onConfirmOpen={onConfirmOpen}
|
|
116
|
+
data={{ mode: 'Simple' }}
|
|
117
|
+
/>
|
|
118
|
+
) : null,
|
|
119
|
+
advancedConfirmation:
|
|
120
|
+
confirmation && confirmationMode === 'Advanced' ? (
|
|
121
|
+
<ConfirmDialog
|
|
122
|
+
referenceElement={referenceElement}
|
|
123
|
+
title={title}
|
|
124
|
+
cancelButtonText={cancelButtonText}
|
|
125
|
+
confirmButtonText={confirmButtonText}
|
|
126
|
+
offsetSkidding={offsetSkidding}
|
|
127
|
+
onCancel={() => setConfirmation(false)}
|
|
128
|
+
onConfirm={() => {
|
|
129
|
+
onConfirmed();
|
|
130
|
+
setConfirmation(false);
|
|
131
|
+
}}
|
|
132
|
+
onConfirmOpen={(isOpen) =>
|
|
133
|
+
onConfirmOpen?.(isOpen, { mode: 'Advanced' })
|
|
134
|
+
}
|
|
135
|
+
>
|
|
136
|
+
{body}
|
|
137
|
+
</ConfirmDialog>
|
|
138
|
+
) : null,
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Message shown in place of the label of an action that is awaiting its second
|
|
144
|
+
* click, which also reports the confirmation state while it is mounted.
|
|
145
|
+
*/
|
|
146
|
+
const SimpleConfirmation: React.FC<{
|
|
147
|
+
onConfirmOpen: ConfirmationConfig['onConfirmOpen'];
|
|
148
|
+
data: { mode: ConfirmationMode };
|
|
149
|
+
}> = ({ onConfirmOpen, data }) => {
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
onConfirmOpen && onConfirmOpen(true, data);
|
|
152
|
+
return () => {
|
|
153
|
+
onConfirmOpen && onConfirmOpen(false, data);
|
|
154
|
+
};
|
|
155
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
156
|
+
}, []);
|
|
157
|
+
|
|
158
|
+
return <span data-test-id="confirm">Click again to confirm</span>;
|
|
159
|
+
};
|
|
@@ -4,6 +4,7 @@ import React from 'react';
|
|
|
4
4
|
import { action } from 'storybook/actions';
|
|
5
5
|
import { createGroups } from '../../helpers/storybook';
|
|
6
6
|
import { IconName } from '../Icons/Icons.models';
|
|
7
|
+
import { PageHeaderActionType } from '../PageHeader';
|
|
7
8
|
import { EmptyStation } from './EmptyStation';
|
|
8
9
|
|
|
9
10
|
const groups = createGroups({
|
|
@@ -90,3 +91,99 @@ export const Default: StoryObj<typeof EmptyStation> = {
|
|
|
90
91
|
subtitle: 'My Asset Type',
|
|
91
92
|
},
|
|
92
93
|
};
|
|
94
|
+
|
|
95
|
+
export const GroupedActions: StoryObj<typeof EmptyStation> = {
|
|
96
|
+
name: 'Grouped Header Actions',
|
|
97
|
+
parameters: {
|
|
98
|
+
docs: {
|
|
99
|
+
description: {
|
|
100
|
+
story: `Header actions can be grouped. A group renders a single action that opens an action bar with its child actions below the header, pushing the station content down.
|
|
101
|
+
Only one group can be open at a time. Standard actions and groups are separated by a spacer, and actions that do not fit move into the overflow menu of the bar.`,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
argTypes: {
|
|
106
|
+
actions: { ...groups.actions, control: false },
|
|
107
|
+
},
|
|
108
|
+
args: {
|
|
109
|
+
title: 'Title',
|
|
110
|
+
subtitle: 'My Asset Type',
|
|
111
|
+
actions: [
|
|
112
|
+
{
|
|
113
|
+
label: 'Undo',
|
|
114
|
+
icon: IconName.Undo,
|
|
115
|
+
kind: 'action',
|
|
116
|
+
onClick: action('Action.onClick'),
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
label: 'Bulk Actions',
|
|
120
|
+
icon: IconName.Bulk,
|
|
121
|
+
kind: 'group',
|
|
122
|
+
actions: [
|
|
123
|
+
{
|
|
124
|
+
label: 'Bulk Edit',
|
|
125
|
+
icon: IconName.BulkEdit,
|
|
126
|
+
actionType: PageHeaderActionType.Context,
|
|
127
|
+
tag: 'BETA',
|
|
128
|
+
onClick: action('BulkAction.onClick'),
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
label: 'Bulk Delete',
|
|
132
|
+
icon: IconName.Delete,
|
|
133
|
+
actionType: PageHeaderActionType.Context,
|
|
134
|
+
confirmationMode: 'Advanced',
|
|
135
|
+
onClick: action('BulkAction.onClick'),
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
label: 'Bulk Unpublish',
|
|
139
|
+
icon: IconName.Unpublish,
|
|
140
|
+
actionType: PageHeaderActionType.Context,
|
|
141
|
+
confirmationMode: 'Simple',
|
|
142
|
+
onClick: action('BulkAction.onClick'),
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
label: 'Disabled Bulk Action',
|
|
146
|
+
icon: IconName.ChevronRight,
|
|
147
|
+
disabled: true,
|
|
148
|
+
onClick: action('BulkAction.onClick'),
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
onActionsGroupToggled: async (expanded: boolean): Promise<void> => {
|
|
152
|
+
action('BulkActions.onActionsGroupToggled')(expanded);
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
label: 'Export',
|
|
157
|
+
icon: IconName.File,
|
|
158
|
+
kind: 'group',
|
|
159
|
+
actions: [
|
|
160
|
+
{
|
|
161
|
+
label: 'Export as CSV',
|
|
162
|
+
icon: IconName.ChevronRight,
|
|
163
|
+
onClick: action('ExportAction.onClick'),
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
label: 'Export as JSON',
|
|
167
|
+
icon: IconName.ChevronRight,
|
|
168
|
+
onClick: action('ExportAction.onClick'),
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
onActionsGroupToggled: async (expanded: boolean): Promise<void> => {
|
|
172
|
+
action('Export.onActionsGroupToggled')(expanded);
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
render: (args) => (
|
|
178
|
+
<EmptyStation {...args}>
|
|
179
|
+
<div>
|
|
180
|
+
<h1 style={{ textAlign: 'center' }}>Your station content goes here</h1>
|
|
181
|
+
{Array.from({ length: 3 }, (_, i) => i + 1).map((i) => (
|
|
182
|
+
<p key={i}>
|
|
183
|
+
{faker.lorem.paragraph(faker.datatype.number({ min: 10, max: 50 }))}
|
|
184
|
+
</p>
|
|
185
|
+
))}
|
|
186
|
+
</div>
|
|
187
|
+
</EmptyStation>
|
|
188
|
+
),
|
|
189
|
+
};
|
|
@@ -77,6 +77,8 @@ export const useBulkEdit = <T extends Data>({
|
|
|
77
77
|
? {
|
|
78
78
|
label: bulkEditRegistration.label ?? 'Edit',
|
|
79
79
|
icon: bulkEditRegistration.icon ?? IconName.BulkEdit,
|
|
80
|
+
// Stays highlighted while the panel it has opened is on display.
|
|
81
|
+
toggled: isBulkEditMode,
|
|
80
82
|
onClick: () => {
|
|
81
83
|
setIsBulkEditMode((prev) => !prev);
|
|
82
84
|
if (!isBulkEditMode) {
|
|
@@ -179,21 +179,22 @@ describe('Explorer', () => {
|
|
|
179
179
|
columns={[{ propertyName: 'id' }]}
|
|
180
180
|
dataProvider={provider}
|
|
181
181
|
stationKey="mock-key"
|
|
182
|
+
openBulkActionsOnStart={true}
|
|
182
183
|
bulkActions={[{ label: 'Something', onClick: vi.fn() }]}
|
|
183
184
|
/>,
|
|
184
185
|
);
|
|
185
186
|
});
|
|
186
187
|
|
|
187
|
-
// When no items are selected,
|
|
188
|
+
// When no items are selected, the actions on the bar should be disabled
|
|
188
189
|
await waitFor(() => {
|
|
189
190
|
const bulkActionButtons = container.querySelectorAll(
|
|
190
|
-
'
|
|
191
|
+
'[data-test-id="action-bar"] button[data-test-id="action"]',
|
|
191
192
|
);
|
|
192
193
|
expect(bulkActionButtons.length).toBeGreaterThan(0);
|
|
193
194
|
});
|
|
194
195
|
|
|
195
196
|
const bulkActionButtons = container.querySelectorAll(
|
|
196
|
-
'
|
|
197
|
+
'[data-test-id="action-bar"] button[data-test-id="action"]',
|
|
197
198
|
);
|
|
198
199
|
bulkActionButtons.forEach((button) => {
|
|
199
200
|
expect((button as HTMLButtonElement).disabled).toBe(true);
|
|
@@ -1777,7 +1778,7 @@ describe('Explorer', () => {
|
|
|
1777
1778
|
});
|
|
1778
1779
|
});
|
|
1779
1780
|
|
|
1780
|
-
it('
|
|
1781
|
+
it('reports the result counts on the action bar', async () => {
|
|
1781
1782
|
const [provider, spy] = getDataProvider();
|
|
1782
1783
|
const data = [{ id: 1 }, { id: 2 }, { id: 3 }];
|
|
1783
1784
|
|
|
@@ -1801,15 +1802,17 @@ describe('Explorer', () => {
|
|
|
1801
1802
|
);
|
|
1802
1803
|
|
|
1803
1804
|
await waitFor(() => {
|
|
1804
|
-
expect(
|
|
1805
|
+
expect(
|
|
1806
|
+
container.querySelector('[data-test-id="action-bar-label"]'),
|
|
1807
|
+
).toHaveTextContent('Total 5, Filtered 4');
|
|
1805
1808
|
});
|
|
1806
1809
|
});
|
|
1807
1810
|
|
|
1808
|
-
it(
|
|
1811
|
+
it('reports the selection on the action bar once items are selected', async () => {
|
|
1809
1812
|
const [provider, spy] = getDataProvider();
|
|
1810
1813
|
const data = [{ id: 1 }, { id: 2 }, { id: 3 }];
|
|
1811
1814
|
|
|
1812
|
-
spy.
|
|
1815
|
+
spy.mockReturnValue(
|
|
1813
1816
|
Promise.resolve({
|
|
1814
1817
|
totalCount: 5,
|
|
1815
1818
|
filteredCount: 4,
|
|
@@ -1829,9 +1832,26 @@ describe('Explorer', () => {
|
|
|
1829
1832
|
),
|
|
1830
1833
|
);
|
|
1831
1834
|
|
|
1835
|
+
// Opening the bulk actions alone changes nothing, the counts stay
|
|
1832
1836
|
await waitFor(() => {
|
|
1833
|
-
expect(
|
|
1837
|
+
expect(
|
|
1838
|
+
container.querySelector('[data-test-id="action-bar-label"]'),
|
|
1839
|
+
).toHaveTextContent('Total 5, Filtered 4');
|
|
1834
1840
|
});
|
|
1841
|
+
|
|
1842
|
+
const firstCheckbox = container
|
|
1843
|
+
.querySelectorAll('[data-test-id="list-entry"]')[0]
|
|
1844
|
+
.querySelector(
|
|
1845
|
+
'[data-test-id="list-entry-checkbox"]',
|
|
1846
|
+
) as HTMLButtonElement;
|
|
1847
|
+
|
|
1848
|
+
await act(async () => {
|
|
1849
|
+
fireEvent.click(firstCheckbox);
|
|
1850
|
+
});
|
|
1851
|
+
|
|
1852
|
+
expect(
|
|
1853
|
+
container.querySelector('[data-test-id="action-bar-label"]'),
|
|
1854
|
+
).toHaveTextContent('Selected 1');
|
|
1835
1855
|
});
|
|
1836
1856
|
|
|
1837
1857
|
describe('global states', () => {
|
|
@@ -2041,5 +2061,74 @@ describe('Explorer', () => {
|
|
|
2041
2061
|
// Note: Testing callback would require finding and clicking the bulk actions toggle button
|
|
2042
2062
|
});
|
|
2043
2063
|
|
|
2064
|
+
describe('Quick edit', () => {
|
|
2065
|
+
const renderQuickEdit = async (onItemClicked = vi.fn()) => {
|
|
2066
|
+
const [provider, spy] = getDataProvider();
|
|
2067
|
+
const data = [
|
|
2068
|
+
{ id: 1, title: 'One', desc: '' },
|
|
2069
|
+
{ id: 2, title: 'Two', desc: '' },
|
|
2070
|
+
{ id: 3, title: 'Three', desc: '' },
|
|
2071
|
+
];
|
|
2072
|
+
|
|
2073
|
+
spy.mockReturnValue(
|
|
2074
|
+
Promise.resolve({
|
|
2075
|
+
totalCount: data.length,
|
|
2076
|
+
filteredCount: data.length,
|
|
2077
|
+
data,
|
|
2078
|
+
}),
|
|
2079
|
+
);
|
|
2080
|
+
|
|
2081
|
+
const { container } = await actWithReturn(async () => {
|
|
2082
|
+
return render(
|
|
2083
|
+
<Explorer
|
|
2084
|
+
columns={mockListColumns}
|
|
2085
|
+
dataProvider={provider}
|
|
2086
|
+
stationKey="mock-key"
|
|
2087
|
+
onItemClicked={onItemClicked}
|
|
2088
|
+
quickEditRegistrations={[{ label: 'Details', component: <div /> }]}
|
|
2089
|
+
/>,
|
|
2090
|
+
);
|
|
2091
|
+
});
|
|
2092
|
+
|
|
2093
|
+
const rows = () =>
|
|
2094
|
+
container.querySelectorAll('[data-test-id="list-entry"]');
|
|
2095
|
+
|
|
2096
|
+
await waitFor(() => expect(rows()).toHaveLength(data.length));
|
|
2097
|
+
|
|
2098
|
+
const quickViewToggle = container.querySelector(
|
|
2099
|
+
'[data-test-id="page-header-actions"] [data-test-id="action"][title="Quick View"]',
|
|
2100
|
+
) as HTMLElement;
|
|
2101
|
+
|
|
2102
|
+
await act(async () => {
|
|
2103
|
+
fireEvent.click(quickViewToggle);
|
|
2104
|
+
});
|
|
2105
|
+
|
|
2106
|
+
return { rows };
|
|
2107
|
+
};
|
|
2108
|
+
|
|
2109
|
+
it('marks the item it opens on as selected in the list', async () => {
|
|
2110
|
+
const onItemClicked = vi.fn();
|
|
2111
|
+
const { rows } = await renderQuickEdit(onItemClicked);
|
|
2112
|
+
|
|
2113
|
+
expect(rows()[0]).toHaveClass('selected');
|
|
2114
|
+
expect(rows()[1]).not.toHaveClass('selected');
|
|
2115
|
+
// Marking the item is no click on it.
|
|
2116
|
+
expect(onItemClicked).not.toHaveBeenCalled();
|
|
2117
|
+
});
|
|
2118
|
+
|
|
2119
|
+
it('moves the selection to the item that is clicked', async () => {
|
|
2120
|
+
const { rows } = await renderQuickEdit();
|
|
2121
|
+
|
|
2122
|
+
await act(async () => {
|
|
2123
|
+
fireEvent.click(
|
|
2124
|
+
rows()[2].querySelector('[data-test-id="list-entry-select-button"]')!,
|
|
2125
|
+
);
|
|
2126
|
+
});
|
|
2127
|
+
|
|
2128
|
+
expect(rows()[2]).toHaveClass('selected');
|
|
2129
|
+
expect(rows()[0]).not.toHaveClass('selected');
|
|
2130
|
+
});
|
|
2131
|
+
});
|
|
2132
|
+
|
|
2044
2133
|
it.todo(`item selection should default to 'SINGLE_ITEMS'`);
|
|
2045
2134
|
});
|