Repository: SAP/luigi (js)

Issue:
Correct webcomponent function return value
<!-- Thank you for your contribution. Before you submit the issue:
1. Search open and closed issues for duplicates.
2. Read the contributing guidelines: https://github.com/SAP/luigi/blob/main/CONTRIBUTING.md
-->
**Goal**
Function should return either:  **object | boolean** , no other values

**Description**
https://github.com/SAP/luigi/blob/3e3d636fff1bf65abb054edff2ffe3b0f6b15bde/container/src/utilities/helpers.ts#L27-L40

Focus on line **29**

In **checkWebcomponentValue()** function in **container/src/utilities/helpers.ts** we parse a string value and expect it to be JSON object. if it is an object we handle it as is and return **parsedValue** as an object. 

However this could fail in two ways:
1. User misconfigures webcomponent config and JSON.parse produces error for non-parseable String: `Uncaught SyntaxError: Unexpected token ... is not valid JSON`.  
      - In this case the Error should be caught and proper warning be given to the user in the console.
2. JSON.parse('123456') or JSON.parse('"Any-String"') would return a String '123456' or "Any-String"
      - In such cases when parse result is not an object the default return value should be a boolean **true**. (It could be argued later that a console information warning here would be beneficial or not)

Other TODOs:
- Adjust the unit tests for **checkWebcomponentValue** function 
- **webcomponent: any** => could be typed to **webcomponent: object | boolean | string**
- Update JSON doc of function parameter to explain what the parameters could be. 
- If possible also add a Cypress test for this scenario




Pull request description:
Refactor check webcomponent function
Fixes #3566
In this PR:
No value validation for properties, like `webcomponent={selfregisteredWrongValue=true}` etc.
Strings will be JSON parsed and if there is any parsing error, the app will throw an error and wc will not be shown.
Function will only return boolean or object.
No standard/default return type.

Additional fix: context can be set as property and no validation as well. If there is a parse error wc will not be shown.

Patch:
diff --git a/container/src/LuigiCompoundContainer.svelte b/container/src/LuigiCompoundContainer.svelte
index c1009099..f741700d 100644
--- a/container/src/LuigiCompoundContainer.svelte
+++ b/container/src/LuigiCompoundContainer.svelte
@@ -8,33 +8,33 @@
       compoundConfig: {
         type: 'Object',
         reflect: false,
-        attribute: 'compound-config'
+        attribute: 'compound-config',
       },
       nodeParams: { type: 'Object', reflect: false, attribute: 'node-params' },
       userSettings: {
         type: 'Object',
         reflect: false,
-        attribute: 'user-settings'
+        attribute: 'user-settings',
       },
       anchor: { type: 'String', reflect: false, attribute: 'anchor' },
       searchParams: {
         type: 'Object',
         reflect: false,
-        attribute: 'search-params'
+        attribute: 'search-params',
       },
       pathParams: { type: 'Object', reflect: false, attribute: 'path-params' },
       clientPermissions: {
         type: 'Object',
         reflect: false,
-        attribute: 'client-permissions'
-      }
+        attribute: 'client-permissions',
+      },
     },
-    extend: customElementConstructor => {
-      let notInitFn = name => {
+    extend: (customElementConstructor) => {
+      let notInitFn = (name) => {
         return () =>
           console.warn(
             name +
-              " can't be called on luigi-container before its micro frontend is attached to the DOM."
+              " can't be called on luigi-container before its micro frontend is attached to the DOM.",
           );
       };
       return class extends customElementConstructor {
@@ -45,7 +45,7 @@
           }
         }
       };
-    }
+    },
   }}
 />
 
@@ -94,17 +94,18 @@
     thisComponent.updateContext = (contextObj: any, internal?: any) => {
       mainComponent._luigi_mfe_webcomponent.context = contextObj;
     };
-    const ctx = context ? JSON.parse(context) : {};
+    const ctx = GenericHelperFunctions.resolveContext(context);
     deferInit = false;
+
     const node = {
       compound: compoundConfig,
       viewUrl: viewurl,
       webcomponent:
-        GenericHelperFunctions.checkWebcomponentValue(webcomponent) || true
+        GenericHelperFunctions.checkWebcomponentValue(webcomponent) || true,
     }; // TODO: fill with sth
     webcomponentService
       .renderWebComponentCompound(node, mainComponent, ctx)
