diff --git a/core/package.json b/core/package.json index 5697efd865..f12db2d7eb 100644 --- a/core/package.json +++ b/core/package.json @@ -77,7 +77,7 @@ "bundlesize": [ { "path": "./public/luigi.js", - "maxSize": "600 kB", + "maxSize": "650 kB", "compression": "none" }, { diff --git a/core/src/App.svelte b/core/src/App.svelte index 7928c72c01..526ff4251b 100644 --- a/core/src/App.svelte +++ b/core/src/App.svelte @@ -220,7 +220,7 @@ // Navigate to the raw path. Any errors/alerts are handled later. // Make sure we use `replaceState` instead of `pushState` method if navigation sync is disabled. - Routing.navigateTo(path, true, isNavigationSyncEnabled); + return Routing.navigateTo(path, true, isNavigationSyncEnabled); }; const removeQueryParams = (str) => str.split('?')[0]; @@ -241,7 +241,7 @@ }; const getUnsavedChangesModalPromise = (source) => { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { if (shouldShowUnsavedChangesModal(source)) { showUnsavedChangesModal().then( () => { @@ -254,7 +254,9 @@ } resolve(); }, - () => {} + () => { + reject(); + } ); } else { resolve(); @@ -1275,6 +1277,9 @@ if ('luigi.navigation.open' === e.data.msg) { isNavigateBack = false; + + const previousUrl = window.location.href; + const srcNode = isSpecialIframe ? iframe.luigi.currentNode : undefined; const srcPathParams = isSpecialIframe ? iframe.luigi.pathParams @@ -1284,25 +1289,58 @@ params; const isSpecial = newTab || modal || splitView || drawer; + const resolveRemotePromise = () => { + const remotePromise = GenericHelpers.getRemotePromise(e.data.remotePromiseId); + if (remotePromise) { + remotePromise.doResolve(); + } + }; + + const rejectRemotePromise = () => { + const remotePromise = GenericHelpers.getRemotePromise(e.data.remotePromiseId); + if (remotePromise) { + remotePromise.doReject(); + } + }; + + const checkResolve = checkLocationChange => { + if (!checkLocationChange || previousUrl !== window.location.href) { + resolveRemotePromise(); + } else { + rejectRemotePromise(); + } + }; + if (e.source !== window && !intent && params.link) { params.link = params.link.split('?')[0]; } if (!isSpecial) { - getUnsavedChangesModalPromise().then(() => { - isNavigationSyncEnabled = !withoutSync; - handleNavigation(e.data, config, srcNode, srcPathParams); - closeModal(); - closeSplitView(); - closeDrawer(); - isNavigationSyncEnabled = true; - }); + getUnsavedChangesModalPromise() + .then(() => { + isNavigationSyncEnabled = !e.data.params.withoutSync; + handleNavigation(e.data, config, srcNode, srcPathParams) + .then(() => { + checkResolve(true); + }) + .catch(() => { + rejectRemotePromise(); + }); + closeModal(); + closeSplitView(); + closeDrawer(); + isNavigationSyncEnabled = true; + }) + .catch(() => { + rejectRemotePromise(); + }); } else { let path = buildPath(e.data.params, srcNode, srcPathParams); path = GenericHelpers.addLeadingSlash(path); if (newTab) { - openViewInNewTab(path); + await openViewInNewTab(path); + checkResolve(); return; } @@ -1312,20 +1350,26 @@ path, pathExist ); + if (!path) { + rejectRemotePromise(); return; } + contentNode = node; if (modal !== undefined) { resetMicrofrontendModalData(); - openViewInModal(path, modal === true ? {} : modal); + await openViewInModal(path, modal === true ? {} : modal); + checkResolve(); } else if (splitView !== undefined) { - openSplitView(path, splitView); + await openSplitView(path, splitView); + checkResolve(); } else if (drawer !== undefined) { resetMicrofrontendDrawerData(); drawer.isDrawer = true; - openViewInDrawer(path, drawer); + await openViewInDrawer(path, drawer); + checkResolve(); } } } diff --git a/core/src/core-api/_internalLinkManager.js b/core/src/core-api/_internalLinkManager.js index 643588a316..d7a1e33be9 100644 --- a/core/src/core-api/_internalLinkManager.js +++ b/core/src/core-api/_internalLinkManager.js @@ -21,10 +21,10 @@ export class linkManager extends LuigiCoreAPIBase { }; } - navigate(path, preserveView, modalSettings, splitViewSettings, drawerSettings) { + async navigate(path, preserveView, modalSettings, splitViewSettings, drawerSettings) { if (this.options.errorSkipNavigation) { this.options.errorSkipNavigation = false; - return; + return Promise.reject(new Error('navigation skipped')); } this.options.preserveView = preserveView; @@ -32,9 +32,11 @@ export class linkManager extends LuigiCoreAPIBase { if (path === '/' && (modalSettings || splitViewSettings || drawerSettings)) { console.warn('Navigation with an absolute path prevented.'); - return; + return Promise.reject(new Error('Navigation with an absolute path prevented.')); } + const remotePromise = GenericHelpers.createRemotePromise(); + const navigationOpenMsg = { msg: 'luigi.navigation.open', params: Object.assign(this.options, { @@ -43,10 +45,12 @@ export class linkManager extends LuigiCoreAPIBase { modal: modalSettings, splitView: splitViewSettings, drawer: drawerSettings - }) + }), + remotePromiseId: remotePromise.id }; this.sendPostMessageToLuigiCore(navigationOpenMsg); + return remotePromise; } openAsModal(path, modalSettings = {}) { diff --git a/core/src/utilities/helpers/generic-helpers.js b/core/src/utilities/helpers/generic-helpers.js index 41ca8a39fe..45f2211b60 100644 --- a/core/src/utilities/helpers/generic-helpers.js +++ b/core/src/utilities/helpers/generic-helpers.js @@ -1,6 +1,5 @@ // Standalone or partly-standalone methods that are used widely through the whole app and are synchronous. -import { LuigiElements } from '../../core-api'; -import { LuigiConfig } from '../../core-api'; +import { LuigiElements, LuigiConfig } from '../../core-api'; class GenericHelpersClass { /** @@ -8,7 +7,7 @@ class GenericHelpersClass { * @returns random numeric value {number} * @private */ - getRandomId() /* istanbul ignore next */ { + getRandomId /* istanbul ignore next */() { // window.msCrypto for IE 11 return (window.crypto || window.msCrypto).getRandomValues(new Uint32Array(1))[0]; } @@ -21,9 +20,9 @@ class GenericHelpersClass { return anyParam && this.isFunction(anyParam.then); } - isIE() /* istanbul ignore next */ { + isIE /* istanbul ignore next */() { const ua = navigator.userAgent; - /* MSIE used to detect old browsers and Trident used to newer ones*/ + /* MSIE used to detect old browsers and Trident used to newer ones */ return Boolean(ua.includes('MSIE ') || ua.includes('Trident/')); } @@ -94,8 +93,8 @@ class GenericHelpersClass { */ getUrlParameter(name) { name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]'); - var regex = new RegExp('[\\?&]' + name + '=([^&#]*)'); - var result = regex.exec(window.location.search); + const regex = new RegExp('[\\?&]' + name + '=([^&#]*)'); + const result = regex.exec(window.location.search); return (result && decodeURIComponent(result[1].replace(/\+/g, ' '))) || ''; } @@ -154,7 +153,7 @@ class GenericHelpersClass { } getTrimmedUrl(path) { - const pathUrl = 0 < path.length ? this.getPathWithoutHash(path) : path; + const pathUrl = path.length > 0 ? this.getPathWithoutHash(path) : path; return this.trimTrailingSlash(pathUrl.split('?')[0]); } @@ -209,7 +208,7 @@ class GenericHelpersClass { return processedString; } - getInnerHeight() /* istanbul ignore next */ { + getInnerHeight /* istanbul ignore next */() { return LuigiElements.isCustomLuigiContainer() ? LuigiElements.getLuigiContainer().clientHeight : window.innerHeight; } @@ -288,11 +287,11 @@ class GenericHelpersClass { * ['1.3', '1.2', '1.4', '1.1'].sort(semverCompare) */ semverCompare(a, b) { - var pa = a.split('-')[0].split('.'); - var pb = b.split('-')[0].split('.'); - for (var i = 0; i < 3; i++) { - var na = Number(pa[i]); - var nb = Number(pb[i]); + const pa = a.split('-')[0].split('.'); + const pb = b.split('-')[0].split('.'); + for (let i = 0; i < 3; i++) { + const na = Number(pa[i]); + const nb = Number(pb[i]); if (na > nb) return 1; if (nb > na) return -1; if (!isNaN(na) && isNaN(nb)) return 1; @@ -316,6 +315,48 @@ class GenericHelpersClass { } return val; } + + /** + * Creates a remote promise. + * @returns {Promise} which returns true when the promise will be resolved and returns false if the promise will be rejected. + */ + createRemotePromise() { + let res, rej; + const prom = new Promise(resolve => { + res = () => { + resolve(true); + }; + rej = () => { + resolve(false); + }; + }); + + let luiRP = LuigiConfig._remotePromises; + if (!luiRP) { + luiRP = { + counter: 0, + promises: [] + }; + LuigiConfig._remotePromises = luiRP; + } + prom.id = luiRP.counter++; + luiRP.promises[prom.id] = prom; + + prom.doResolve = () => { + delete luiRP.promises[prom.id]; + res(); + }; + prom.doReject = () => { + delete luiRP.promises[prom.id]; + rej(); + }; + + return prom; + } + + getRemotePromise(id) { + return LuigiConfig._remotePromises ? LuigiConfig._remotePromises.promises[id] : undefined; + } } export const GenericHelpers = new GenericHelpersClass(); diff --git a/core/test/core-api/internal-link-manager.spec.js b/core/test/core-api/internal-link-manager.spec.js index 273a47317d..1de8373606 100644 --- a/core/test/core-api/internal-link-manager.spec.js +++ b/core/test/core-api/internal-link-manager.spec.js @@ -1,3 +1,5 @@ +import { GenericHelpers } from '../../src/utilities/helpers'; + const sinon = require('sinon'); import { linkManager } from '../../src/core-api/_internalLinkManager'; @@ -76,6 +78,38 @@ describe('linkManager', function() { lm.navigate(path, true, modalSettings, splitViewSettings, drawerSettings); lm.sendPostMessageToLuigiCore.calledOnceWithExactly(navigationOpenMsg); }); + + it('should call sendPostMessageToLuigiCore with Promise', () => { + this.options = { + preserveView: false, + nodeParams: {}, + errorSkipNavigation: false, + fromContext: null, + fromClosestContext: false, + relative: false, + link: '' + }; + const remotePromise = GenericHelpers.createRemotePromise(); + const modalSettings = { modalSetting: 'modalValue' }; + const splitViewSettings = { splitViewSetting: 'splitViewValue' }; + const drawerSettings = { drawerSetting: 'drawerValue' }; + const path = '/path'; + const relativePath = path[0] !== '/'; + const navigationOpenMsg = { + msg: 'luigi.navigation.open', + params: Object.assign(this.options, { + link: path, + relative: relativePath, + modal: modalSettings, + splitView: splitViewSettings, + drawer: drawerSettings + }), + remotePromiseId: remotePromise.id + }; + + lm.navigate(path, true, modalSettings, splitViewSettings, drawerSettings); + lm.sendPostMessageToLuigiCore.calledOnceWithExactly(navigationOpenMsg); + }); }); describe('openAsModal', () => { diff --git a/docs/luigi-client-api.md b/docs/luigi-client-api.md index 5cf0ebb926..fcd0b7b7bf 100644 --- a/docs/luigi-client-api.md +++ b/docs/luigi-client-api.md @@ -507,6 +507,7 @@ Navigates to the given path in the application hosted by Luigi. It contains eith - `drawerSettings.backdrop` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** By default, it is set to `false`. If it is set to `true` the rest of the screen has a backdrop. - `drawerSettings.size` **(`"l"` \| `"m"` \| `"s"` \| `"xs"`)** size of the drawer (optional, default `"s"`) + ##### Examples ```javascript diff --git a/docs/luigi-core-api.md b/docs/luigi-core-api.md index 7c9ef57333..14aeeef454 100644 --- a/docs/luigi-core-api.md +++ b/docs/luigi-core-api.md @@ -540,6 +540,8 @@ Navigates to the given path in the application. It contains either a full absolu - `drawerSettings.backdrop` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** By default, it is set to `false`. If it is set to `true` the rest of the screen has a backdrop. - `drawerSettings.size` **(`"l"` \| `"m"` \| `"s"` \| `"xs"`)** size of the drawer (optional, default `"s"`) +Returns **[promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** a promise which resolves to a Boolean variable specifying whether the navigation was executed or not. + ##### Examples ```javascript @@ -697,7 +699,7 @@ let pathExists; ); ``` -Returns **[promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** a promise which resolves to a Boolean variable specifying whether the path exists or not +Returns **[promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** a promise which resolves to a Boolean variable specifying whether the path exists or not. #### hasBack diff --git a/package-lock.json b/package-lock.json index 6d6b4fe6b4..df9964187a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "sapluigi", + "name": "rafalluigi", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/test/e2e-test-application/e2e/tests/1-angular/luigi-client-link-manager-features.spec.js b/test/e2e-test-application/e2e/tests/1-angular/luigi-client-link-manager-features.spec.js index d134de34fb..9203a87d3d 100644 --- a/test/e2e-test-application/e2e/tests/1-angular/luigi-client-link-manager-features.spec.js +++ b/test/e2e-test-application/e2e/tests/1-angular/luigi-client-link-manager-features.spec.js @@ -651,7 +651,6 @@ describe('Luigi client linkManager', () => { }); }); - describe('Webcomponent compound view test', () => { beforeEach(() => { cy.visitLoggedIn('/projects/pr1/wc_grid_compound'); @@ -664,8 +663,9 @@ describe('Luigi client linkManager', () => { it('open webcomponent btn', () => { cy.window().then(win => { - cy.wait(500); + cy.wait(700); cy.get('.wcContainer>div>div>*').then(container => { + cy.wait(500); const root = container.children().prevObject[0].shadowRoot; const wcContent = root.querySelector('button').innerText; @@ -739,7 +739,7 @@ describe('Luigi client linkManager', () => { cy.wait(500); const wcContentStart = container.children().prevObject[1].shadowRoot.querySelector('p').innerText; - + rootBtn.querySelector('button').click(); const wcContent = rootBtn.querySelector('button').innerText; expect(wcContent).to.equal('Start'); @@ -751,7 +751,7 @@ describe('Luigi client linkManager', () => { }); }); }); - + describe('linkManager preserveQueryParams features', () => { let $iframeBody; beforeEach(() => {