mirror of
https://github.com/qlik-oss/nebula.js.git
synced 2025-12-19 09:48:18 -05:00
refactor(listbox): Enable integration of Listbox by providing additional options (#742)
* refactor: support custom selections api * refactor: add update as option and unit tests * test: for listbox * docs: update * fix: user-select none and make example run * fix: prevent conditional hooks calls * refactor: prevent executing unnecessary code
This commit is contained in:
@@ -15,8 +15,17 @@ import useLayout from '../../hooks/useLayout';
|
||||
|
||||
import Row from './ListBoxRow';
|
||||
import Column from './ListBoxColumn';
|
||||
import { selectValues } from './listbox-selections';
|
||||
|
||||
export default function ListBox({ model, selections, direction, height, width, listLayout = 'vertical' }) {
|
||||
export default function ListBox({
|
||||
model,
|
||||
selections,
|
||||
direction,
|
||||
height,
|
||||
width,
|
||||
listLayout = 'vertical',
|
||||
update = undefined,
|
||||
}) {
|
||||
const [layout] = useLayout(model);
|
||||
const [pages, setPages] = useState(null);
|
||||
const loaderRef = useRef(null);
|
||||
@@ -35,11 +44,11 @@ export default function ListBox({ model, selections, direction, height, width, l
|
||||
return;
|
||||
}
|
||||
const elemNumber = +e.currentTarget.getAttribute('data-n');
|
||||
const elemNumbers = [elemNumber];
|
||||
const isSingleSelect = layout.qListObject.qDimensionInfo.qIsOneAndOnlyOne;
|
||||
|
||||
if (!Number.isNaN(elemNumber)) {
|
||||
selections.select({
|
||||
method: 'selectListObjectValues',
|
||||
params: ['/qListObjectDef', [elemNumber], !layout.qListObject.qDimensionInfo.qIsOneAndOnlyOne],
|
||||
});
|
||||
selectValues({ selections, elemNumbers, isSingleSelect });
|
||||
}
|
||||
},
|
||||
[
|
||||
@@ -55,8 +64,9 @@ export default function ListBox({ model, selections, direction, height, width, l
|
||||
return false;
|
||||
}
|
||||
local.current.checkIdx = index;
|
||||
const page = pages.filter((p) => p.qArea.qTop <= index && index < p.qArea.qTop + p.qArea.qHeight)[0];
|
||||
return page && page.qArea.qTop <= index && index < page.qArea.qTop + page.qArea.qHeight;
|
||||
const isLoaded = (p) => p.qArea.qTop <= index && index < p.qArea.qTop + p.qArea.qHeight;
|
||||
const page = pages.filter((p) => isLoaded(p))[0];
|
||||
return page && isLoaded(page);
|
||||
},
|
||||
[layout, pages]
|
||||
);
|
||||
@@ -102,7 +112,7 @@ export default function ListBox({ model, selections, direction, height, width, l
|
||||
[layout]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = () => {
|
||||
local.current.queue = [];
|
||||
local.current.validPages = false;
|
||||
if (loaderRef.current) {
|
||||
@@ -113,6 +123,15 @@ export default function ListBox({ model, selections, direction, height, width, l
|
||||
}
|
||||
loaderRef.current._listRef.scrollToItem(0);
|
||||
}
|
||||
};
|
||||
|
||||
if (update) {
|
||||
// Hand over the update function for manual refresh from hosting application.
|
||||
update.call(null, fetchData);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [layout]);
|
||||
|
||||
if (!layout) {
|
||||
@@ -137,6 +156,7 @@ export default function ListBox({ model, selections, direction, height, width, l
|
||||
return (
|
||||
<FixedSizeList
|
||||
direction={direction}
|
||||
data-testid="fixed-size-list"
|
||||
useIsScrolling
|
||||
style={{}}
|
||||
height={listHeight}
|
||||
|
||||
@@ -22,6 +22,7 @@ const useStyles = makeStyles((theme) => ({
|
||||
whiteSpace: 'nowrap',
|
||||
fontSize: '12px',
|
||||
lineHeight: '16px',
|
||||
userSelect: 'none',
|
||||
},
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
|
||||
@@ -29,7 +29,18 @@ export default function ListBoxPortal({ app, fieldIdentifier, stateName, element
|
||||
}
|
||||
|
||||
export function ListBoxInline({ app, fieldIdentifier, stateName = '$', options = {} }) {
|
||||
const { title, direction, listLayout, search = true, toolbar = true, properties = {} } = options;
|
||||
const {
|
||||
title,
|
||||
direction,
|
||||
listLayout,
|
||||
search = true,
|
||||
toolbar = true,
|
||||
properties = {},
|
||||
sessionModel = undefined,
|
||||
selectionsApi = undefined,
|
||||
update = undefined,
|
||||
} = options;
|
||||
|
||||
const listdef = {
|
||||
qInfo: {
|
||||
qType: 'njsListbox',
|
||||
@@ -59,9 +70,9 @@ export function ListBoxInline({ app, fieldIdentifier, stateName = '$', options =
|
||||
title,
|
||||
...properties,
|
||||
};
|
||||
let fieldName;
|
||||
|
||||
// Something something lib dimension
|
||||
let fieldName;
|
||||
if (fieldIdentifier.qLibraryId) {
|
||||
listdef.qListObjectDef.qLibraryId = fieldIdentifier.qLibraryId;
|
||||
fieldName = fieldIdentifier.qLibraryId;
|
||||
@@ -70,8 +81,17 @@ export function ListBoxInline({ app, fieldIdentifier, stateName = '$', options =
|
||||
fieldName = fieldIdentifier;
|
||||
}
|
||||
|
||||
let [model] = useSessionModel(listdef, sessionModel ? null : app, fieldName, stateName);
|
||||
if (sessionModel) {
|
||||
model = sessionModel;
|
||||
}
|
||||
|
||||
let selections = useObjectSelections(selectionsApi ? {} : app, model)[0];
|
||||
if (selectionsApi) {
|
||||
selections = selectionsApi;
|
||||
}
|
||||
|
||||
const theme = useTheme();
|
||||
const [model] = useSessionModel(listdef, app, fieldName, stateName);
|
||||
|
||||
const lock = useCallback(() => {
|
||||
model.lock('/qListObjectDef');
|
||||
@@ -83,7 +103,7 @@ export function ListBoxInline({ app, fieldIdentifier, stateName = '$', options =
|
||||
|
||||
const { translator } = useContext(InstanceContext);
|
||||
const moreAlignTo = useRef();
|
||||
const [selections] = useObjectSelections(app, model);
|
||||
|
||||
const [layout] = useLayout(model);
|
||||
const [showToolbar, setShowToolbar] = useState(false);
|
||||
|
||||
@@ -193,6 +213,7 @@ export function ListBoxInline({ app, fieldIdentifier, stateName = '$', options =
|
||||
listLayout={listLayout}
|
||||
height={height}
|
||||
width={width}
|
||||
update={update}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
|
||||
@@ -22,6 +22,7 @@ const useStyles = makeStyles((theme) => ({
|
||||
whiteSpace: 'nowrap',
|
||||
fontSize: '12px',
|
||||
lineHeight: '16px',
|
||||
userSelect: 'none',
|
||||
},
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
|
||||
@@ -1,34 +1,36 @@
|
||||
import React from 'react';
|
||||
import { create, act } from 'react-test-renderer';
|
||||
import { IconButton, Grid, Typography } from '@material-ui/core';
|
||||
|
||||
describe('<ListboxInline />', () => {
|
||||
let sandbox;
|
||||
const app = {};
|
||||
|
||||
const app = { key: 'app' };
|
||||
const fieldIdentifier = { qLibraryId: 'qLibraryId' };
|
||||
const stateName = '$';
|
||||
const options = {
|
||||
title: 'title',
|
||||
direction: 'vertical',
|
||||
listLayout: 'vertical',
|
||||
search: true,
|
||||
toolbar: true,
|
||||
properties: {},
|
||||
};
|
||||
let customSelectionsKey;
|
||||
let customSessionModelKey;
|
||||
|
||||
let options;
|
||||
let ListBoxInline;
|
||||
let useState;
|
||||
let useEffect;
|
||||
let useCallback;
|
||||
let useContext;
|
||||
let useRef;
|
||||
let sessionModel;
|
||||
let selections;
|
||||
let ActionsToolbar;
|
||||
let ListBoxSearch;
|
||||
let createListboxSelectionToolbar;
|
||||
let useTheme;
|
||||
let theme;
|
||||
let layout;
|
||||
let useSessionModel;
|
||||
let useObjectSelections;
|
||||
let selections;
|
||||
let renderer;
|
||||
let render;
|
||||
|
||||
const InstanceContext = React.createContext();
|
||||
|
||||
before(() => {
|
||||
sandbox = sinon.createSandbox({ useFakeTimers: true });
|
||||
@@ -36,22 +38,16 @@ describe('<ListboxInline />', () => {
|
||||
useState = sandbox.stub(React, 'useState');
|
||||
useEffect = sandbox.stub(React, 'useEffect');
|
||||
useCallback = sandbox.stub(React, 'useCallback');
|
||||
useContext = sandbox.stub(React, 'useContext');
|
||||
useRef = sandbox.stub(React, 'useRef');
|
||||
useSessionModel = sandbox.stub();
|
||||
useObjectSelections = sandbox.stub();
|
||||
|
||||
sessionModel = {
|
||||
key: 'session-model',
|
||||
lock: sandbox.stub(),
|
||||
unlock: sandbox.stub(),
|
||||
};
|
||||
|
||||
selections = {
|
||||
key: 'selections',
|
||||
isModal: () => false,
|
||||
isActive: () => 'isActive',
|
||||
on: sandbox.stub().callsFake((event, func) => (eventTriggered) => {
|
||||
if (event === eventTriggered) func();
|
||||
}),
|
||||
};
|
||||
ActionsToolbar = sandbox.stub();
|
||||
ListBoxSearch = sandbox.stub();
|
||||
createListboxSelectionToolbar = sandbox.stub();
|
||||
@@ -82,15 +78,16 @@ describe('<ListboxInline />', () => {
|
||||
Typography,
|
||||
}),
|
||||
],
|
||||
[require.resolve('react-virtualized-auto-sizer'), () => (props) => props.children],
|
||||
[require.resolve('react-virtualized-auto-sizer'), () => () => <div data-testid="virtualized-auto-sizer" />],
|
||||
[require.resolve('@nebula.js/ui/icons/unlock'), () => () => 'unlock'],
|
||||
[require.resolve('@nebula.js/ui/icons/lock'), () => () => 'lock'],
|
||||
[require.resolve('@nebula.js/ui/theme'), () => ({ makeStyles: () => () => ({ icon: 'icon' }), useTheme })],
|
||||
[require.resolve('../../../contexts/InstanceContext'), () => 'context'],
|
||||
[require.resolve('../../../hooks/useObjectSelections'), () => () => [selections]],
|
||||
[require.resolve('../../../hooks/useSessionModel'), () => () => [sessionModel]],
|
||||
[require.resolve('../../../contexts/InstanceContext'), () => InstanceContext],
|
||||
[require.resolve('../../../hooks/useObjectSelections'), () => useObjectSelections],
|
||||
[require.resolve('../../../hooks/useSessionModel'), () => useSessionModel],
|
||||
[require.resolve('../../../hooks/useLayout'), () => () => [layout]],
|
||||
[require.resolve('../../ActionsToolbar'), () => ActionsToolbar],
|
||||
[require.resolve('../ListBox'), () => <div className="TheListBox" />],
|
||||
[require.resolve('../ListBoxSearch'), () => ListBoxSearch],
|
||||
[require.resolve('../listbox-selection-toolbar'), () => createListboxSelectionToolbar],
|
||||
],
|
||||
@@ -99,9 +96,31 @@ describe('<ListboxInline />', () => {
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
selections = {
|
||||
key: 'selections',
|
||||
isModal: () => false,
|
||||
isActive: () => 'isActive',
|
||||
on: sandbox.stub().callsFake((event, func) => (eventTriggered) => {
|
||||
if (event === eventTriggered) func();
|
||||
}),
|
||||
};
|
||||
useSessionModel.returns([sessionModel]);
|
||||
useObjectSelections.returns([selections]);
|
||||
|
||||
options = {
|
||||
title: 'title',
|
||||
direction: 'vertical',
|
||||
listLayout: 'vertical',
|
||||
search: true,
|
||||
toolbar: true,
|
||||
properties: {},
|
||||
sessionModel: undefined,
|
||||
selectionsApi: undefined,
|
||||
update: undefined,
|
||||
};
|
||||
|
||||
theme.spacing.returns('padding');
|
||||
useState.callsFake((startValue) => [startValue, () => {}]);
|
||||
useContext.returns({ translator: 'translator' });
|
||||
useRef.returns({ current: 'current' });
|
||||
useTheme.returns(theme);
|
||||
createListboxSelectionToolbar.returns('actions');
|
||||
@@ -112,24 +131,24 @@ describe('<ListboxInline />', () => {
|
||||
useEffect
|
||||
.onCall(0)
|
||||
.callsFake((effectFunc, watchArr) => {
|
||||
expect(watchArr[0].key).to.equal('selections');
|
||||
expect(watchArr[0].key).to.equal(customSelectionsKey || 'selections');
|
||||
effectFunc();
|
||||
})
|
||||
.onCall(1)
|
||||
.callsFake((effectFunc, watchArr) => {
|
||||
expect(watchArr[0].key).to.equal('selections');
|
||||
expect(watchArr[0].key).to.equal(customSelectionsKey || 'selections');
|
||||
effectFunc();
|
||||
});
|
||||
|
||||
useCallback
|
||||
.onCall(0)
|
||||
.callsFake((effectFunc, watchArr) => {
|
||||
expect(watchArr).to.deep.equal([sessionModel]);
|
||||
expect(watchArr[0].key).to.equal(customSessionModelKey || 'session-model');
|
||||
return effectFunc;
|
||||
})
|
||||
.onCall(1)
|
||||
.callsFake((effectFunc, watchArr) => {
|
||||
expect(watchArr).to.deep.equal([sessionModel]);
|
||||
expect(watchArr[0].key).to.equal(customSessionModelKey || 'session-model');
|
||||
return effectFunc;
|
||||
});
|
||||
});
|
||||
@@ -142,16 +161,97 @@ describe('<ListboxInline />', () => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('should call expected stuff', () => {
|
||||
const response = ListBoxInline({ app, fieldIdentifier, stateName, options });
|
||||
describe('Check rendering with different options', () => {
|
||||
beforeEach(() => {
|
||||
render = async () => {
|
||||
await act(async () => {
|
||||
renderer = create(
|
||||
<InstanceContext.Provider value={{ translator: { get: (s) => s, language: () => 'sv' } }}>
|
||||
<ListBoxInline app={app} fieldIdentifier={fieldIdentifier} stateName={stateName} options={options} />
|
||||
</InstanceContext.Provider>
|
||||
);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
expect(response).to.be.an('object');
|
||||
expect(useEffect).calledTwice;
|
||||
expect(useState).calledOnce.calledWith(false);
|
||||
expect(useCallback).calledTwice;
|
||||
expect(theme.spacing).calledOnce;
|
||||
expect(sessionModel.lock).not.called;
|
||||
expect(sessionModel.unlock).not.called;
|
||||
expect(selections.on).calledTwice;
|
||||
it('should render with everything included', async () => {
|
||||
await render();
|
||||
const actionToolbars = renderer.root.findAllByType(ActionsToolbar);
|
||||
expect(actionToolbars.length).to.equal(1);
|
||||
|
||||
const typographs = renderer.root.findAllByType(Typography);
|
||||
expect(typographs.length).to.equal(1);
|
||||
|
||||
const listBoxSearches = renderer.root.findAllByType(ListBoxSearch);
|
||||
expect(listBoxSearches.length).to.equal(1);
|
||||
|
||||
const autoSizers = renderer.root.findAllByProps({ 'data-testid': 'virtualized-auto-sizer' });
|
||||
expect(autoSizers.length).to.equal(1);
|
||||
|
||||
expect(useSessionModel).calledOnce;
|
||||
expect(useSessionModel.args[0][1], 'app should not be null as when using a custom sessionModel').to.deep.equal({
|
||||
key: 'app',
|
||||
});
|
||||
expect(useObjectSelections).calledOnce;
|
||||
});
|
||||
|
||||
it('should render without toolbar', async () => {
|
||||
options.toolbar = false;
|
||||
await render();
|
||||
const actionToolbars = renderer.root.findAllByType(ActionsToolbar);
|
||||
expect(actionToolbars.length).to.equal(0);
|
||||
|
||||
const typographs = renderer.root.findAllByType(Typography);
|
||||
expect(typographs.length).to.equal(0);
|
||||
|
||||
const listBoxSearches = renderer.root.findAllByType(ListBoxSearch);
|
||||
expect(listBoxSearches.length).to.equal(1);
|
||||
});
|
||||
|
||||
it('should render without search', async () => {
|
||||
options.search = false;
|
||||
await render();
|
||||
const actionToolbars = renderer.root.findAllByType(ActionsToolbar);
|
||||
expect(actionToolbars.length).to.equal(1);
|
||||
|
||||
const typographs = renderer.root.findAllByType(Typography);
|
||||
expect(typographs.length).to.equal(1);
|
||||
|
||||
const listBoxSearches = renderer.root.findAllByType(ListBoxSearch);
|
||||
expect(listBoxSearches.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('should use a custom selectionsApi and sessionModel', async () => {
|
||||
const isModal = sandbox.stub();
|
||||
const on = sandbox.stub();
|
||||
const isActive = sandbox.stub();
|
||||
customSelectionsKey = 'custom-selections';
|
||||
customSessionModelKey = 'custom-session-model';
|
||||
options.selectionsApi = {
|
||||
key: 'custom-selections',
|
||||
isModal,
|
||||
on,
|
||||
isActive,
|
||||
};
|
||||
options.sessionModel = {
|
||||
key: 'custom-session-model',
|
||||
lock: sandbox.stub(),
|
||||
unlock: sandbox.stub(),
|
||||
};
|
||||
await render();
|
||||
|
||||
const actionToolbars = renderer.root.findAllByType(ActionsToolbar);
|
||||
expect(actionToolbars.length).to.equal(1);
|
||||
|
||||
expect(isModal).calledOnce;
|
||||
expect(selections.on, 'should not use default selections api').not.called;
|
||||
expect(on, 'should use custom selections api').calledTwice;
|
||||
expect(isActive).calledOnce;
|
||||
expect(useSessionModel).calledOnce;
|
||||
expect(useSessionModel.args[0][1], 'app should be null to prevent unncessary rendering').to.equal(null);
|
||||
expect(useObjectSelections).calledOnce;
|
||||
const [, ourSessionModel] = useObjectSelections.args[0];
|
||||
expect(ourSessionModel.key, 'should use custom session model').to.equal('custom-session-model');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import * as listboxSelections from '../listbox-selections';
|
||||
|
||||
describe('Listbox selections', () => {
|
||||
let sandbox;
|
||||
|
||||
before(() => {
|
||||
sandbox = sinon.createSandbox();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.reset();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('selectValues', () => {
|
||||
let selections;
|
||||
|
||||
beforeEach(() => {
|
||||
selections = { select: sandbox.stub().resolves(true) };
|
||||
});
|
||||
|
||||
it('should call select', () => {
|
||||
listboxSelections
|
||||
.selectValues({ selections, elemNumbers: [1, 2, 4], isSingleSelect: false })
|
||||
.then((successStory) => {
|
||||
expect(successStory).to.equal(true);
|
||||
});
|
||||
expect(selections.select).calledOnce.calledWithExactly({
|
||||
method: 'selectListObjectValues',
|
||||
params: ['/qListObjectDef', [1, 2, 4], true],
|
||||
});
|
||||
});
|
||||
|
||||
it('should call select with toggle false when single select', () => {
|
||||
listboxSelections.selectValues({ selections, elemNumbers: [2], isSingleSelect: true }).then((successStory) => {
|
||||
expect(successStory).to.equal(true);
|
||||
});
|
||||
expect(selections.select).calledOnce.calledWithExactly({
|
||||
method: 'selectListObjectValues',
|
||||
params: ['/qListObjectDef', [2], false],
|
||||
});
|
||||
});
|
||||
|
||||
it('should not call select when NaN values exist', () => {
|
||||
listboxSelections
|
||||
.selectValues({ selections, elemNumbers: [1, NaN, 4], isSingleSelect: false })
|
||||
.then((successStory) => {
|
||||
expect(successStory).to.equal(false);
|
||||
});
|
||||
expect(selections.select).not.called;
|
||||
});
|
||||
|
||||
it('should handle select failure and then resolve false', () => {
|
||||
selections.select.rejects(false);
|
||||
listboxSelections
|
||||
.selectValues({ selections, elemNumbers: [1, 2, 4], isSingleSelect: false })
|
||||
.then((successStory) => {
|
||||
expect(successStory).to.equal(false);
|
||||
});
|
||||
expect(selections.select).calledOnce;
|
||||
});
|
||||
});
|
||||
});
|
||||
138
apis/nucleus/src/components/listbox/__tests__/list-box.spec.jsx
Normal file
138
apis/nucleus/src/components/listbox/__tests__/list-box.spec.jsx
Normal file
@@ -0,0 +1,138 @@
|
||||
import React from 'react';
|
||||
import { create, act } from 'react-test-renderer';
|
||||
|
||||
describe('<Listbox />', () => {
|
||||
let sandbox;
|
||||
|
||||
let args;
|
||||
let layout;
|
||||
let selectValues;
|
||||
let selections;
|
||||
let renderer;
|
||||
let ListBox;
|
||||
let render;
|
||||
let pages;
|
||||
let FixedSizeList;
|
||||
|
||||
before(() => {
|
||||
sandbox = sinon.createSandbox({ useFakeTimers: true });
|
||||
|
||||
selectValues = sandbox.stub();
|
||||
|
||||
layout = {
|
||||
qSelectionInfo: { qInSelections: false },
|
||||
qListObject: {
|
||||
qSize: { qcy: 2 },
|
||||
qDimensionInfo: { qLocked: false, qIsOneAndOnlyOne: false },
|
||||
qStateCounts: { qSelected: 2, qSelectedExcluded: 10, qLocked: 0, qLockedExcluded: 0 },
|
||||
},
|
||||
};
|
||||
|
||||
FixedSizeList = sandbox.stub().callsFake((funcArgs) => {
|
||||
const { children } = funcArgs;
|
||||
const RowOrColumn = children;
|
||||
return <RowOrColumn index={funcArgs.itemCount} style={funcArgs.style} data={funcArgs.itemData} />;
|
||||
});
|
||||
|
||||
[{ default: ListBox }] = aw.mock(
|
||||
[
|
||||
[require.resolve('react-window'), () => ({ FixedSizeList })],
|
||||
[require.resolve('../../../hooks/useLayout'), () => () => [layout]],
|
||||
[require.resolve('../listbox-selections'), () => ({ selectValues })],
|
||||
[
|
||||
require.resolve('react-window-infinite-loader'),
|
||||
() => (props) => {
|
||||
const func = props.children;
|
||||
return func({ onItemsRendered: {}, ref: {} });
|
||||
},
|
||||
],
|
||||
[require.resolve('../ListBoxRow'), () => () => <div className="a-value-row" />],
|
||||
[require.resolve('../ListBoxColumn'), () => () => <div className="a-value-column" />],
|
||||
],
|
||||
['../ListBox']
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
pages = [{ qArea: { qTop: 1, qHeight: 100 } }];
|
||||
|
||||
selections = { key: 'selections' };
|
||||
args = {
|
||||
model: {
|
||||
getListObjectData: sandbox.stub().resolves(pages),
|
||||
},
|
||||
selections,
|
||||
direction: 'ltr',
|
||||
height: 200,
|
||||
width: 100,
|
||||
listLayout: 'vertical',
|
||||
update: sandbox.stub(),
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.reset();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('Check rendering with different options', () => {
|
||||
beforeEach(() => {
|
||||
render = async () => {
|
||||
await act(async () => {
|
||||
renderer = create(
|
||||
<ListBox
|
||||
selections={args.selections}
|
||||
direction={args.direction}
|
||||
height={args.height}
|
||||
width={args.width}
|
||||
listLayout={args.listLayout}
|
||||
update={args.update}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.reset();
|
||||
renderer.unmount();
|
||||
});
|
||||
|
||||
it('should render and call stuff', async () => {
|
||||
await render();
|
||||
|
||||
// check rendering
|
||||
const fixedSizeLists = renderer.root.findAllByType(FixedSizeList);
|
||||
expect(fixedSizeLists.length).to.equal(1);
|
||||
|
||||
const [Container] = fixedSizeLists;
|
||||
const rows = Container.findAllByProps({ className: 'a-value-row' });
|
||||
const columns = Container.findAllByProps({ className: 'a-value-column' });
|
||||
expect(rows.length).to.equal(1);
|
||||
expect(columns.length).to.equal(0);
|
||||
|
||||
// onClick with valid numbers should call selectValues
|
||||
const { onClick } = Container.props.itemData;
|
||||
expect(selectValues).not.called;
|
||||
onClick({
|
||||
currentTarget: { getAttribute: () => 1 },
|
||||
});
|
||||
expect(selectValues).calledOnce.calledWithExactly({
|
||||
selections,
|
||||
elemNumbers: [1],
|
||||
isSingleSelect: false,
|
||||
});
|
||||
|
||||
// Test on click with NaN values
|
||||
selectValues.reset();
|
||||
expect(selectValues).not.called;
|
||||
onClick({
|
||||
currentTarget: { getAttribute: () => NaN },
|
||||
});
|
||||
expect(selectValues).not.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
17
apis/nucleus/src/components/listbox/listbox-selections.js
Normal file
17
apis/nucleus/src/components/listbox/listbox-selections.js
Normal file
@@ -0,0 +1,17 @@
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export async function selectValues({ selections, elemNumbers, isSingleSelect = false }) {
|
||||
const SUCCESS = false;
|
||||
let resolved = Promise.resolve(SUCCESS);
|
||||
const hasNanValues = elemNumbers.some((elemNumber) => Number.isNaN(elemNumber));
|
||||
if (!hasNanValues) {
|
||||
const elemNumbersToSelect = elemNumbers;
|
||||
resolved = selections
|
||||
.select({
|
||||
method: 'selectListObjectValues',
|
||||
params: ['/qListObjectDef', elemNumbersToSelect, !isSingleSelect],
|
||||
})
|
||||
.then((success) => success !== false)
|
||||
.catch(() => false);
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
@@ -326,6 +326,10 @@ function nuked(configuration = {}) {
|
||||
throw new Error(`Field identifier must be provided`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {function(function)} ReceiverFunction A callback function which receives another function as input.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @alias FieldInstance
|
||||
@@ -344,6 +348,9 @@ function nuked(configuration = {}) {
|
||||
* @param {boolean=} [options.toolbar=true] To show the toolbar
|
||||
* @param {boolean=} [options.stateName="$"] Sets the state to make selections in
|
||||
* @param {object=} [options.properties={}] Properties object to extend default properties with
|
||||
* @param {object} [options.sessionModel] Use a custom sessionModel.
|
||||
* @param {object} [options.selectionsApi] Use a custom selectionsApi to customize how values are selected.
|
||||
* @param {ReceiverFunction} [options.update] A function which receives an update function which upon call will trigger a data fetch.
|
||||
* @since 1.1.0
|
||||
* @example
|
||||
* fieldInstance.mount(element);
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
"build": "parcel build index.html"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nebula.js/stardust": "latest",
|
||||
"@nebula.js/sn-bar-chart": "latest",
|
||||
"@nebula.js/sn-line-chart": "latest",
|
||||
"@nebula.js/stardust": "latest",
|
||||
"enigma.js": "2.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.12.3",
|
||||
"parcel": "^2.0.0-rc.0"
|
||||
"parcel-bundler": "^1.12.5"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user