From ca46370b1b2bb5799f5e8363a12149a4b1fe7d8c Mon Sep 17 00:00:00 2001 From: Johan Lahti Date: Thu, 27 Apr 2023 12:12:15 +0200 Subject: [PATCH] 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 --- .../src/components/listbox/ListBoxInline.jsx | 4 + .../__tests__/list-box-inline.test.jsx | 3 + .../listbox-selection-toolbar.test.js | 143 ++++++++++++++++++ .../interactions/listbox-selection-toolbar.js | 52 ++++--- .../utils/__tests__/is-direct-query.test.js | 16 ++ .../listbox/utils/is-direct-query.js | 4 + 6 files changed, 198 insertions(+), 24 deletions(-) create mode 100644 apis/nucleus/src/components/listbox/interactions/__tests__/listbox-selection-toolbar.test.js create mode 100644 apis/nucleus/src/components/listbox/utils/__tests__/is-direct-query.test.js create mode 100644 apis/nucleus/src/components/listbox/utils/is-direct-query.js diff --git a/apis/nucleus/src/components/listbox/ListBoxInline.jsx b/apis/nucleus/src/components/listbox/ListBoxInline.jsx index 6da6e8a1d..58ddf2978 100644 --- a/apis/nucleus/src/components/listbox/ListBoxInline.jsx +++ b/apis/nucleus/src/components/listbox/ListBoxInline.jsx @@ -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; diff --git a/apis/nucleus/src/components/listbox/__tests__/list-box-inline.test.jsx b/apis/nucleus/src/components/listbox/__tests__/list-box-inline.test.jsx index 1cf4560e6..d4b45ea06 100644 --- a/apis/nucleus/src/components/listbox/__tests__/list-box-inline.test.jsx +++ b/apis/nucleus/src/components/listbox/__tests__/list-box-inline.test.jsx @@ -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('', () => { 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 =
; @@ -192,6 +194,7 @@ describe('', () => { 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 () => { diff --git a/apis/nucleus/src/components/listbox/interactions/__tests__/listbox-selection-toolbar.test.js b/apis/nucleus/src/components/listbox/interactions/__tests__/listbox-selection-toolbar.test.js new file mode 100644 index 000000000..314be4fbc --- /dev/null +++ b/apis/nucleus/src/components/listbox/interactions/__tests__/listbox-selection-toolbar.test.js @@ -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'); + }); + }); +}); diff --git a/apis/nucleus/src/components/listbox/interactions/listbox-selection-toolbar.js b/apis/nucleus/src/components/listbox/interactions/listbox-selection-toolbar.js index b6a67f85c..bb587717b 100644 --- a/apis/nucleus/src/components/listbox/interactions/listbox-selection-toolbar.js +++ b/apis/nucleus/src/components/listbox/interactions/listbox-selection-toolbar.js @@ -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); }; diff --git a/apis/nucleus/src/components/listbox/utils/__tests__/is-direct-query.test.js b/apis/nucleus/src/components/listbox/utils/__tests__/is-direct-query.test.js new file mode 100644 index 000000000..d06da5203 --- /dev/null +++ b/apis/nucleus/src/components/listbox/utils/__tests__/is-direct-query.test.js @@ -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); + }); +}); diff --git a/apis/nucleus/src/components/listbox/utils/is-direct-query.js b/apis/nucleus/src/components/listbox/utils/is-direct-query.js new file mode 100644 index 000000000..0a477f321 --- /dev/null +++ b/apis/nucleus/src/components/listbox/utils/is-direct-query.js @@ -0,0 +1,4 @@ +export default function isDirectQueryEnabled({ appLayout }) { + const isDQ = !!appLayout?.qIsDirectQueryMode; + return isDQ; +}