Repository: SAP/luigi (js)

Issue:
Make it possible to preserve non-client viewgroup iframes
by adding an option for a viewGroup in viewGroupSettings, e.g. **noClientCheck** or sth., that prevents a nav check when reactivating the iframe, which currently leads to a reload because there is no **luigi.navigation.ok** response.

Pull request description:
Preserve non client viewgroup iframes
**Description**

Changes proposed in this pull request:

- adds option to preserve non client viewgroup iframes

**Related issue(s)**

Resolves #4258


Patch:
diff --git a/core/src/services/iframe.js b/core/src/services/iframe.js
index 8abf8b7a..137bb847 100644
--- a/core/src/services/iframe.js
+++ b/core/src/services/iframe.js
@@ -11,7 +11,7 @@ class IframeClass {
 
   getActiveIframe(node) {
     const children = [...node.children];
-    return children.filter(child => child.tagName === 'IFRAME').find(GenericHelpers.isElementVisible);
+    return children.filter((child) => child.tagName === 'IFRAME').find(GenericHelpers.isElementVisible);
   }
 
   setActiveIframeToPrevious(node) {
@@ -32,7 +32,7 @@ class IframeClass {
 
   removeInactiveIframes(node) {
     const children = Array.from(node.children);
-    children.forEach(child => {
+    children.forEach((child) => {
       if (!GenericHelpers.isElementVisible(child) && !child.vg && child.tagName === 'IFRAME') {
         node.removeChild(child);
       }
@@ -44,7 +44,7 @@ class IframeClass {
   }
 
   getPreservedViewsInDom(iframes) {
-    return iframes.filter(iframe => iframe.pv);
+    return iframes.filter((iframe) => iframe.pv);
   }
 
   canCache(viewGroup) {
@@ -70,7 +70,7 @@ class IframeClass {
     if (currentActiveIframe !== newActiveIframe) {
       let newActiveFound = false;
       const children = Array.from(container.children);
-      children.forEach(child => {
+      children.forEach((child) => {
         if (child === currentActiveIframe) {
           if (removeCurrentActive) {
             container.removeChild(child);
@@ -113,6 +113,16 @@ class IframeClass {
   }
 
   setOkResponseHandler(config, component, node) {
+    const noClientCheck = NavigationHelpers.getViewGroupSettings(config.iframe?.vg)?.noClientCheck;
+
+    /**
+     * check for `noClientCheck` attribute
+     * when set to `true`, it prevents a navigation check when reactivating the iframe
+     */
+    if (noClientCheck) {
+      return;
+    }
+
     /**
      * check if luigi responded
      * if not, callback again to replace the iframe
@@ -154,7 +164,18 @@ class IframeClass {
     if (!(config && config.iframe && config.iframe.luigi)) {
       return true;
     }
+
     const clientVersion = config.iframe.luigi.clientVersion;
+    const noClientCheck = NavigationHelpers.getViewGroupSettings(config.iframe.vg)?.noClientCheck;
+
+    /**
+     * check for `noClientCheck` attribute
+     * when set to `true`, it prevents a navigation check when reactivating the iframe
+     */
+    if (noClientCheck) {
+      return false;
+    }
+
     if (config.iframe.luigi.initOk === undefined) {
       // initial get-context request was not received
       return true;
@@ -165,6 +186,7 @@ class IframeClass {
     ) {
       return false;
     }
+
     return !config.iframe.luigi.initOk;
   }
 
@@ -213,7 +235,7 @@ class IframeClass {
       let targetIframe;
       if (!nextViewIsolated && componentData.viewGroup) {
         const iframes = IframeHelpers.getMainIframes();
-        const sameViewGroupIframes = iframes.filter(iframe => {
+        const sameViewGroupIframes = iframes.filter((iframe) => {
           return iframe.vg === componentData.viewGroup;
         });
         if (sameViewGroupIframes.length > 0) {
@@ -307,10 +329,10 @@ class IframeClass {
 
       const withSync = componentData.isNavigationSyncEnabled;
       if (withSync) {
-        IframeHelpers.getVisibleIframes().forEach(iframe => {
+        IframeHelpers.getVisibleIframes().forEach((iframe) => {
           if (iframe !== config.iframe) {
             if (iframe.userSettingsGroup) {
-              Luigi.readUserSettings().then(storedUserSettings => {
+              Luigi.readUserSettings().then((storedUserSettings) => {
                 IframeHelpers.sendMessageToIframe(iframe, {
                   msg: 'luigi.navigate',
                   context: {
diff --git a/docs/navigation-parameters-reference.md b/docs/navigation-parameters-reference.md
index db428527..19612947 100644
--- a/docs/navigation-parameters-reference.md
+++ b/docs/navigation-parameters-reference.md
@@ -249,15 +249,15 @@ settings: {
 - **type**: object
 - **description**: contains key-object pairs, where the key is the view group name as specified in the node parameters, and the object contains key-value pairs. In each key-value pair, the key is the feature name and the value is the actual setting. The following options are supported:
 - **attributes**:
+  - **noClientCheck**(boolean): when set to `true`, it prevents a navigation check when reactivating the iframe
   - **preloadUrl**(string): needs to be an absolute URL of a micro frontend belonging to a view group. It cannot be an URL of a node. It is recommended that you use a dedicated small, visually empty view, which imports Luigi Client and is fine with getting an empty context, for example, without an access token. The **preloadUrl** parameter
  is also required for view group caching in case you need a view group iframe to refresh whenever you navigate back to it.
-  - **loadOnStartup**(boolean): when set to `true`, it loads the respective view group with the respective **preloadUrl** in the background as soon as the app first starts. 
+  - **loadOnStartup**(boolean): when set to `true`, it loads the respective view group with the respective **preloadUrl** in the background as soon as the app first starts.
 
 ### globalContext
 - **type**: object
 - **description**: contains key-object pairs which are inherited from all node contexts.
 
-
 ## Node parameters
 Node parameters are all the parameters that can be added to an individual navigation node in the `nodes:` section of the Luigi configuration file.
 
@@ -866,7 +866,7 @@ navigation: {
 ### viewGroup
 - **type**: string
 - **description**: allows you to associate nodes to be rendered in the same iframe, as long as they belong to the same origin.
-The value of this parameter is considered as the view group ID. If you want to use this feature, you must set [preloadViewGroups](navigation-advanced.md#preloadViewGroups) to `true` and provide a preload URL for your view group ID in [viewGroupsSettings](navigation-advanced.md##viewGroupsSettings).
+The value of this parameter is considered as the view group ID. If you want to use this feature, you must set [preloadViewGroups](navigation-advanced.md#preloadViewGroups) to `true` and provide a preload URL for your view group ID in [viewGroupSettings](navigation-advanced.md#viewGroupSettings).
 For further explanations, see [this section](navigation-configuration.md#view-groups).
 - **example:**
 ```javascript


Test patch:
diff --git a/core/test/services/iframe.spec.js b/core/test/services/iframe.spec.js
index bf5cd70d..81feda50 100644
--- a/core/test/services/iframe.spec.js
+++ b/core/test/services/iframe.spec.js
@@ -26,7 +26,7 @@ describe('Iframe', () => {
       isNavigationSyncEnabled: true
     };
     component = {
-      set: obj => {
+      set: (obj) => {
         Object.assign(lastObj, obj);
       },
       get: () => lastObj,
@@ -38,9 +38,9 @@ describe('Iframe', () => {
     sinon.stub(GenericHelpers);
     GenericHelpers.getRandomId.returns('abc');
     sinon.stub(RoutingHelpers, 'substituteViewUrl');
-    GenericHelpers.isElementVisible.callsFake(element => element.visible);
+    GenericHelpers.isElementVisible.callsFake((element) => element.visible);
     sinon.stub(ViewUrlDecorator);
-    ViewUrlDecorator.applyDecorators.callsFake(url => url);
+    ViewUrlDecorator.applyDecorators.callsFake((url) => url);
 
     node = {
       children: [
@@ -77,7 +77,7 @@ describe('Iframe', () => {
           tagName: 'IFRAME'
         }
       ],
-      removeChild: child => {
+      removeChild: (child) => {
         node.children.forEach((c, i) => {
           if (c === child) {
             node.children.splice(i, 1);
@@ -416,20 +416,32 @@ describe('Iframe', () => {
       assert.isTrue(component.get().isNavigationSyncEnabled);
     });
   });
+
   describe('init handshake failed', () => {
     let someConfig = {};
+
     beforeEach(() => {
       someConfig = {
         iframe: {}
       };
     });
+
     it('init handshake failed no luigi object on iframe', () => {
       assert.equal(Iframe.initHandshakeFailed(someConfig), true);
     });
+
     it('init handshake failed initOk undefined', () => {
       someConfig.iframe.luigi = {};
       assert.equal(Iframe.initHandshakeFailed(someConfig), true);
     });
+
+    it('init handshake noClientCheck true', () => {
+      someConfig.iframe.luigi = {};
+      someConfig.iframe.vg = 'view-group-name';
+      sinon.stub(NavigationHelpers, 'getViewGroupSettings').callsFake(() => ({ noClientCheck: true }));
+      assert.equal(Iframe.initHandshakeFailed(someConfig), false);
+    });
+
     it('init handshake success', () => {
       someConfig.iframe.luigi = {
         initOk: true,
diff --git a/test/e2e-test-application/src/luigi-config/extended/navigation.js b/test/e2e-test-application/src/luigi-config/extended/navigation.js
index 998d930c..af63e41c 100644
--- a/test/e2e-test-application/src/luigi-config/extended/navigation.js
+++ b/test/e2e-test-application/src/luigi-config/extended/navigation.js
@@ -23,6 +23,7 @@ class Navigation {
       preloadUrl: '/sampleapp.html#/preload'
     },
     view_group_components: {
+      noClientCheck: true,
       preloadUrl: '/sampleapp.html#/view-group/preload'
     }
   };