Skip to content

Commit

Permalink
feat!: return promise from field mount fn (#1162)
Browse files Browse the repository at this point in the history
* feat: return promise from field mount fn

* docs: update specs

* refactor: simplify logic
  • Loading branch information
veinfors committed Mar 20, 2023
1 parent 39ec748 commit 4bdd762
Show file tree
Hide file tree
Showing 10 changed files with 501 additions and 58 deletions.
11 changes: 9 additions & 2 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,
onCtrlF,
}) {
const [initScrollPosIsSet, setInitScrollPosIsSet] = useState(false);
Expand All @@ -60,6 +61,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 @@ -80,11 +83,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 @@ -238,7 +246,6 @@ export default function ListBox({
selectionState.clearItemStates(false);
model.selectListObjectAll('/qListObjectDef');
};
const frequencyMax = useFrequencyMax(app, layout);

const { List, Grid } = getListBoxComponents({
direction,
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 @@ -79,6 +79,7 @@ function ListBoxInline({ options, layout }) {
calculatePagesHeight,
showGray = true,
scrollState = undefined,
renderedCallback,
} = options;
let { toolbar = true } = options;

Expand Down Expand Up @@ -393,6 +394,7 @@ function ListBoxInline({ options, layout }) {
state: currentScrollIndex,
set: setCurrentScrollIndex,
}}
renderedCallback={renderedCallback}
onCtrlF={onCtrlF}
/>
)}
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;
31 changes: 18 additions & 13 deletions apis/nucleus/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -448,13 +448,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 = {}) {
if (!element) {
throw new Error(`Element for ${fieldName || qId} not provided`);
}
Expand All @@ -463,19 +464,23 @@ 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 || '$',

return new Promise((resolve) => {
this._instance = ListBoxPortal({
element,
app,
fieldIdentifier,
qId,
options: getListboxPortalOptions({
onSelectionActivated,
onSelectionDeactivated,
...options,
}),
stateName: options.stateName || '$',
renderedCallback: resolve,
});
root.add(this._instance);
});
root.add(this._instance);
},
/**
* Unmounts the field listbox from the DOM.
Expand Down
63 changes: 36 additions & 27 deletions apis/stardust/api-spec/spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,15 @@
}
}
],
"returns": {
"description": "A promise that resolves when the data is fetched.",
"type": "Promise",
"generics": [
{
"type": "void"
}
]
},
"examples": [
"fieldInstance.mount(element);"
]
Expand Down Expand Up @@ -1374,6 +1383,33 @@
}
}
},
"Plugin": {
"description": "An object literal containing meta information about the plugin and a function containing the plugin implementation.",
"stability": "experimental",
"availability": {
"since": "1.2.0"
},
"kind": "interface",
"entries": {
"info": {
"description": "Object that can hold various meta info about the plugin",
"kind": "object",
"entries": {
"name": {
"description": "The name of the plugin",
"type": "string"
}
}
},
"fn": {
"description": "The implementation of the plugin. Input and return value is up to the plugin implementation to decide based on its purpose.",
"type": "function"
}
},
"examples": [
"const plugin = {\n info: {\n name: \"example-plugin\",\n type: \"meta-type\",\n },\n fn: () => {\n // Plugin implementation goes here\n }\n};"
]
},
"Flags": {
"kind": "interface",
"entries": {
Expand Down Expand Up @@ -1512,33 +1548,6 @@
}
}
},
"Plugin": {
"description": "An object literal containing meta information about the plugin and a function containing the plugin implementation.",
"stability": "experimental",
"availability": {
"since": "1.2.0"
},
"kind": "interface",
"entries": {
"info": {
"description": "Object that can hold various meta info about the plugin",
"kind": "object",
"entries": {
"name": {
"description": "The name of the plugin",
"type": "string"
}
}
},
"fn": {
"description": "The implementation of the plugin. Input and return value is up to the plugin implementation to decide based on its purpose.",
"type": "function"
}
},
"examples": [
"const plugin = {\n info: {\n name: \"example-plugin\",\n type: \"meta-type\",\n },\n fn: () => {\n // Plugin implementation goes here\n }\n};"
]
},
"LoadType": {
"kind": "interface",
"params": [
Expand Down
22 changes: 11 additions & 11 deletions apis/stardust/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ declare namespace stardust {
dense?: boolean;
stateName?: string;
properties?: object;
}): void;
}): Promise<void>;

/**
* Unmounts the field listbox from the DOM.
Expand Down Expand Up @@ -428,6 +428,16 @@ declare namespace stardust {

}

/**
* An object literal containing meta information about the plugin and a function containing the plugin implementation.
*/
interface Plugin {
info: {
name: string;
};
fn: ()=>void;
}

interface Flags {
/**
* Checks whether the specified flag is enabled.
Expand Down Expand Up @@ -470,16 +480,6 @@ declare namespace stardust {
type: "dimension" | "measure";
}

/**
* An object literal containing meta information about the plugin and a function containing the plugin implementation.
*/
interface Plugin {
info: {
name: string;
};
fn: ()=>void;
}

interface LoadType {
(type: {
name: string;
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

0 comments on commit 4bdd762

Please sign in to comment.