Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: return promise from field mount fn #1162

Merged
merged 5 commits into from
Mar 20, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions apis/nucleus/src/components/listbox/ListBox.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export default function ListBox({
selectDisabled = () => false,
keyScroll = { state: {}, reset: () => {} },
currentScrollIndex = { set: () => {} },
renderedCallback,
}) {
const [initScrollPosIsSet, setInitScrollPosIsSet] = useState(false);
const isSingleSelect = !!(layout && layout.qListObject.qDimensionInfo.qIsOneAndOnlyOne);
Expand All @@ -59,6 +60,8 @@ export default function ListBox({
// The time from scroll end until new data is being fetched, may be exposed in API later on.
const scrollTimeout = 0;

const { frequencyMax, awaitingFrequencyMax } = useFrequencyMax(app, layout);

const { isLoadingData, ...itemsLoader } = useItemsLoader({
local,
loaderRef,
Expand All @@ -79,11 +82,16 @@ export default function ListBox({
if (itemsLoader?.pages) {
selectionState.update({
setPages,
pages: itemsLoader?.pages,
pages: itemsLoader.pages,
isSingleSelect,
selectDisabled,
layout,
});

if (itemsLoader.pages.length && !awaitingFrequencyMax) {
// All necessary data fetching done - signal rendering done!
renderedCallback();
}
}

const isItemLoaded = useCallback(
Expand Down Expand Up @@ -224,8 +232,6 @@ export default function ListBox({
setLast: (last) => setFocusListItem((prevState) => ({ ...prevState, last })),
});

const frequencyMax = useFrequencyMax(app, layout);

const { List, Grid } = getListBoxComponents({
direction,
layout,
Expand Down
2 changes: 2 additions & 0 deletions apis/nucleus/src/components/listbox/ListBoxInline.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ function ListBoxInline({ options, layout }) {
calculatePagesHeight,
showGray = true,
scrollState = undefined,
renderedCallback,
} = options;
let { toolbar = true } = options;

Expand Down Expand Up @@ -377,6 +378,7 @@ function ListBoxInline({ options, layout }) {
state: currentScrollIndex,
set: setCurrentScrollIndex,
}}
renderedCallback={renderedCallback}
/>
)}
</AutoSizer>
Expand Down
14 changes: 12 additions & 2 deletions apis/nucleus/src/components/listbox/ListBoxPortal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const getOptions = (usersOptions = {}) => {
return squashedOptions;
};

function ListBoxWrapper({ app, fieldIdentifier, qId, stateName, element, options }) {
function ListBoxWrapper({ app, fieldIdentifier, qId, stateName, element, options, renderedCallback }) {
const { isExistingObject, hasExternalSelectionsApi } = identify({ qId, options });
const [changeCount, setChangeCount] = useState(0);

Expand Down Expand Up @@ -86,6 +86,7 @@ function ListBoxWrapper({ app, fieldIdentifier, qId, stateName, element, options
selections,
model,
app,
renderedCallback,
}),
[options, selections, model, app]
);
Expand All @@ -97,7 +98,15 @@ function ListBoxWrapper({ app, fieldIdentifier, qId, stateName, element, options
return <ListBoxInline options={opts} />;
}

export default function ListBoxPortal({ element, app, fieldIdentifier, qId, stateName = '$', options = {} }) {
export default function ListBoxPortal({
element,
app,
fieldIdentifier,
qId,
stateName = '$',
options = {},
renderedCallback,
}) {
return ReactDOM.createPortal(
<ListBoxWrapper
app={app}
Expand All @@ -106,6 +115,7 @@ export default function ListBoxPortal({ element, app, fieldIdentifier, qId, stat
qId={qId}
stateName={stateName}
options={options}
renderedCallback={renderedCallback}
/>,
element,
uid()
Expand Down
10 changes: 7 additions & 3 deletions apis/nucleus/src/components/listbox/hooks/useFrequencyMax.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { useState, useEffect } from 'react';
import { getFrequencyMax, needToFetchFrequencyMax } from '../utils/frequencyMaxUtil';

const useFrequencyMax = (app, layout) => {
const [frequencyMax, setFrequencyMax] = useState();

const needFetch = needToFetchFrequencyMax(layout);
const [frequencyMax, setFrequencyMax] = useState();
const [awaitingFrequencyMax, setAwaitingFrequencyMax] = useState(needFetch);

useEffect(() => {
if (!needFetch) {
Expand All @@ -13,11 +13,15 @@ const useFrequencyMax = (app, layout) => {
const fetch = async () => {
const newValue = await getFrequencyMax(layout, app);
setFrequencyMax(newValue);
setAwaitingFrequencyMax(false);
};
fetch();
}, [needFetch && layout]);

return needFetch ? frequencyMax : layout?.frequencyMax;
return {
frequencyMax: needFetch ? frequencyMax : layout?.frequencyMax,
awaitingFrequencyMax,
};
};

export default useFrequencyMax;
39 changes: 26 additions & 13 deletions apis/nucleus/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -446,13 +446,14 @@ function nuked(configuration = {}) {
* @param {boolean=} [options.dense=false] Reduces padding and text size (not applicable for existing objects)
* @param {string=} [options.stateName="$"] Sets the state to make selections in (not applicable for existing objects)
* @param {object=} [options.properties={}] Properties object to extend default properties with
* @returns {Promise<void>} A promise that resolves when the data is fetched.
*
* @since 1.1.0
* @instance
* @example
* fieldInstance.mount(element);
*/
mount(element, options = {}) {
async mount(element, options = {}) {
veinfors marked this conversation as resolved.
Show resolved Hide resolved
if (!element) {
throw new Error(`Element for ${fieldName || qId} not provided`);
}
Expand All @@ -461,19 +462,31 @@ function nuked(configuration = {}) {
}
const onSelectionActivated = () => fieldSels.emit('selectionActivated');
const onSelectionDeactivated = () => fieldSels.emit('selectionDeactivated');
this._instance = ListBoxPortal({
element,
app,
fieldIdentifier,
qId,
options: getListboxPortalOptions({
onSelectionActivated,
onSelectionDeactivated,
...options,
}),
stateName: options.stateName || '$',
let renderCount = 0;

return new Promise((resolve) => {
const renderedCallback = () => {
renderCount++;
if (renderCount === 1) {
resolve();
}
veinfors marked this conversation as resolved.
Show resolved Hide resolved
};

this._instance = ListBoxPortal({
element,
app,
fieldIdentifier,
qId,
options: getListboxPortalOptions({
onSelectionActivated,
onSelectionDeactivated,
...options,
}),
stateName: options.stateName || '$',
renderedCallback,
});
root.add(this._instance);
});
root.add(this._instance);
},
/**
* Unmounts the field listbox from the DOM.
Expand Down
36 changes: 36 additions & 0 deletions test/mashup/listbox/listbox.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<script src="/apis/stardust/dist/stardust.dev.js"></script>
<script src="/apis/enigma-mocker/dist/enigma-mocker.dev.js"></script>
<script src="scenarios.js"></script>

<style>
html,
body {
margin: 20px;
padding: 0;
background-color: #fafafa;
}

.wrapper {
display: flex;
}

.listbox1 {
flex: 0 0 600px;
position: relative;
width: 600px;
height: 400px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
}
</style>
</head>
<body>
<div class="wrapper">
<div class="listbox1"></div>
</div>
<div id="flow-tracker"></div>
</body>
</html>
35 changes: 35 additions & 0 deletions test/mashup/listbox/listbox.int.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
describe('listbox', () => {
const awaitText = async (selector, text, options = { timeout: 10000 }) => {
await page.waitForSelector(selector, { visible: true });
await page.waitForFunction(
(s, t) => document.querySelector(s) && document.querySelector(s).innerText.includes(t),
options,
`${selector}`,
text
);
};
const listboxSelector = `.listbox-container`;

function getScenarioUrl(scenario) {
return `${process.env.BASE_URL}/listbox/listbox.html?scenario=${scenario}`;
}

it('should resolve mount promise after data is fetched', async () => {
const url = getScenarioUrl('scenario1');
await page.goto(url);

await page.waitForSelector(listboxSelector, { visible: true });
await page.waitForSelector('#flow-tracker', { visible: true });
await awaitText('#flow-tracker', 'mount promise resolved');

// eslint-disable-next-line no-promise-executor-return
await new Promise((r) => setTimeout(r, 2000));
const innerText = await page.$eval('#flow-tracker', (el) => el.innerText);
expect(innerText.includes('getLayout')).equal(true);
expect(innerText.includes('getListObjectData')).equal(true);

const actions = innerText.slice(0, -1).split(',');
const lastAction = actions.pop();
expect(lastAction).equal('mount promise resolved');
});
});
Loading