-      .then(compCnt => {
+      .then((compCnt) => {
         eventBusElement = compCnt as HTMLElement;
         if (thisComponent.hasAttribute('skip-init-check') || !node.viewUrl) {
           thisComponent.initialized = true;
diff --git a/container/src/LuigiContainer.svelte b/container/src/LuigiContainer.svelte
index 64194bdc..defaae70 100644
--- a/container/src/LuigiContainer.svelte
+++ b/container/src/LuigiContainer.svelte
@@ -9,45 +9,45 @@
       webcomponent: {
         type: 'String',
         reflect: false,
-        attribute: 'webcomponent'
+        attribute: 'webcomponent',
       },
       locale: { type: 'String', reflect: false, attribute: 'locale' },
       theme: { type: 'String', reflect: false, attribute: 'theme' },
       activeFeatureToggleList: {
         type: 'Array',
         reflect: false,
-        attribute: 'active-feature-toggle-list'
+        attribute: 'active-feature-toggle-list',
       },
       skipInitCheck: {
         type: 'Boolean',
         reflect: false,
-        attribute: 'skip-init-check'
+        attribute: 'skip-init-check',
       },
       nodeParams: { type: 'Object', reflect: false, attribute: 'node-params' },
       userSettings: {
         type: 'Object',
         reflect: false,
-        attribute: 'user-settings'
+        attribute: 'user-settings',
       },
       anchor: { type: 'String', reflect: false, attribute: 'anchor' },
       searchParams: {
         type: 'Object',
         reflect: false,
-        attribute: 'search-params'
+        attribute: 'search-params',
       },
       pathParams: { type: 'Object', reflect: false, attribute: 'path-params' },
       clientPermissions: {
         type: 'Object',
         reflect: false,
-        attribute: 'client-permissions'
-      }
+        attribute: 'client-permissions',
+      },
     },
-    extend: customElementConstructor => {
-      let notInitFn = name => {
+    extend: (customElementConstructor) => {
+      let notInitFn = (name) => {
         return () =>
           console.warn(
             name +
-              " can't be called on luigi-container before its micro frontend is attached to the DOM."
+              " can't be called on luigi-container before its micro frontend is attached to the DOM.",
           );
       };
       return class extends customElementConstructor {
@@ -60,7 +60,7 @@
           }
         }
       };
-    }
+    },
   }}
 />
 
@@ -123,7 +123,7 @@
           mainComponent,
           !!webcomponent,
           iframeHandle,
-          data
+          data,
         );
       };
 
@@ -142,19 +142,18 @@
       containerService.registerContainer(thisComponent);
       webcomponentService.thisComponent = thisComponent;
 
