Repository: SAP/luigi (js)

Issue:
luigi-compound-container cascading context update
on initial rendering of compound children, they get a merged context object (https://github.com/SAP/luigi/blob/5517e98f14ee511cd22a6558d28d044fb1928e2a/container/src/services/webcomponents.service.ts#L653)
when updating the context at the compound wc, the children don't get an updated ctx, only the "parent" mfe, if present.
Enhance updateContext (https://github.com/SAP/luigi/blob/5517e98f14ee511cd22a6558d28d044fb1928e2a/container/src/LuigiCompoundContainer.svelte#L111) to also update compound childrens context.

Pull request:
Improves context updating method
**Description**

Changes proposed in this pull request:

- improves context updating method in compound container
- adds test for compound container scenario

**Related issue(s)**

Resolves #3826 


Patch:
diff --git a/container/src/LuigiCompoundContainer.svelte b/container/src/LuigiCompoundContainer.svelte
index e31fcb17..cfbe262d 100644
--- a/container/src/LuigiCompoundContainer.svelte
+++ b/container/src/LuigiCompoundContainer.svelte
@@ -108,7 +108,17 @@
       return;
     }
     thisComponent.updateContext = (contextObj: any, internal?: any) => {
-      (thisComponent.getNoShadow() ? thisComponent : mainComponent)._luigi_mfe_webcomponent.context = contextObj;
+      const rootElement = thisComponent.getNoShadow() ? thisComponent : mainComponent;
+      rootElement._luigi_mfe_webcomponent.context = contextObj;
+
+      const compoundChildrenQueryElement = rootElement._luigi_mfe_webcomponent;
+      if (compoundChildrenQueryElement) {
+        const compoundChildren = compoundChildrenQueryElement.querySelectorAll('[lui_web_component]');
+        compoundChildren?.forEach((item) => {
+            const ctx = item.context || {};
+            item.context = Object.assign(ctx, contextObj);
+        });
+      }
     };
     const ctx = GenericHelperFunctions.resolveContext(context);
     deferInit = false;
@@ -118,12 +128,12 @@
       viewUrl: viewurl,
       webcomponent: GenericHelperFunctions.checkWebcomponentValue(webcomponent) || true
     }; // TODO: fill with sth
