From 53e500e4fd0503618a76f09c985e124e31aa397e Mon Sep 17 00:00:00 2001 From: Ndricim Date: Mon, 7 Oct 2024 11:23:56 +0200 Subject: [PATCH] Add publishEvent documentation and tests (#3882) --- client/luigi-client-wc-docu-mixin.js | 62 +++++++++++++++++ .../compound/wc-compound-container.cy.js | 21 ++++++ .../e2e/test-app/wc/wc-container.cy.js | 15 ++++- .../src/services/webcomponents.service.ts | 1 + .../test-app/compound/compoundClientAPI.html | 8 ++- container/test-app/compound/helloWorldWC.js | 16 +++-- container/test-app/wc/clientAPI.html | 17 +++-- container/test-app/wc/helloWorldWC.js | 25 ++++--- docs/luigi-client-api.md | 66 +++++++++++++++++++ scripts/package.json | 2 +- 10 files changed, 202 insertions(+), 31 deletions(-) create mode 100644 client/luigi-client-wc-docu-mixin.js diff --git a/client/luigi-client-wc-docu-mixin.js b/client/luigi-client-wc-docu-mixin.js new file mode 100644 index 0000000000..e9c52d4839 --- /dev/null +++ b/client/luigi-client-wc-docu-mixin.js @@ -0,0 +1,62 @@ +/** + * + * Publish an event that can be listened to from the container host. + * + * Similar to {@link luigi-client-api.md#sendCustomMessage sendCustomMessage} but for WebComponent based microfrontends only. + * + * @param {CustomEvent} event Custom event to be published + * @memberof Lifecycle + * + * @example + * // case 1: publish an event from a WC based microfrontend + * + * // wcComponent.js + * // sending a message to parent host + * this.LuigiClient.publishEvent(new CustomEvent('sendSomeMsg', { detail: 'My own message' })); + * + * // host.html + * myContainer.addEventListener('custom-message', event => { + * console.log('My custom message from the microfrontend', event.detail.data); + * } + * + * // case 2: publish an event from a compound microfrontend + * + * // secondChild.js + * // Set the custom event name = 'sendInput' and + * // send a message to its parent (main.html) and sibling (firstChild.js) + * this.LuigiClient.publishEvent(new CustomEvent('sendInput', { detail: 'My own message' })); + * + * // main.html + * myContainer.addEventListener('custom-message', event => { + * console.log('My custom message from microfrontend', event.detail.data); + * } + * + * // Note: eventListeners.name must match CustomEvent name above + * // eventListeners.source = input1 = id of secondChild.js, which is where the message being sent from + * compoundConfig = { + * ... + * children: [ + * { + * viewUrl: 'firstChild.js' + * ... + * eventListeners: [ + * { + * source: 'input1', + * name: 'sendInput', + * action: 'update', + * dataConverter: data => { + * console.log( + * 'dataConverter(): Received Custom Message from "input1" MF ' + data + * ); + * return 'new text: ' + data; + * } + * } + * ] + * }, + * { + * viewUrl: 'secondChild.js', + * id: 'input1', + * } + * + */ +export function publishEvent(event) {} 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 63d4f06327..a934d8fe86 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 @@ -174,5 +174,26 @@ describe('Compound Container Tests', () => { cy.get('#defer-init-flag').should('exist'); }); + + it('LuigiClient API publishEvent', () => { + cy.on('window:alert', stub); + + // Set up a spy on console.log + cy.window().then(win => { + cy.spy(win.console, 'log').as('consoleLogSpy'); + }); + + cy.get(containerSelector) + .shadow() + .contains('Publish event') + .click() + .then(() => { + expect(stub.getCall(0)).to.be.calledWith('sendInput'); + cy.get('@consoleLogSpy').should( + 'be.calledWith', + 'dataConverter(): Received Custom Message from "input1" MF My own event data' + ); + }); + }); }); }); diff --git a/container/cypress/e2e/test-app/wc/wc-container.cy.js b/container/cypress/e2e/test-app/wc/wc-container.cy.js index 0ef0ebdf0d..5e1eb4c1f1 100644 --- a/container/cypress/e2e/test-app/wc/wc-container.cy.js +++ b/container/cypress/e2e/test-app/wc/wc-container.cy.js @@ -137,12 +137,23 @@ describe('Web Container Test', () => { .find('#customMessageDiv') .should('have.text', 'Received Custom Message: '); - cy.get('#sendCustomMessageBtn') - .click() + cy.get('#sendCustomMessageBtn').click(); cy.get(containerSelector) .shadow() .find('#customMessageDiv') .should('have.text', 'Received Custom Message: cool custom Message'); + }); + + it('receive custom message from WC', () => { + cy.on('window:alert', stub); + + cy.get('[data-test-id="luigi-client-api-test-01"]') + .shadow() + .contains('Publish event') + .click() + .then(() => { + expect(stub.getCall(0)).to.be.calledWith('My Custom Message from Microfrontend'); }); + }); }); }); diff --git a/container/src/services/webcomponents.service.ts b/container/src/services/webcomponents.service.ts index 0461c1f6b6..a9d9587c3b 100644 --- a/container/src/services/webcomponents.service.ts +++ b/container/src/services/webcomponents.service.ts @@ -277,6 +277,7 @@ export class WebComponentService { }, publishEvent: ev => { if (eventBusElement && eventBusElement.eventBus) { + // compound component use case only eventBusElement.eventBus.onPublishEvent(ev, nodeId, wc_id); } const payload = { diff --git a/container/test-app/compound/compoundClientAPI.html b/container/test-app/compound/compoundClientAPI.html index 6eccf82165..6dc5ee12fe 100644 --- a/container/test-app/compound/compoundClientAPI.html +++ b/container/test-app/compound/compoundClientAPI.html @@ -62,9 +62,11 @@

import MFEventID from '../bundle.js'; const compoundContainer = document.getElementById('dashboard'); + compoundContainer.addEventListener(MFEventID.CUSTOM_MESSAGE, e => { - console.log('Publish Event', e.detail); + console.log('CUSTOM_MESSAGE Listener picked up: ', e.detail); }); + compoundContainer.compoundConfig = { renderer: { use: 'grid', @@ -103,6 +105,9 @@

name: 'sendInput', action: 'update', dataConverter: data => { + console.log( + 'dataConverter(): Received Custom Message from "input1" MF ' + data + ); return 'new text: ' + data; } } @@ -168,7 +173,6 @@

} ); compoundContainer.addEventListener(MFEventID.CUSTOM_MESSAGE, event => { - console.log('Custom Event', event); if (event.detail.id !== 'timer') { alert(event.detail.id); } diff --git a/container/test-app/compound/helloWorldWC.js b/container/test-app/compound/helloWorldWC.js index 846e741773..8194bd83f0 100644 --- a/container/test-app/compound/helloWorldWC.js +++ b/container/test-app/compound/helloWorldWC.js @@ -17,7 +17,7 @@ export default class extends HTMLElement { current_locale.innerHTML = ''; const templateBtn2 = document.createElement('template'); - templateBtn2.innerHTML = ''; + templateBtn2.innerHTML = ''; const addNodeParamsBtn = document.createElement('template'); addNodeParamsBtn.innerHTML = ''; @@ -151,20 +151,22 @@ export default class extends HTMLElement { }); } }); - this._shadowRoot.querySelector('.button2').addEventListener('click', () => { + + this.$publishEventBtn = this._shadowRoot.querySelector('#publishEvent'); + this.$publishEventBtn.addEventListener('click', () => { if (this.LuigiClient) { - this.LuigiClient.publishEvent(new CustomEvent('btnClick')); + this.LuigiClient.publishEvent(new CustomEvent('sendInput', { detail: 'My own event data' })); } }); - this.$button2 = this._shadowRoot.querySelector('#addNodeParams'); - this.$button2.addEventListener('click', () => { + this.$addNodeParamsBtn = this._shadowRoot.querySelector('#addNodeParams'); + this.$addNodeParamsBtn.addEventListener('click', () => { if (this.LuigiClient) { this.LuigiClient.addNodeParams({ Luigi: 'rocks' }, true); } }); - this.$button3 = this._shadowRoot.querySelector('#getNodeParams'); - this.$button3.addEventListener('click', () => { + this.$getNodeParamsBtn = this._shadowRoot.querySelector('#getNodeParams'); + this.$getNodeParamsBtn.addEventListener('click', () => { if (this.LuigiClient) { let nodeParams = this.LuigiClient.getNodeParams(false); this.LuigiClient.uxManager().showAlert({ diff --git a/container/test-app/wc/clientAPI.html b/container/test-app/wc/clientAPI.html index 186331bdac..c9cd0ae3fa 100644 --- a/container/test-app/wc/clientAPI.html +++ b/container/test-app/wc/clientAPI.html @@ -10,7 +10,9 @@

component based microfrontend

- +
@@ -49,7 +51,7 @@

defer-init="true" >

- +
@@ -70,10 +72,14 @@

deferInitContainer.init(); }); // document.querySelector('luigi-container'); - const container = document.querySelector('[data-test-id="luigi-client-api-test-01"]') + const container = document.querySelector( + '[data-test-id="luigi-client-api-test-01"]' + ); const sendCustomMsgBtn = document.getElementById('sendCustomMessageBtn'); sendCustomMsgBtn.addEventListener('click', () => { - container.sendCustomMessage('custom-message-id', {dataToSend: 'cool custom Message'}); + container.sendCustomMessage('custom-message-id', { + dataToSend: 'cool custom Message' + }); }); [...document.querySelectorAll('luigi-container')].forEach(luigiContainer => { @@ -96,9 +102,8 @@

} ); luigiContainer.addEventListener(MFEventID.CUSTOM_MESSAGE, event => { - console.log('Custom Event', event); if (event.detail.id !== 'timer') { - alert(event.detail.id); + alert(event.detail.data); } }); diff --git a/container/test-app/wc/helloWorldWC.js b/container/test-app/wc/helloWorldWC.js index 2618d59a0f..88cd9aaffd 100644 --- a/container/test-app/wc/helloWorldWC.js +++ b/container/test-app/wc/helloWorldWC.js @@ -14,7 +14,7 @@ export default class extends HTMLElement { templateBtn.innerHTML = ''; const templateBtn2 = document.createElement('template'); - templateBtn2.innerHTML = ''; + templateBtn2.innerHTML = ''; const addNodeParamsBtn = document.createElement('template'); addNodeParamsBtn.innerHTML = ''; @@ -90,7 +90,6 @@ export default class extends HTMLElement { const customMessageDiv = document.createElement('template'); customMessageDiv.innerHTML = '
Received Custom Message:
'; - this._shadowRoot = this.attachShadow({ mode: 'open', @@ -141,20 +140,21 @@ export default class extends HTMLElement { }); } }); - this._shadowRoot.querySelector('.button2').addEventListener('click', () => { + this._shadowRoot.querySelector('#publishEvent').addEventListener('click', () => { if (this.LuigiClient) { - this.LuigiClient.publishEvent(new CustomEvent('btnClick')); + // send a custom event (similar to sendCustomMessage) + this.LuigiClient.publishEvent(new CustomEvent('sendMSG', { detail: 'My Custom Message from Microfrontend' })); } }); - this.$button2 = this._shadowRoot.querySelector('#addNodeParams'); - this.$button2.addEventListener('click', () => { + this.$addNodeParamsBtn = this._shadowRoot.querySelector('#addNodeParams'); + this.$addNodeParamsBtn.addEventListener('click', () => { if (this.LuigiClient) { this.LuigiClient.addNodeParams({ Luigi: 'rocks' }, true); } }); - this.$button3 = this._shadowRoot.querySelector('#getNodeParams'); - this.$button3.addEventListener('click', () => { + this.$getNodeParamsBtn = this._shadowRoot.querySelector('#getNodeParams'); + this.$getNodeParamsBtn.addEventListener('click', () => { if (this.LuigiClient) { let nodeParams = this.LuigiClient.getNodeParams(false); this.LuigiClient.uxManager().showAlert({ @@ -313,13 +313,12 @@ export default class extends HTMLElement { } }); - this.addEventListener('custom-message-id', (event) => { - console.log('custom message received: ', event.detail) + this.addEventListener('custom-message-id', event => { + console.log('custom message received: ', event.detail); const customMessageDiv = this._shadowRoot.querySelector('#customMessageDiv'); customMessageDiv.textContent = `Received Custom Message: ${event.detail.dataToSend}`; - customMessageDiv.style = "color: red;"; - }) - + customMessageDiv.style = 'color: red;'; + }); } get context() { diff --git a/docs/luigi-client-api.md b/docs/luigi-client-api.md index 740410c17c..a13e9351bb 100644 --- a/docs/luigi-client-api.md +++ b/docs/luigi-client-api.md @@ -33,6 +33,72 @@ This document outlines the features provided by the Luigi Client API. It covers Use the functions and parameters to define the Lifecycle of listeners, navigation nodes, and Event data. +#### publishEvent + + + +Publish an event that can be listened to from the container host. + +Similar to [sendCustomMessage](luigi-client-api.md#sendCustomMessage) but for WebComponent based microfrontends only. + +##### Parameters + +* `event` **[CustomEvent](https://developer.mozilla.org/docs/Web/API/CustomEvent/CustomEvent)** Custom event to be published + +##### Examples + +```javascript +// case 1: publish an event from a WC based microfrontend + +// wcComponent.js +// sending a message to parent host +this.LuigiClient.publishEvent(new CustomEvent('sendSomeMsg', { detail: 'My own message' })); + +// host.html +myContainer.addEventListener('custom-message', event => { + console.log('My custom message from the microfrontend', event.detail.data); +} + +// case 2: publish an event from a compound microfrontend + +// secondChild.js +// Set the custom event name = 'sendInput' and +// send a message to its parent (main.html) and sibling (firstChild.js) +this.LuigiClient.publishEvent(new CustomEvent('sendInput', { detail: 'My own message' })); + +// main.html +myContainer.addEventListener('custom-message', event => { + console.log('My custom message from microfrontend', event.detail.data); +} + +// Note: eventListeners.name must match CustomEvent name above +// eventListeners.source = input1 = id of secondChild.js, which is where the message being sent from +compoundConfig = { + ... + children: [ + { + viewUrl: 'firstChild.js' + ... + eventListeners: [ + { + source: 'input1', + name: 'sendInput', + action: 'update', + dataConverter: data => { + console.log( + 'dataConverter(): Received Custom Message from "input1" MF ' + data + ); + return 'new text: ' + data; + } + } + ] + }, + { + viewUrl: 'secondChild.js', + id: 'input1', + } +``` + #### isLuigiClientInitialized Check if LuigiClient is initialized diff --git a/scripts/package.json b/scripts/package.json index 3d6405fab9..fbfc9a5ca5 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -10,7 +10,7 @@ "docu:container:generate:container-api": "documentation readme ../container/typings/LuigiContainer.svelte.d.ts --parse-extension ts -f md --readme-file=../docs/luigi-container-api.md --section=\"API Reference\" --markdown-toc=false --quiet --github false", "docu:container:generate:compound-container-api": "documentation readme ../container/typings/LuigiCompoundContainer.svelte.d.ts --parse-extension ts -f md --readme-file=../docs/luigi-compound-container-api.md --section=\"API Reference\" --markdown-toc=false --quiet --github false", "docu:client": "npm run docu:client:validate && npm run docu:client:generate:section", - "docu:client:generate:section": "documentation readme ../client/src/luigi-client.js -f md --readme-file=../docs/luigi-client-api.md --section=\"API Reference\" --markdown-toc=false --quiet --github false", + "docu:client:generate:section": "documentation readme ../client/src/luigi-client.js ../client/luigi-client-wc-docu-mixin.js -f md --readme-file=../docs/luigi-client-api.md --section=\"API Reference\" --markdown-toc=false --quiet --github false", "docu:client:validate": "documentation lint ../client/src/luigi-client.js", "docu:core": "npm run docu:core:validate && npm run docu:core:generate:sections", "docu:core:validate": "documentation lint --shallow ../core/src/core-api/config.js ../core/src/core-api/elements.js ../core/src/core-api/auth.js ../core/src/core-api/navigation.js ../core/src/core-api/i18n.js ../core/src/core-api/custom-messages.js ../core/src/core-api/ux.js ../core/src/core-api/globalsearch.js ../core/src/core-api/theming.js ../core/src/core-api/featuretoggles.js",