-      const ctx = context ? JSON.parse(context) : {};
+      const ctx = GenericHelperFunctions.resolveContext(context);
       if (webcomponent) {
         mainComponent.innerHTML = '';
-        const webComponentValue = GenericHelperFunctions.checkWebcomponentValue(
-          webcomponent
-        );
+        const webComponentValue =
+          GenericHelperFunctions.checkWebcomponentValue(webcomponent);
         webcomponentService.renderWebComponent(
           viewurl,
           mainComponent,
           ctx,
           typeof webComponentValue === 'object'
             ? { webcomponent: webComponentValue }
-            : {}
+            : {},
         );
       }
       if (skipInitCheck) {
diff --git a/container/src/utilities/helpers.ts b/container/src/utilities/helpers.ts
index 562fe7b2..fa5161e2 100644
--- a/container/src/utilities/helpers.ts
+++ b/container/src/utilities/helpers.ts
@@ -21,22 +21,32 @@ export class GenericHelpersClass {
 
   /**
    * Checks whether web component is an attribute or property. In case of attribute, it returns the parsed value.
-   * @param webcomponent 
-   * @returns returns the parsed webcomponent value.
+   * @param {object | boolean | string} webcomponent value can either be an object, boolean or a stringified object, e.g webcomponent='{"selfregistered":"true"}'
+   * @returns {object | boolean} webcomponent returns the parsed webcomponent value.
    */
-  checkWebcomponentValue(webcomponent: any): object | boolean {
+  checkWebcomponentValue(webcomponent: object | boolean | string): object | boolean {
     if (typeof webcomponent === 'string') {
-      let parsedValue = JSON.parse(webcomponent);
-      if (this.isObject(parsedValue)) {
-        if ('selfRegistered' in parsedValue) {
-          parsedValue.selfRegistered = parsedValue.selfRegistered === 'true';
-        }
-      }
-      return parsedValue;
+      return JSON.parse(webcomponent);
     } else if (typeof webcomponent === 'boolean' || typeof webcomponent === 'object') {
       return webcomponent;
+    } else {
+      console.warn('Webcomponent value has a wrong type.')
     }
   }
+
+  /**
+  * Resolves the context to an object. If the context is a string, it attempts to parse
+  * it as JSON. If parsing fails JSON parse error will be thrown.
+  * @param {object | string} context - The context to be resolved.
+  * @returns {object} The resolved context as an object.
+  */
+  resolveContext(context: object | string): object {
+    return context
+      ? typeof context === 'string'
+        ? JSON.parse(context)
+        : context
+      : {};
+  }
 }
 
 export const GenericHelperFunctions = new GenericHelpersClass();
diff --git a/container/typings/LuigiCompoundContainer.svelte.d.ts b/container/typings/LuigiCompoundContainer.svelte.d.ts
index 0b5a057f..ae2d0c29 100644
--- a/container/typings/LuigiCompoundContainer.svelte.d.ts
+++ b/container/typings/LuigiCompoundContainer.svelte.d.ts
@@ -23,6 +23,13 @@ export default class LuigiCompoundContainer extends HTMLElement {
    */
   deferInit: boolean;
 
+  /**
+   * The context to be passed to the microfrontend. It can be an object or a stringified object.
+   * @param {Object} object to be passed to the microfrontend
+   * @param {string} string must be a stringified JSON object.
+   */
+  context: object | string;
+
   /**
    * The search parameters to be passed to the compound micro frontend.
    */
@@ -33,11 +40,6 @@ export default class LuigiCompoundContainer extends HTMLElement {
    */
   pathParams: any;
 
-  /**
-   * The context to be passed to the compound microfrontend
-   */
-  context: any;
-
   /**
    * The clientPermissions to be passed to the compound micro frontend.
    */
@@ -59,8 +61,9 @@ export default class LuigiCompoundContainer extends HTMLElement {
    * @param {string} WebComponentSettings.type: string, like module.
    * @param {boolean} WebComponentSettings.selfRegistered: if it is true, the web component bundle will be added via script tag.
    * @param {string} WebComponentSettings.tagName: tag name where web component is added to DOM.
+   * @param {string} string must be a stringified JSON object from type `WebComponentSettings`.
    */
-  webcomponent: boolean | WebComponentSettings;
+  webcomponent: boolean | WebComponentSettings | stringf;
 
   /**
    * Updates the context of the compound microfrontend
diff --git a/container/typings/LuigiContainer.svelte.d.ts b/container/typings/LuigiContainer.svelte.d.ts
index d698c460..b3aad8b3 100644
--- a/container/typings/LuigiContainer.svelte.d.ts
+++ b/container/typings/LuigiContainer.svelte.d.ts
@@ -24,9 +24,11 @@ export default class LuigiContainer extends HTMLElement {
   deferInit: boolean;
 
   /**
-   * The context to be passed to the microfrontend
+   * The context to be passed to the microfrontend. It can be an object or a stringified object.
+   * @param {Object} object to be passed to the microfrontend
+   * @param {string} string must be a stringified JSON object.
    */
-  context: any;
+  context: object | string;
 
   /**
    * Label information for the microfrontend
@@ -35,12 +37,14 @@ export default class LuigiContainer extends HTMLElement {
 
   /**
    * Predicate that sets whether the microfrontend is to be rendered in a web component or not. It can also be an object with the following attributes:
-   * @param {Object} [WebComponentSettings]
+   * @param {boolean} specifies if a microfrontend is a webcomponent or not without any other settings.
+   * @param {Object} [WebComponentSettings] specifies that the microfrontend is a webcomponent with addtional settings.
    * @param {string} WebComponentSettings.type: string, like module.
    * @param {boolean} WebComponentSettings.selfRegistered: if it is true, the web component bundle will be added via script tag.
    * @param {string} WebComponentSettings.tagName: tag name where web component is added to DOM.
+   * @param {string} string must be a stringified boolean or JSON object from type `WebComponentSettings`.
    */
-  webcomponent: boolean | WebComponentSettings;
+  webcomponent: boolean | WebComponentSettings | string;
 
   /**
    * The locale to be passed to the web-component-based micro frontend


Test patch:
diff --git a/container/cypress/e2e/create-container-dynamic.cy.js b/container/cypress/e2e/create-container-dynamic.cy.js
new file mode 100644
index 00000000..013638b8
--- /dev/null
+++ b/container/cypress/e2e/create-container-dynamic.cy.js
@@ -0,0 +1,287 @@
+describe('Create container dynamically', () => {
+    const tetsPage = 'http://localhost:8080/index3.html';
+    describe('luigi container', () => {
+        it('luigi container webcomponent object selfRegistered', () => {
+            cy.once('uncaught:exception', () => false);
+            const scriptCode = `
+            const lc = document.querySelector('#lc');
+            lc.viewurl = "./helloWorldWCSelfRegistered.js";
+            lc.webcomponent = { "selfRegistered": "true" };
+            lc.context = { title: 'Nested' }
+            `;
+
+            const htmlCode = `
+            <luigi-container id="lc" data-test-id="luigi-container"></luigi-container>
+              <script>${scriptCode}</script>
+            `;
+
+
+            const stub = cy.stub();
+            cy.on('window:alert', stub);
+            cy.visit(tetsPage);
+            cy.get('.content').invoke('append', htmlCode);
+            cy.get('[data-test-id="luigi-container"]')
+                .shadow()
+                .find(
+                    'luigi-wc-687474703a2f2f6c6f63616c686f73743a383038302f68656c6c6f576f726c64574353656c66526567697374657265642e6a73'
+                )
+                .shadow()
+                .find('section')
+                .should('contain.text', 'Hello World (self registered)!');
+        });
+        it('luigi container webcomponent is true', () => {
+            cy.once('uncaught:exception', () => false);
+            const scriptCode = `
+            const lc = document.querySelector('#lc');
+            lc.viewurl = "./helloWorldWC.js";
+            lc.webcomponent = true;
+            lc.context = { title: 'Nested' }
+            `;
+
+            const htmlCode = `
+            <luigi-container id="lc" data-test-id="luigi-container"></luigi-container>
+              <script>${scriptCode}</script>
+            `;
+
+            const stub = cy.stub();
+            cy.on('window:alert', stub);
+            cy.visit(tetsPage);
+            cy.get('.content').invoke('append', htmlCode);
+            cy.get('[data-test-id="luigi-container"]')
+                .shadow()
+                .find('luigi-wc-687474703a2f2f6c6f63616c686f73743a383038302f68656c6c6f576f726c6457432e6a73')
+                .shadow()
+                .find('section')
+                .should('contain.text', 'Nested');
+        });
+        it('luigi container webcomponent invalid value for attributes e.g. client-permissions ', () => {
+            const scriptCode = `
+            const lc = document.querySelector('#lc');
+            lc.viewurl = "./helloWorldWCSelfRegistered.js";
+            lc.webcomponent = { "selfRegistered": "true" };
+            `;
+
+            const htmlCode = `
+            <luigi-container id="lc" data-test-id="luigi-container" client-permissions="value should be an object"></luigi-container>
+              <script>${scriptCode}</script>
+            `;
+
+            const stub = cy.stub();
+            cy.on('window:alert', stub);
+            cy.visit(tetsPage);
+            cy.get('.content').invoke('append', htmlCode);
+            cy.get('[data-test-id="luigi-container"]')
+                .shadow()
+                .find(
+                    'luigi-wc-687474703a2f2f6c6f63616c686f73743a383038302f68656c6c6f576f726c64574353656c66526567697374657265642e6a73'
+                )
+                .should('not.exist');
+        });
+    });
+    describe('luigi compound container', () => {
+        it('luigi compound container webcomponent object selfRegistered', () => {
+            const scriptCode = `
+            <script>
+                const content = document.querySelector('.content');
+                const wc = document.createElement('luigi-compound-container');
+                wc.context = { title: 'Nested' }
+                wc.viewurl = 'https://luigiwebcomponents.gitlab.io/nested2.js';
+
+
+                wc.compoundConfig = {
+                    eventListeners: [
+                        {
+                            source: "*",
+                            name: 'buttonPressed',
+                            action: 'changeColor',
+                            dataConverter: (data) => {
+                                var rd = () => {
+                                    return Math.round(Math.random() * 255);
+                                };
+
+                                return {
+                                    color1: "251,244,199",
+                                    color2: "255,149,219"
+                                }
+                            }
+                        }
+                    ],
+                    children: [{
+                        viewUrl: 'https://luigiwebcomponents.gitlab.io/layouts/panelHeader.js',
+                        context: {
+                            title: 'My Awesome Grid',
+                            description: 'Really awesome'
+                        },
+                        layoutConfig: {
+                            slot: "header"
+                        },
+                        eventListeners: [{
+                            source: 'input1',
+                            name: 'sendInput',
+                            action: 'update',
+                            dataConverter: (data) => {
+                                return 'new text: ' + data;
+                            }
+                        }]
+                    }, {
+                        viewUrl: 'https://luigiwebcomponents.gitlab.io/layouts/panelFooter.js',
+                        context: {
+                            footer: 'This is the end of awesomeness'
+                        },
+                        layoutConfig: {
+                            slot: "footer"
+                        }
+                    }, {
+                        viewUrl: 'https://luigiwebcomponents.gitlab.io/luigiwebcomponentmf/main.js',
+                        layoutConfig: {
+                            slot: "content"
+                        },
+                        context: {
+                            label: 'I am nested!'
+                        }
+                    }, {
+                        viewUrl: 'https://team-phteven.gitlab.io/joke-of-the-day-wc/main.js',
+                        context: {
+                            apiUrl: 'https://edenhauser.com/stupidb/?db=unijokes_db&random',
+                            fontScale: '0.5'
+                        },
+                        eventListeners: [{
+                            source: "*",
+                            name: 'buttonPressed',
+                            action: 'update'
+                        }]
+                    }, {
+                        viewUrl: 'https://luigiwebcomponents.gitlab.io/luigi-wc-mfe/main.js',
+                        context: {
+                            label: 'Update Joke',
+                            showAlert: false
+                        }
+                    }]
+                };
+                content.appendChild(wc);
+                </script>
+           `;
+            const stub = cy.stub();
+            cy.on('window:alert', stub);
+            cy.visit(tetsPage);
+            cy.get('.content').invoke('append', scriptCode);
+            cy.get('luigi-compound-container')
+                .shadow()
+                .then($container => {
+                    return cy
+                        .wrap($container)
+                        .find(
+                            'luigi-wc-68747470733a2f2f6c75696769776562636f6d706f6e656e74732e6769746c61622e696f2f6e6573746564322e6a73'
+                        )
+                        .shadow();
+                })
+                .then($innerContainer => {
+                    cy.wrap($innerContainer)
+                        .get(
+                            'luigi-wc-68747470733a2f2f6c75696769776562636f6d706f6e656e74732e6769746c61622e696f2f6c756967692d77632d6d66652f6d61696e2e6a73'
+                        )
+                        .should('exist')
+                        .shadow()
+                        .find('section')
+                        .should('contain.text', 'This is a luigi micro frontend, based on web components.');
+                });
+        });
+        it('luigi compound container invalid JSON in context property', () => {
+            const scriptCode = `
+            <script>
+                const content = document.querySelector('.content');
+                const wc = document.createElement('luigi-compound-container');
+                wc.context = '{"invalid": "JSON}'
+                wc.viewurl = 'https://luigiwebcomponents.gitlab.io/nested2.js';
+
+
+                wc.compoundConfig = {
+                    eventListeners: [
+                        {
+                            source: "*",
+                            name: 'buttonPressed',
+                            action: 'changeColor',
+                            dataConverter: (data) => {
+                                var rd = () => {
+                                    return Math.round(Math.random() * 255);
+                                };
+
+                                return {
+                                    color1: "251,244,199",
+                                    color2: "255,149,219"
+                                }
+                            }
+                        }
+                    ],
+                    children: [{
+                        viewUrl: 'https://luigiwebcomponents.gitlab.io/layouts/panelHeader.js',
+                        context: {
+                            title: 'My Awesome Grid',
+                            description: 'Really awesome'
+                        },
+                        layoutConfig: {
+                            slot: "header"
+                        },
+                        eventListeners: [{
+                            source: 'input1',
+                            name: 'sendInput',
+                            action: 'update',
+                            dataConverter: (data) => {
+                                return 'new text: ' + data;
+                            }
+                        }]
+                    }, {
+                        viewUrl: 'https://luigiwebcomponents.gitlab.io/layouts/panelFooter.js',
+                        context: {
+                            footer: 'This is the end of awesomeness'
+                        },
+                        layoutConfig: {
+                            slot: "footer"
+                        }
+                    }, {
+                        viewUrl: 'https://luigiwebcomponents.gitlab.io/luigiwebcomponentmf/main.js',
+                        layoutConfig: {
+                            slot: "content"
+                        },
+                        context: {
+                            label: 'I am nested!'
+                        }
+                    }, {
+                        viewUrl: 'https://team-phteven.gitlab.io/joke-of-the-day-wc/main.js',
+                        context: {
+                            apiUrl: 'https://edenhauser.com/stupidb/?db=unijokes_db&random',
+                            fontScale: '0.5'
+                        },
+                        eventListeners: [{
+                            source: "*",
+                            name: 'buttonPressed',
+                            action: 'update'
+                        }]
+                    }, {
+                        viewUrl: 'https://luigiwebcomponents.gitlab.io/luigi-wc-mfe/main.js',
+                        context: {
+                            label: 'Update Joke',
+                            showAlert: false
+                        }
+                    }]
+                };
+                content.appendChild(wc);
+                </script>
+           `;
+            const stub = cy.stub();
+            cy.on('window:alert', stub);
+            cy.visit(tetsPage);
+            cy.get('.content').invoke('append', scriptCode);
+
+            cy.get('luigi-compound-container')
+                .shadow()
+                .then($container => {
+                    cy.wrap($container)
+                        .find(
+                            'luigi-wc-68747470733a2f2f6c75696769776562636f6d706f6e656e74732e6769746c61622e696f2f6e6573746564322e6a73'
+                        )
+                        .should('not.exist');
+                });
+        });
+    });
+});
diff --git a/container/test-app/index3.html b/container/test-app/index3.html
new file mode 100644
index 00000000..9e26b435
--- /dev/null
+++ b/container/test-app/index3.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <meta charset="utf-8" />
+    <title>luigi-container</title>
+    <script type="module">
+      import './bundle.js';
+    </script>
+    <style>
+      html,
+      body {
+        margin: 0;
+        font-family: var(--sapFontFamily);
+        height: 100%;
+      }
+
+      .layout {
+        display: flex;
+        flex-direction: column;
+        height: 100%;
+      }
+
+      .layout .content {
+        flex: 1;
+        position: relative;
+      }
+    </style>
+  </head>
+
+  <body>
+    <div class="layout">
+      <div class="content"></div>
+    </div>
+  </body>
+</html>
diff --git a/container/test/utilities/helpers.spec.ts b/container/test/utilities/helpers.spec.ts
index 0c0a6bc2..886d41ff 100644
--- a/container/test/utilities/helpers.spec.ts
+++ b/container/test/utilities/helpers.spec.ts
@@ -8,97 +8,155 @@ describe('GenericHelpers', () => {
       const result = GenericHelperFunctions.isFunction(testFunction);
       expect(result).toBe(true);
     });
-  
+
     it('should return true for an arrow function', () => {
       const testArrowFunction = () => { };
       const result = GenericHelperFunctions.isFunction(testArrowFunction);
       expect(result).toBe(true);
     });
-  
+
     it('should return false for an object', () => {
       const testObject = { key: 'value' };
       const result = GenericHelperFunctions.isFunction(testObject);
       expect(result).toBe(false);
     });
-  
+
     it('should return false for an array', () => {
       const testArray = [1, 2, 3];
       const result = GenericHelperFunctions.isFunction(testArray);
       expect(result).toBe(false);
     });
-  
+
     it('should return false for a string', () => {
       const testString = 'Hello, World!';
       const result = GenericHelperFunctions.isFunction(testString);
       expect(result).toBe(false);
     });
   });
-    
+
   describe('isObject function', () => {
     it('should return true for an object', () => {
       const testObject = { key: 'value' };
       const result = GenericHelperFunctions.isObject(testObject);
       expect(result).toBe(true);
     });
-  
+
     it('should return false for an array', () => {
       const testArray = [1, 2, 3];
       const result = GenericHelperFunctions.isObject(testArray);
       expect(result).toBe(false);
     });
-  
+
     it('should return false for a string', () => {
       const testString = 'Hello, World!';
       const result = GenericHelperFunctions.isObject(testString);
       expect(result).toBe(false);
     });
-  
+
     it('should return false for null', () => {
       const testNull = null;
       const result = GenericHelperFunctions.isObject(testNull);
       expect(result).toBe(false);
     });
-  
+
     it('should return false for undefined', () => {
       const testUndefined = undefined;
       const result = GenericHelperFunctions.isObject(testUndefined);
       expect(result).toBe(false);
     });
-  }); 
-  
+  });
+
   describe('checkWebcomponentValue function', () => {
     it('should return the parsed object for a valid JSON string', () => {
       const jsonString = '{"selfRegistered": "true", "name": "MyComponent"}';
       const result = GenericHelperFunctions.checkWebcomponentValue(jsonString);
+      expect(result).toEqual({ selfRegistered: "true", name: "MyComponent" });
+    });
+
+    it('should return the parsed object for a valid JSON string', () => {
+      const jsonString = '{"selfRegistered": true, "name": "MyComponent"}';
+      const result = GenericHelperFunctions.checkWebcomponentValue(jsonString);
       expect(result).toEqual({ selfRegistered: true, name: "MyComponent" });
     });
-  
+
     it('should return the boolean value as is', () => {
       const booleanValue = true;
       const result = GenericHelperFunctions.checkWebcomponentValue(booleanValue);
       expect(result).toBe(booleanValue);
     });
-  
+
     it('should return the object as is', () => {
       const objValue = { name: "MyComponent", selfRegistered: false };
       const result = GenericHelperFunctions.checkWebcomponentValue(objValue);
       expect(result).toEqual(objValue);
     });
-  
-    // SHOULD HAVE THIS TEST, BUT IMPLEMENTATION NEEDS ADJUSTING
-    // it('should return false for an invalid JSON string', () => {
-    //   const invalidJsonString = '{"selfRegistered": "true", "name": "MyComponent"';
-    //   const result = GenericHelperFunctions.checkWebcomponentValue(invalidJsonString);
-    //   expect(result).toBe(false);
-    // });
-  
+
+    it('should throw invalid JSON error', () => {
+      const consoleErrorSpy = jest.spyOn(console, 'error');
+      const invalidJsonString = '{"selfRegistered": "true", "name": "MyComponent"';
+
+      jest.spyOn(JSON, 'parse').mockImplementation(() => {
+        console.error(new Error('Invalid JSON'));
+      });
+
+      const result = GenericHelperFunctions.checkWebcomponentValue(invalidJsonString);
+
+      expect(result).toEqual(undefined);
+      expect(consoleErrorSpy).toHaveBeenCalledWith(new Error('Invalid JSON'));
+
+      jest.restoreAllMocks();
+      consoleErrorSpy.mockRestore();
+    });
+
     it('should return undefined for unsupported input types', () => {
       const unsupportedValue = 42; // Number
       const result = GenericHelperFunctions.checkWebcomponentValue(unsupportedValue);
-      expect(result).toBe(undefined);
+      expect(result).toStrictEqual(undefined);
+    });
+  });
+
+  describe('resolveContext', () => {
+    it('should parse a valid JSON string and return an object', () => {
+      const stringObject = '{"key": "value"}';
+      const result = GenericHelperFunctions.resolveContext(stringObject);
+      expect(result).toEqual({ key: 'value' });
+    });
+
+    it('should return the input object if it is not a string', () => {
+      const object = { key: 'value' };
+      const result = GenericHelperFunctions.resolveContext(object);
+      expect(result).toEqual(object);
+    });
+
+    it('should handle an empty string and return an empty object', () => {
+      const emptyString = '';
+      const result = GenericHelperFunctions.resolveContext(emptyString);
+      expect(result).toEqual({});
+    });
+
+    it('should handle an undefined context and return an empty object', () => {
+      const input = undefined;
+      const result = GenericHelperFunctions.resolveContext(input);
+      expect(result).toEqual({});
+    });
+
+    it('should log an error invalid JSON', () => {
+      const consoleErrorSpy = jest.spyOn(console, 'error');
+      const invalidJsonString = '{"key": "value", "missingQuotes": "invalid}';
+
+      jest.spyOn(JSON, 'parse').mockImplementation(() => {
+        console.error(new Error('Invalid JSON'));
+      });
+
+      const result = GenericHelperFunctions.resolveContext(invalidJsonString);
+
+      expect(result).toEqual(undefined);
+      expect(consoleErrorSpy).toHaveBeenCalledWith(new Error('Invalid JSON'));
+
+      jest.restoreAllMocks();
+      consoleErrorSpy.mockRestore();
     });
   });
-  
 });