-    if(!thisComponent.getNoShadow()){
+    if (!thisComponent.getNoShadow()) {
       mainComponent.innerHTML=''
       const shadow = thisComponent.attachShadow({ mode: "open"});
       shadow.append(mainComponent);
-    }else{
-      //removing mainComponent
+    } else {
+      // removing mainComponent
       thisComponent.innerHTML = '';
     }
     webcomponentService.renderWebComponentCompound(node, thisComponent.getNoShadow() ? thisComponent : mainComponent, ctx).then(compCnt => {
diff --git a/container/src/LuigiContainer.svelte b/container/src/LuigiContainer.svelte
index 01ccb77b..625a97f7 100644
--- a/container/src/LuigiContainer.svelte
+++ b/container/src/LuigiContainer.svelte
@@ -109,6 +109,7 @@
       clientPermissions &&
       userSettings &&
       anchor &&
+      authData &&
       dirtyStatus &&
       hasBack &&
       documentTitle &&
diff --git a/container/src/services/web-component-helpers.ts b/container/src/services/web-component-helpers.ts
index bfa9fcb7..97215662 100644
--- a/container/src/services/web-component-helpers.ts
+++ b/container/src/services/web-component-helpers.ts
@@ -125,6 +125,7 @@ export class GridCompoundRenderer extends DefaultCompoundRenderer {
     const config = layoutConfig || {};
     const compoundItemCnt = document.createElement('div');
     compoundItemCnt.setAttribute('style', `grid-row: ${config.row || 'auto'}; grid-column: ${config.column || 'auto'}`);
+    compoundItemCnt.classList.add('lui-compoundItemCnt');
     return compoundItemCnt;
   }
 }
diff --git a/container/src/services/webcomponents.service.ts b/container/src/services/webcomponents.service.ts
index f7c11f74..73ba84b4 100644
--- a/container/src/services/webcomponents.service.ts
+++ b/container/src/services/webcomponents.service.ts
@@ -52,6 +52,7 @@ export class WebComponentService {
       if (nodeId) {
         wc.setAttribute('nodeId', nodeId);
       }
+      wc.setAttribute('lui_web_component', 'true');
 
       this.initWC(wc, wc_id, wc_container, viewUrl, ctx, nodeId, isCompoundChild);
       wc_container.replaceChild(wc, wcItemPlaceholder);
@@ -572,6 +573,7 @@ export class WebComponentService {
           if (navNode.webcomponent && navNode.webcomponent.selfRegistered) {
             this.includeSelfRegisteredWCFromUrl(navNode, renderer.viewUrl, () => {
               const wc = document.createElement(wc_id);
+              wc.setAttribute('lui_web_component', true);
               this.initWC(wc, wc_id, wc, renderer.viewUrl, ctx, '_root');
               resolve(wc);
             });
@@ -579,6 +581,7 @@ export class WebComponentService {
             this.registerWCFromUrl(renderer.viewUrl, wc_id)
               .then(() => {
                 const wc = document.createElement(wc_id);
+                wc.setAttribute('lui_web_component', true);
                 this.initWC(wc, wc_id, wc, renderer.viewUrl, ctx, '_root');
                 resolve(wc);
               })


Test patch:
diff --git a/container/cypress/e2e/test-app/compound/wc-compound-container.cy.js b/container/cypress/e2e/test-app/compound/wc-compound-container.cy.js
index fe39ad1c..0514c1b7 100644
--- a/container/cypress/e2e/test-app/compound/wc-compound-container.cy.js
+++ b/container/cypress/e2e/test-app/compound/wc-compound-container.cy.js
@@ -31,6 +31,24 @@ describe('Compound Container Tests', () => {
           expect(stub.getCall(0)).to.be.calledWith('LuigiClient.getAnchor()="testAnchorCompound"');
         });
     });
+
+    it('LuigiClient API updateContext', () => {
+      cy.on('window:alert', stub);
+
+      cy.wait(500);
+      cy.get('#luigi-update-context')
+        .click()
+        .then(() => {
+          cy.get(containerSelector)
+            .shadow()
+            .contains('updateContext')
+            .click()
+            .then(() => {
+              expect(stub.getCall(0)).to.be.calledWith('compoundWC.ctx={"label":"Dashboard","title":"Some input","instant":true,"newContextData":"some data"}');
+            });
+        });
+    });
+
     it('defer-init flag for LuigiCompoundContainer', () => {
       // the initialized webcomponent has id="defer-init-flag"
       cy.get('#defer-init-flag').should('not.exist');
diff --git a/container/test-app/compound/compoundClientAPI.html b/container/test-app/compound/compoundClientAPI.html
index 7dc4e6bc..0fccf017 100644
--- a/container/test-app/compound/compoundClientAPI.html
+++ b/container/test-app/compound/compoundClientAPI.html
@@ -18,6 +18,8 @@
       This page is used to test Compound-Container Luigi CLient API functionalities for
       web component based microfrontend
     </h3>
+    <button id="luigi-update-context" type="button">Update context</button>
+
     <!-- base scenario compound container-->
     <span>Below is a compound container example for testing functionality</span>
     <div style="border:solid 1px blue">
@@ -242,6 +244,10 @@
           }
         ]
       };
+
+      document.querySelector('#luigi-update-context').addEventListener('click', () => {
+        compoundContainer.updateContext({ newContextData: 'some data' });
+      });
     </script>
   </body>
 </html>
diff --git a/container/test-app/compound/helloWorldWC.js b/container/test-app/compound/helloWorldWC.js
index a3352aff..d2bbfc23 100644
--- a/container/test-app/compound/helloWorldWC.js
+++ b/container/test-app/compound/helloWorldWC.js
@@ -49,6 +49,9 @@ export default class extends HTMLElement {
     const getDirtyStatusBtn = document.createElement('template');
     getDirtyStatusBtn.innerHTML = '<button id="getDirtyStatus">getDirtyStatus</button>';
 
+    const updateContextBtn = document.createElement('template');
+    updateContextBtn.innerHTML = '<button id="updateContext">updateContext</button>';
+
     const uxManagerMultipleRequestsBtn = document.createElement('template');
     uxManagerMultipleRequestsBtn.innerHTML = `<button id="uxManagerManyRequests">uxManager().closeUserSettings,
     openUserSettings,
@@ -95,6 +98,7 @@ export default class extends HTMLElement {
     this._shadowRoot.appendChild(getUserSettingsBtn.content.cloneNode(true));
     this._shadowRoot.appendChild(getAnchorBtn.content.cloneNode(true));
     this._shadowRoot.appendChild(getDirtyStatusBtn.content.cloneNode(true));
+    this._shadowRoot.appendChild(updateContextBtn.content.cloneNode(true));
     this._shadowRoot.appendChild(uxManagerMultipleRequestsBtn.content.cloneNode(true));
     this._shadowRoot.appendChild(linkManagerChainedFunctionsRequestsBtn.content.cloneNode(true));
     this._shadowRoot.appendChild(linkManagerOpenAsRequestsBtn.content.cloneNode(true));
@@ -203,6 +207,14 @@ export default class extends HTMLElement {
       });
     });
 
+    this.$updateContextBtn = this._shadowRoot.querySelector('#updateContext');
+    this.$updateContextBtn.addEventListener('click', () => {
+      this.LuigiClient.uxManager().showAlert({
+        text: `compoundWC.ctx=${JSON.stringify(this.ctx)}`,
+        type: 'info'
+      });
+    });
+
     this.$uxManagerManyRequests = this._shadowRoot.querySelector('#uxManagerManyRequests');
     this.$uxManagerManyRequests.addEventListener('click', () => {
       this.LuigiClient.uxManager().closeUserSettings();
diff --git a/container/test/services/webcomponents.service.spec.ts b/container/test/services/webcomponents.service.spec.ts
index 1e4ec08a..8f68f146 100644
--- a/container/test/services/webcomponents.service.spec.ts
+++ b/container/test/services/webcomponents.service.spec.ts
@@ -42,6 +42,7 @@ describe('attachWC', () => {
   it('wc_container contains wcItemPlaceholder and nodeId is provided', () => {
     const innerWCElement = document.createElement(wc_id);
     innerWCElement.setAttribute('nodeId', nodeId);
+    innerWCElement.setAttribute('lui_web_component', 'true');
 
     // Mock methods to spy on them
     const dispatchEventSpy = jest.spyOn(wc_container, 'dispatchEvent');
@@ -78,6 +79,7 @@ describe('attachWC', () => {
 
   it('nodeId not provided', () => {
     const innerWCElement = document.createElement(wc_id);
+    innerWCElement.setAttribute('lui_web_component', 'true');
 
     // Mock methods to spy on them
     const dispatchEventSpy = jest.spyOn(wc_container, 'dispatchEvent');
@@ -93,6 +95,7 @@ describe('attachWC', () => {
 
   it('_luigi_node provided', () => {
     const innerWCElement = document.createElement(wc_id);
+    innerWCElement.setAttribute('lui_web_component', 'true');
 
     // Mock methods to spy on them
     const dispatchEventSpy = jest.spyOn(wc_container, 'dispatchEvent');
@@ -167,7 +170,7 @@ describe('createClientAPI', () => {
       { slug: 'Sales-settings', params: null },
       { slug: null, params: { project: 'pr2', user: 'john' } },
       { slug: 'Sales-settings', params: { project: 'pr2', user: 'john' } }
-    ])('test linkManager navigateToIntent', (data) => {
+    ])('test linkManager navigateToIntent', data => {
       let payloadLink = `#?intent=${data.slug}`;
 
       if (data.params && Object.keys(data.params)?.length) {
@@ -1381,6 +1384,7 @@ describe('createCompoundContainerAsync', () => {
     const ctx = {};
     const mockGeneratedWCId = 'mocked-wc-id';
     const mockWebComponent = document.createElement(mockGeneratedWCId);
+    mockWebComponent.setAttribute('lui_web_component', 'true');
     const navNode = {};
 
     service.initWC = jest.fn();