refactor: limit selection actions in direct query (#1246)

* refactor: compensate for checkboxes in item width

* test: search highlight rendering test

* refactor: disable some select actions in dq mode

* fix: rm test

* refactor: add flags fallback to context

* refactor: send flags through options instead of
  context

* refactor: rm flags completely
This commit is contained in:
Johan Lahti
2023-04-27 12:12:15 +02:00
committed by GitHub
parent 6dbd394493
commit ca46370b1b
6 changed files with 198 additions and 24 deletions

View File

@@ -23,6 +23,7 @@ import { CELL_PADDING_LEFT, ICON_WIDTH, ICON_PADDING, BUTTON_ICON_WIDTH } from '
import useTempKeyboard from './components/useTempKeyboard';
import ListBoxError from './components/ListBoxError';
import useRect from '../../hooks/useRect';
import isDirectQueryEnabled from './utils/is-direct-query';
const PREFIX = 'ListBoxInline';
const classes = {
@@ -113,6 +114,8 @@ function ListBoxInline({ options, layout }) {
const { translator, keyboardNavigation, themeApi, constraints } = useContext(InstanceContext);
theme.listBox = addListboxTheme(themeApi);
const isDirectQuery = isDirectQueryEnabled({ appLayout: app?.layout });
const containerRef = useRef();
const [containerRectRef, containerRect] = useRect();
const [searchContainer, searchContainerRef] = useRefWithCallback();
@@ -240,6 +243,7 @@ function ListBoxInline({ options, layout }) {
model,
translator,
selectionState,
isDirectQuery,
});
const showTitle = true;

View File

@@ -16,6 +16,7 @@ import * as ListBoxModule from '../ListBox';
import * as ListBoxSearchModule from '../components/ListBoxSearch';
import * as listboxSelectionToolbarModule from '../interactions/listbox-selection-toolbar';
import * as addListboxTheme from '../assets/addListboxTheme';
import * as isDirectQueryEnabled from '../utils/is-direct-query';
const virtualizedModule = require('react-virtualized-auto-sizer');
const listboxKeyboardNavigationModule = require('../interactions/keyboard-navigation/keyboard-nav-container');
@@ -91,6 +92,7 @@ describe('<ListboxInline />', () => {
jest.spyOn(useLayoutModule, 'default').mockImplementation(() => [layout]);
jest.spyOn(listboxKeyboardNavigationModule, 'default').mockImplementation(getListboxInlineKeyboardNavigation);
jest.spyOn(addListboxTheme, 'default').mockImplementation(() => {});
jest.spyOn(isDirectQueryEnabled, 'default').mockImplementation(() => false);
ActionsToolbarModule.default = ActionsToolbar;
ListBoxModule.default = <div className="theListBox" />;
@@ -192,6 +194,7 @@ describe('<ListboxInline />', () => {
expect(selections.on.mock.calls[0][0]).toBe('activated');
expect(selections.on.mock.calls[1][0]).toBe('deactivated');
expect(selections.removeListener).not.toHaveBeenCalled();
expect(isDirectQueryEnabled.default).toHaveBeenCalled();
});
test('should render properly with search toggle option', async () => {

View File

@@ -0,0 +1,143 @@
import createListboxSelectionToolbar from '../listbox-selection-toolbar';
describe('getScrollIndex', () => {
let layout;
let model;
let translator;
let selectionState;
let isDirectQuery;
afterEach(() => {
jest.resetAllMocks();
});
afterAll(() => {
jest.restoreAllMocks();
});
beforeEach(() => {
isDirectQuery = false;
layout = {
qListObject: {
qDimensionInfo: {
qStateCounts: {
qOption: 2,
qAlternative: 2,
qExcluded: 2,
qDeselected: 2,
},
qIsOneAndOnlyOne: false,
},
},
};
model = {
selectListObjectAll: jest.fn(),
selectListObjectPossible: jest.fn(),
selectListObjectAlternative: jest.fn(),
selectListObjectExcluded: jest.fn(),
};
translator = {
get: jest.fn((t) => t),
};
selectionState = {
clearItemStates: jest.fn(),
};
});
const create = (overrides = {}) =>
createListboxSelectionToolbar({
layout,
model,
translator,
selectionState,
isDirectQuery,
...overrides,
});
it('should create all actions', () => {
const actions = create();
expect(actions).toHaveLength(4);
const [all, possible, alternative, excluded] = actions;
expect(all).toMatchObject({ key: 'selectAll', label: 'Selection.SelectAll', type: 'menu-icon-button' });
expect(possible).toMatchObject({
key: 'selectPossible',
label: 'Selection.SelectPossible',
type: 'menu-icon-button',
});
expect(alternative).toMatchObject({
key: 'selectAlternative',
label: 'Selection.SelectAlternative',
type: 'menu-icon-button',
});
expect(excluded).toMatchObject({
key: 'selectExcluded',
label: 'Selection.SelectExcluded',
type: 'menu-icon-button',
});
expect(all.enabled()).toEqual(true);
expect(possible.enabled()).toEqual(true);
expect(alternative.enabled()).toEqual(true);
expect(excluded.enabled()).toEqual(true);
});
it('should not create any actions if single select', () => {
layout.qListObject.qDimensionInfo.qIsOneAndOnlyOne = true;
const actions = create({ layout });
expect(actions).toEqual([]);
});
it('should only create two actions in direct query mode', () => {
const actions = create({ isDirectQuery: true });
expect(actions).toHaveLength(2);
const [all, possible] = actions;
expect(all).toMatchObject({ key: 'selectAll', label: 'Selection.SelectAll', type: 'menu-icon-button' });
expect(possible).toMatchObject({
key: 'selectPossible',
label: 'Selection.SelectPossible',
type: 'menu-icon-button',
});
});
describe('test each action', () => {
let all;
let possible;
let alternative;
let excluded;
beforeEach(() => {
const actions = create();
[all, possible, alternative, excluded] = actions;
expect(selectionState.clearItemStates).not.toHaveBeenCalled();
});
afterEach(() => {
expect(selectionState.clearItemStates).toHaveBeenCalledTimes(1);
});
it('select all', () => {
all.action();
expect(model.selectListObjectAll).toHaveBeenCalledTimes(1);
expect(model.selectListObjectAll).toBeCalledWith('/qListObjectDef');
});
it('select possible', () => {
possible.action();
expect(model.selectListObjectPossible).toHaveBeenCalledTimes(1);
expect(model.selectListObjectPossible).toBeCalledWith('/qListObjectDef');
});
it('select alternative', () => {
alternative.action();
expect(model.selectListObjectAlternative).toHaveBeenCalledTimes(1);
expect(model.selectListObjectAlternative).toBeCalledWith('/qListObjectDef');
});
it('select excluded', () => {
excluded.action();
expect(model.selectListObjectExcluded).toHaveBeenCalledTimes(1);
expect(model.selectListObjectExcluded).toBeCalledWith('/qListObjectDef');
});
});
});

View File

@@ -3,7 +3,7 @@ import { selectAlternative } from '@nebula.js/ui/icons/select-alternative';
import { selectPossible } from '@nebula.js/ui/icons/select-possible';
import { selectExcluded } from '@nebula.js/ui/icons/select-excluded';
export default ({ layout, model, translator, selectionState }) => {
export default ({ layout, model, translator, selectionState, isDirectQuery = false }) => {
if (layout.qListObject.qDimensionInfo.qIsOneAndOnlyOne) {
return [];
}
@@ -41,27 +41,31 @@ export default ({ layout, model, translator, selectionState }) => {
model.selectListObjectPossible('/qListObjectDef');
},
},
{
key: 'selectAlternative',
type: 'menu-icon-button',
label: translator.get('Selection.SelectAlternative'),
getSvgIconShape: selectAlternative,
enabled: canSelectAlternative,
action: () => {
selectionState.clearItemStates(false);
model.selectListObjectAlternative('/qListObjectDef');
},
},
{
key: 'selectExcluded',
type: 'menu-icon-button',
label: translator.get('Selection.SelectExcluded'),
getSvgIconShape: selectExcluded,
enabled: canSelectExcluded,
action: () => {
selectionState.clearItemStates(false);
model.selectListObjectExcluded('/qListObjectDef');
},
},
];
isDirectQuery
? false
: {
key: 'selectAlternative',
type: 'menu-icon-button',
label: translator.get('Selection.SelectAlternative'),
getSvgIconShape: selectAlternative,
enabled: canSelectAlternative,
action: () => {
selectionState.clearItemStates(false);
model.selectListObjectAlternative('/qListObjectDef');
},
},
isDirectQuery
? false
: {
key: 'selectExcluded',
type: 'menu-icon-button',
label: translator.get('Selection.SelectExcluded'),
getSvgIconShape: selectExcluded,
enabled: canSelectExcluded,
action: () => {
selectionState.clearItemStates(false);
model.selectListObjectExcluded('/qListObjectDef');
},
},
].filter(Boolean);
};

View File

@@ -0,0 +1,16 @@
const { default: isDirectQueryEnabled } = require('../is-direct-query');
describe('isDirectQuery', () => {
const appLayout = { qIsDirectQueryMode: false };
test('should return false since app is not a DQ app', async () => {
const dq = isDirectQueryEnabled({ appLayout });
expect(dq).toEqual(false);
});
test('should return true since it is a DQ app', async () => {
appLayout.qIsDirectQueryMode = true;
const dq = isDirectQueryEnabled({ appLayout });
expect(dq).toEqual(true);
});
});

View File

@@ -0,0 +1,4 @@
export default function isDirectQueryEnabled({ appLayout }) {
const isDQ = !!appLayout?.qIsDirectQueryMode;
return isDQ;
}