From 44f2d3b87a8ac8632ba407dcd6f9c806f15fd618 Mon Sep 17 00:00:00 2001 From: "Doberer, Johannes" Date: Fri, 30 Aug 2024 11:22:02 +0200 Subject: [PATCH 01/14] Generation of custom-element manifest --- container/package-lock.json | 342 +++++++++++++++ container/package.json | 6 +- container/public/custom-elements.json | 601 ++++++++++++++++++++++++++ 3 files changed, 947 insertions(+), 2 deletions(-) create mode 100644 container/public/custom-elements.json diff --git a/container/package-lock.json b/container/package-lock.json index 417474d9a4..0886c0da42 100644 --- a/container/package-lock.json +++ b/container/package-lock.json @@ -11,6 +11,7 @@ "@babel/node": "7.22.10", "@babel/preset-env": "7.22.10", "@babel/preset-typescript": "7.22.5", + "@custom-elements-manifest/analyzer": "0.10.3", "@rollup/plugin-commonjs": "25.0.4", "@rollup/plugin-node-resolve": "15.2.0", "@rollup/plugin-typescript": "11.1.2", @@ -1945,6 +1946,91 @@ "node": ">=0.1.90" } }, + "node_modules/@custom-elements-manifest/analyzer": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@custom-elements-manifest/analyzer/-/analyzer-0.10.3.tgz", + "integrity": "sha512-e2Ax59vK9sNedmDlPqZS11L54iAlKSjOJuv5etpTy5SygLBW3GcUtocHZm8wO013L0griTPpgWB0tuV7/JXy5A==", + "dev": true, + "dependencies": { + "@custom-elements-manifest/find-dependencies": "^0.0.5", + "@github/catalyst": "^1.6.0", + "@web/config-loader": "0.1.3", + "chokidar": "3.5.2", + "command-line-args": "5.1.2", + "comment-parser": "1.2.4", + "custom-elements-manifest": "1.0.0", + "debounce": "1.2.1", + "globby": "11.0.4", + "typescript": "~5.4.2" + }, + "bin": { + "cem": "cem.js", + "custom-elements-manifest": "cem.js" + } + }, + "node_modules/@custom-elements-manifest/analyzer/node_modules/chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/@custom-elements-manifest/analyzer/node_modules/globby": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@custom-elements-manifest/analyzer/node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@custom-elements-manifest/find-dependencies": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@custom-elements-manifest/find-dependencies/-/find-dependencies-0.0.5.tgz", + "integrity": "sha512-fKIMMZCDFSoL2ySUoz8knWgpV4jpb0lUXgLOvdZQMQFHxgxz1PqOJpUIypwvEVyKk3nEHRY4f10gNol02HjeCg==", + "dev": true, + "dependencies": { + "es-module-lexer": "^0.9.3" + } + }, "node_modules/@cypress/request": { "version": "2.88.12", "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.12.tgz", @@ -2094,6 +2180,12 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@github/catalyst": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@github/catalyst/-/catalyst-1.6.0.tgz", + "integrity": "sha512-u8A+DameixqpeyHzvnJWTGj+wfiskQOYHzSiJscCWVfMkIT3rxnbHMtGh3lMthaRY21nbUOK71WcsCnCrXhBJQ==", + "dev": true + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.11", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", @@ -3577,6 +3669,18 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@web/config-loader": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@web/config-loader/-/config-loader-0.1.3.tgz", + "integrity": "sha512-XVKH79pk4d3EHRhofete8eAnqto1e8mCRAqPV00KLNFzCWSe8sWmLnqKCqkPNARC6nksMaGrATnA5sPDRllMpQ==", + "dev": true, + "dependencies": { + "semver": "^7.3.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -3760,6 +3864,15 @@ "dequal": "^2.0.3" } }, + "node_modules/array-back": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", + "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", + "dev": true, + "engines": { + "node": ">=12.17" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", @@ -4752,12 +4865,36 @@ "node": ">= 0.8" } }, + "node_modules/command-line-args": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.1.2.tgz", + "integrity": "sha512-fytTsbndLbl+pPWtS0CxLV3BEWw9wJayB8NnU2cbQqVPsNdYezQeT+uIQv009m+GShnMNyuoBrRo8DTmuTfSCA==", + "dev": true, + "dependencies": { + "array-back": "^6.1.2", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/comment-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.2.4.tgz", + "integrity": "sha512-pm0b+qv+CkWNriSTMsfnjChF9kH0kxz55y44Wo5le9qLxMj5xDQAaEd9ZN1ovSuk9CsrncWaFwgpOMg7ClJwkw==", + "dev": true, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/common-tags": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", @@ -5207,6 +5344,12 @@ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", "dev": true }, + "node_modules/custom-elements-manifest": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/custom-elements-manifest/-/custom-elements-manifest-1.0.0.tgz", + "integrity": "sha512-j59k0ExGCKA8T6Mzaq+7axc+KVHwpEphEERU7VZ99260npu/p/9kd+Db+I3cGKxHkM5y6q5gnlXn00mzRQkX2A==", + "dev": true + }, "node_modules/cypress": { "version": "12.17.4", "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.17.4.tgz", @@ -5423,6 +5566,12 @@ "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==", "dev": true }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -5738,6 +5887,12 @@ "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", "dev": true }, + "node_modules/es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true + }, "node_modules/es-set-tostringtag": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", @@ -6582,6 +6737,27 @@ "semver": "bin/semver" } }, + "node_modules/find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "dev": true, + "dependencies": { + "array-back": "^3.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/find-replace/node_modules/array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -10087,6 +10263,12 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -12807,6 +12989,15 @@ "node": ">=14.17" } }, + "node_modules/typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -14580,6 +14771,71 @@ "dev": true, "optional": true }, + "@custom-elements-manifest/analyzer": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@custom-elements-manifest/analyzer/-/analyzer-0.10.3.tgz", + "integrity": "sha512-e2Ax59vK9sNedmDlPqZS11L54iAlKSjOJuv5etpTy5SygLBW3GcUtocHZm8wO013L0griTPpgWB0tuV7/JXy5A==", + "dev": true, + "requires": { + "@custom-elements-manifest/find-dependencies": "^0.0.5", + "@github/catalyst": "^1.6.0", + "@web/config-loader": "0.1.3", + "chokidar": "3.5.2", + "command-line-args": "5.1.2", + "comment-parser": "1.2.4", + "custom-elements-manifest": "1.0.0", + "debounce": "1.2.1", + "globby": "11.0.4", + "typescript": "~5.4.2" + }, + "dependencies": { + "chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "globby": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + } + }, + "typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true + } + } + }, + "@custom-elements-manifest/find-dependencies": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@custom-elements-manifest/find-dependencies/-/find-dependencies-0.0.5.tgz", + "integrity": "sha512-fKIMMZCDFSoL2ySUoz8knWgpV4jpb0lUXgLOvdZQMQFHxgxz1PqOJpUIypwvEVyKk3nEHRY4f10gNol02HjeCg==", + "dev": true, + "requires": { + "es-module-lexer": "^0.9.3" + } + }, "@cypress/request": { "version": "2.88.12", "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.12.tgz", @@ -14697,6 +14953,12 @@ "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==", "dev": true }, + "@github/catalyst": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@github/catalyst/-/catalyst-1.6.0.tgz", + "integrity": "sha512-u8A+DameixqpeyHzvnJWTGj+wfiskQOYHzSiJscCWVfMkIT3rxnbHMtGh3lMthaRY21nbUOK71WcsCnCrXhBJQ==", + "dev": true + }, "@humanwhocodes/config-array": { "version": "0.11.11", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", @@ -15813,6 +16075,15 @@ "eslint-visitor-keys": "^3.4.1" } }, + "@web/config-loader": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@web/config-loader/-/config-loader-0.1.3.tgz", + "integrity": "sha512-XVKH79pk4d3EHRhofete8eAnqto1e8mCRAqPV00KLNFzCWSe8sWmLnqKCqkPNARC6nksMaGrATnA5sPDRllMpQ==", + "dev": true, + "requires": { + "semver": "^7.3.4" + } + }, "abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -15943,6 +16214,12 @@ "dequal": "^2.0.3" } }, + "array-back": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", + "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", + "dev": true + }, "array-buffer-byte-length": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", @@ -16673,12 +16950,30 @@ "delayed-stream": "~1.0.0" } }, + "command-line-args": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.1.2.tgz", + "integrity": "sha512-fytTsbndLbl+pPWtS0CxLV3BEWw9wJayB8NnU2cbQqVPsNdYezQeT+uIQv009m+GShnMNyuoBrRo8DTmuTfSCA==", + "dev": true, + "requires": { + "array-back": "^6.1.2", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + } + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "comment-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.2.4.tgz", + "integrity": "sha512-pm0b+qv+CkWNriSTMsfnjChF9kH0kxz55y44Wo5le9qLxMj5xDQAaEd9ZN1ovSuk9CsrncWaFwgpOMg7ClJwkw==", + "dev": true + }, "common-tags": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", @@ -17012,6 +17307,12 @@ } } }, + "custom-elements-manifest": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/custom-elements-manifest/-/custom-elements-manifest-1.0.0.tgz", + "integrity": "sha512-j59k0ExGCKA8T6Mzaq+7axc+KVHwpEphEERU7VZ99260npu/p/9kd+Db+I3cGKxHkM5y6q5gnlXn00mzRQkX2A==", + "dev": true + }, "cypress": { "version": "12.17.4", "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.17.4.tgz", @@ -17179,6 +17480,12 @@ "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==", "dev": true }, + "debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -17418,6 +17725,12 @@ "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", "dev": true }, + "es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true + }, "es-set-tostringtag": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", @@ -18037,6 +18350,23 @@ } } }, + "find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "dev": true, + "requires": { + "array-back": "^3.0.1" + }, + "dependencies": { + "array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "dev": true + } + } + }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -20600,6 +20930,12 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true + }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -22575,6 +22911,12 @@ "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", "dev": true }, + "typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "dev": true + }, "unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", diff --git a/container/package.json b/container/package.json index 37c3d22c48..933c71915c 100644 --- a/container/package.json +++ b/container/package.json @@ -28,7 +28,8 @@ "cypress-browser": "cypress open --e2e --browser chrome -c video=false", "release":"node release-cli.js", "replace-version-in-docu": "node prepareNextRelease.js", - "sync-event-typings":"cp src/constants/communication.ts typings/constants/events.d.ts" + "sync-event-typings":"cp src/constants/communication.ts typings/constants/events.d.ts", + "generate-cem": "custom-elements-manifest analyze --globs './src/*.svelte' --outdir './public'" }, "devDependencies": { "@babel/node": "7.22.10", @@ -60,7 +61,8 @@ "tslib": "2.6.1", "typescript": "5.1.6", "cli-color": "^2.0.4", - "github-api": "^3.4.0" + "github-api": "^3.4.0", + "@custom-elements-manifest/analyzer":"0.10.3" }, "engines": { "node": ">=18.19.1" diff --git a/container/public/custom-elements.json b/container/public/custom-elements.json new file mode 100644 index 0000000000..022b38fc3f --- /dev/null +++ b/container/public/custom-elements.json @@ -0,0 +1,601 @@ +{ + "schemaVersion": "1.0.0", + "readme": "", + "modules": [ + { + "kind": "javascript-module", + "path": "src/LuigiCompoundContainer.svelte", + "declarations": [ + { + "kind": "variable", + "name": "viewurl", + "type": { + "text": "string" + } + }, + { + "kind": "variable", + "name": "webcomponent", + "type": { + "text": "any" + } + }, + { + "kind": "variable", + "name": "context", + "type": { + "text": "string" + } + }, + { + "kind": "variable", + "name": "deferInit", + "type": { + "text": "boolean" + } + }, + { + "kind": "variable", + "name": "noShadow", + "type": { + "text": "boolean" + } + }, + { + "kind": "variable", + "name": "compoundConfig", + "type": { + "text": "any" + } + }, + { + "kind": "variable", + "name": "nodeParams", + "type": { + "text": "any" + } + }, + { + "kind": "variable", + "name": "searchParams", + "type": { + "text": "any" + } + }, + { + "kind": "variable", + "name": "pathParams", + "type": { + "text": "any" + } + }, + { + "kind": "variable", + "name": "clientPermissions", + "type": { + "text": "any" + } + }, + { + "kind": "variable", + "name": "userSettings", + "type": { + "text": "any" + } + }, + { + "kind": "variable", + "name": "anchor", + "type": { + "text": "string" + } + }, + { + "kind": "variable", + "name": "dirtyStatus", + "type": { + "text": "boolean" + } + }, + { + "kind": "variable", + "name": "hasBack", + "type": { + "text": "boolean" + } + }, + { + "kind": "variable", + "name": "documentTitle", + "type": { + "text": "string" + } + }, + { + "kind": "function", + "name": "unwarn" + } + ], + "exports": [ + { + "kind": "js", + "name": "viewurl", + "declaration": { + "name": "viewurl", + "module": "src/LuigiCompoundContainer.svelte" + } + }, + { + "kind": "js", + "name": "webcomponent", + "declaration": { + "name": "webcomponent", + "module": "src/LuigiCompoundContainer.svelte" + } + }, + { + "kind": "js", + "name": "context", + "declaration": { + "name": "context", + "module": "src/LuigiCompoundContainer.svelte" + } + }, + { + "kind": "js", + "name": "deferInit", + "declaration": { + "name": "deferInit", + "module": "src/LuigiCompoundContainer.svelte" + } + }, + { + "kind": "js", + "name": "noShadow", + "declaration": { + "name": "noShadow", + "module": "src/LuigiCompoundContainer.svelte" + } + }, + { + "kind": "js", + "name": "compoundConfig", + "declaration": { + "name": "compoundConfig", + "module": "src/LuigiCompoundContainer.svelte" + } + }, + { + "kind": "js", + "name": "nodeParams", + "declaration": { + "name": "nodeParams", + "module": "src/LuigiCompoundContainer.svelte" + } + }, + { + "kind": "js", + "name": "searchParams", + "declaration": { + "name": "searchParams", + "module": "src/LuigiCompoundContainer.svelte" + } + }, + { + "kind": "js", + "name": "pathParams", + "declaration": { + "name": "pathParams", + "module": "src/LuigiCompoundContainer.svelte" + } + }, + { + "kind": "js", + "name": "clientPermissions", + "declaration": { + "name": "clientPermissions", + "module": "src/LuigiCompoundContainer.svelte" + } + }, + { + "kind": "js", + "name": "userSettings", + "declaration": { + "name": "userSettings", + "module": "src/LuigiCompoundContainer.svelte" + } + }, + { + "kind": "js", + "name": "anchor", + "declaration": { + "name": "anchor", + "module": "src/LuigiCompoundContainer.svelte" + } + }, + { + "kind": "js", + "name": "dirtyStatus", + "declaration": { + "name": "dirtyStatus", + "module": "src/LuigiCompoundContainer.svelte" + } + }, + { + "kind": "js", + "name": "hasBack", + "declaration": { + "name": "hasBack", + "module": "src/LuigiCompoundContainer.svelte" + } + }, + { + "kind": "js", + "name": "documentTitle", + "declaration": { + "name": "documentTitle", + "module": "src/LuigiCompoundContainer.svelte" + } + }, + { + "kind": "js", + "name": "unwarn", + "declaration": { + "name": "unwarn", + "module": "src/LuigiCompoundContainer.svelte" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/LuigiContainer.svelte", + "declarations": [ + { + "kind": "variable", + "name": "viewurl", + "type": { + "text": "string" + } + }, + { + "kind": "variable", + "name": "context", + "type": { + "text": "string" + } + }, + { + "kind": "variable", + "name": "label", + "type": { + "text": "string" + } + }, + { + "kind": "variable", + "name": "webcomponent", + "type": { + "text": "any" + } + }, + { + "kind": "variable", + "name": "deferInit", + "type": { + "text": "boolean" + } + }, + { + "kind": "variable", + "name": "noShadow", + "type": { + "text": "Boolean" + } + }, + { + "kind": "variable", + "name": "locale", + "type": { + "text": "string" + } + }, + { + "kind": "variable", + "name": "theme", + "type": { + "text": "string" + } + }, + { + "kind": "variable", + "name": "activeFeatureToggleList", + "type": { + "text": "string[]" + } + }, + { + "kind": "variable", + "name": "skipInitCheck", + "type": { + "text": "boolean" + } + }, + { + "kind": "variable", + "name": "nodeParams", + "type": { + "text": "any" + } + }, + { + "kind": "variable", + "name": "searchParams", + "type": { + "text": "any" + } + }, + { + "kind": "variable", + "name": "pathParams", + "type": { + "text": "any" + } + }, + { + "kind": "variable", + "name": "clientPermissions", + "type": { + "text": "any" + } + }, + { + "kind": "variable", + "name": "dirtyStatus", + "type": { + "text": "boolean" + } + }, + { + "kind": "variable", + "name": "hasBack", + "type": { + "text": "boolean" + } + }, + { + "kind": "variable", + "name": "documentTitle", + "type": { + "text": "string" + } + }, + { + "kind": "variable", + "name": "allowRules", + "type": { + "text": "string[]" + } + }, + { + "kind": "variable", + "name": "sandboxRules", + "type": { + "text": "string[]" + } + }, + { + "kind": "variable", + "name": "userSettings", + "type": { + "text": "any" + } + }, + { + "kind": "variable", + "name": "anchor", + "type": { + "text": "string" + } + }, + { + "kind": "variable", + "name": "authData", + "type": { + "text": "any" + } + }, + { + "kind": "function", + "name": "unwarn" + } + ], + "exports": [ + { + "kind": "js", + "name": "viewurl", + "declaration": { + "name": "viewurl", + "module": "src/LuigiContainer.svelte" + } + }, + { + "kind": "js", + "name": "context", + "declaration": { + "name": "context", + "module": "src/LuigiContainer.svelte" + } + }, + { + "kind": "js", + "name": "label", + "declaration": { + "name": "label", + "module": "src/LuigiContainer.svelte" + } + }, + { + "kind": "js", + "name": "webcomponent", + "declaration": { + "name": "webcomponent", + "module": "src/LuigiContainer.svelte" + } + }, + { + "kind": "js", + "name": "deferInit", + "declaration": { + "name": "deferInit", + "module": "src/LuigiContainer.svelte" + } + }, + { + "kind": "js", + "name": "noShadow", + "declaration": { + "name": "noShadow", + "module": "src/LuigiContainer.svelte" + } + }, + { + "kind": "js", + "name": "locale", + "declaration": { + "name": "locale", + "module": "src/LuigiContainer.svelte" + } + }, + { + "kind": "js", + "name": "theme", + "declaration": { + "name": "theme", + "module": "src/LuigiContainer.svelte" + } + }, + { + "kind": "js", + "name": "activeFeatureToggleList", + "declaration": { + "name": "activeFeatureToggleList", + "module": "src/LuigiContainer.svelte" + } + }, + { + "kind": "js", + "name": "skipInitCheck", + "declaration": { + "name": "skipInitCheck", + "module": "src/LuigiContainer.svelte" + } + }, + { + "kind": "js", + "name": "nodeParams", + "declaration": { + "name": "nodeParams", + "module": "src/LuigiContainer.svelte" + } + }, + { + "kind": "js", + "name": "searchParams", + "declaration": { + "name": "searchParams", + "module": "src/LuigiContainer.svelte" + } + }, + { + "kind": "js", + "name": "pathParams", + "declaration": { + "name": "pathParams", + "module": "src/LuigiContainer.svelte" + } + }, + { + "kind": "js", + "name": "clientPermissions", + "declaration": { + "name": "clientPermissions", + "module": "src/LuigiContainer.svelte" + } + }, + { + "kind": "js", + "name": "dirtyStatus", + "declaration": { + "name": "dirtyStatus", + "module": "src/LuigiContainer.svelte" + } + }, + { + "kind": "js", + "name": "hasBack", + "declaration": { + "name": "hasBack", + "module": "src/LuigiContainer.svelte" + } + }, + { + "kind": "js", + "name": "documentTitle", + "declaration": { + "name": "documentTitle", + "module": "src/LuigiContainer.svelte" + } + }, + { + "kind": "js", + "name": "allowRules", + "declaration": { + "name": "allowRules", + "module": "src/LuigiContainer.svelte" + } + }, + { + "kind": "js", + "name": "sandboxRules", + "declaration": { + "name": "sandboxRules", + "module": "src/LuigiContainer.svelte" + } + }, + { + "kind": "js", + "name": "userSettings", + "declaration": { + "name": "userSettings", + "module": "src/LuigiContainer.svelte" + } + }, + { + "kind": "js", + "name": "anchor", + "declaration": { + "name": "anchor", + "module": "src/LuigiContainer.svelte" + } + }, + { + "kind": "js", + "name": "authData", + "declaration": { + "name": "authData", + "module": "src/LuigiContainer.svelte" + } + }, + { + "kind": "js", + "name": "unwarn", + "declaration": { + "name": "unwarn", + "module": "src/LuigiContainer.svelte" + } + } + ] + } + ] +} From 934e194c61f15ccee7218772aed330bb83c949db Mon Sep 17 00:00:00 2001 From: JohannesDoberer Date: Mon, 28 Oct 2024 11:36:47 +0100 Subject: [PATCH 02/14] prettier --- client/src/lifecycleManager.js | 22 +- client/src/linkManager.js | 10 +- client/src/uxManager.js | 8 +- .../compound/wc-compound-container.cy.js | 8 +- .../test-app/iframe/iframe-container.cy.js | 20 +- .../e2e/test-app/iframe/iframe-cookies.cy.js | 4 +- .../e2e/test-app/iframe/iframe-settings.cy.js | 30 +- .../e2e/test-app/wc/wc-container.cy.js | 7 +- container/public/LuigiCompoundContainer.js | 2 +- container/public/LuigiContainer.js | 2 +- container/src/services/container.service.ts | 412 ++--- .../src/services/webcomponents.service.ts | 1358 ++++++++--------- container/test-app/compound/helloWorldWC.js | 22 +- container/test-app/iframe/iframe-cookies.html | 11 +- .../test-app/iframe/iframe-settings.html | 4 +- .../microfrontend-luigi-client-init.html | 29 +- container/test-app/iframe/microfrontend.html | 26 +- container/test-app/index.html | 2 +- container/test-app/wc/clientAPI.html | 64 +- container/test-app/wc/helloWorldWC.js | 26 +- .../test/services/container.service.spec.ts | 1350 ++++++++-------- core/src/core-api/config.js | 14 +- core/src/main.js | 18 +- core/src/services/routing.js | 17 +- .../utilities/helpers/navigation-helpers.js | 35 +- core/src/utilities/helpers/routing-helpers.js | 26 +- core/test/core-api/config.spec.js | 9 +- .../helpers/navigation-helpers.spec.js | 10 +- core/third-party-cookies/init.html | 8 +- scripts/tools/publish-nightly.js | 19 +- scripts/tools/release-cli/release-cli.js | 12 +- .../1-angular/login-flow-nav-dropdown.cy.js | 14 +- test/e2e-test-application/src/logout.html | 2 +- website/docs/src/app.html | 12 +- .../src/luigi-config/extended/resultRender.js | 140 +- website/docs/svelte.config.js | 2 +- website/fiddle/webpack.config.js | 2 +- website/landingpage/dev/src/assets/js/app.js | 24 +- 38 files changed, 1865 insertions(+), 1916 deletions(-) diff --git a/client/src/lifecycleManager.js b/client/src/lifecycleManager.js index 3abbe9720d..eb9c5a55d2 100644 --- a/client/src/lifecycleManager.js +++ b/client/src/lifecycleManager.js @@ -13,7 +13,7 @@ class LifecycleManager extends LuigiClientBase { this.luigiInitialized = false; this.defaultContextKeys = ['context', 'internal', 'nodeParams', 'pathParams', 'searchParams']; this.setCurrentContext( - this.defaultContextKeys.reduce(function(acc, key) { + this.defaultContextKeys.reduce(function (acc, key) { acc[key] = {}; return acc; }, {}) @@ -66,7 +66,7 @@ class LifecycleManager extends LuigiClientBase { * Save context data every time navigation to a different node happens * @private */ - const setContext = rawData => { + const setContext = (rawData) => { for (let index = 0; index < this.defaultContextKeys.length; index++) { let key = this.defaultContextKeys[index]; try { @@ -80,13 +80,13 @@ class LifecycleManager extends LuigiClientBase { this.setCurrentContext(rawData); }; - const setAuthData = eventPayload => { + const setAuthData = (eventPayload) => { if (eventPayload) { this.authData = eventPayload; } }; - helpers.addEventListener('luigi.init', e => { + helpers.addEventListener('luigi.init', (e) => { setContext(e.data); setAuthData(e.data.authData); helpers.setLuigiCoreDomain(e.origin); @@ -96,15 +96,15 @@ class LifecycleManager extends LuigiClientBase { helpers.sendPostMessageToLuigiCore({ msg: 'luigi.init.ok' }); }); - helpers.addEventListener('luigi-client.inactive-microfrontend', e => { + helpers.addEventListener('luigi-client.inactive-microfrontend', (e) => { this._notifyInactive(e.origin); }); - helpers.addEventListener('luigi.auth.tokenIssued', e => { + helpers.addEventListener('luigi.auth.tokenIssued', (e) => { setAuthData(e.data.authData); }); - helpers.addEventListener('luigi.navigate', e => { + helpers.addEventListener('luigi.navigate', (e) => { setContext(e.data); if (!this.currentContext.internal.isNavigateBack && !this.currentContext.withoutSync) { const previousHash = window.location.hash; @@ -149,8 +149,8 @@ class LifecycleManager extends LuigiClientBase { if (cookies) { luigiCookie = cookies .split(';') - .map(cookie => cookie.trim()) - .find(cookie => cookie === 'luigiCookie=true'); + .map((cookie) => cookie.trim()) + .find((cookie) => cookie === 'luigiCookie=true'); } if (luigiCookie === 'luigiCookie=true') { document.cookie = 'luigiCookie=; Max-Age=-99999999; SameSite=None; Secure'; @@ -160,8 +160,8 @@ class LifecycleManager extends LuigiClientBase { if (cookies) { luigiCookie = cookies .split(';') - .map(cookie => cookie.trim()) - .find(cookie => cookie === 'luigiCookie=true'); + .map((cookie) => cookie.trim()) + .find((cookie) => cookie === 'luigiCookie=true'); } if (luigiCookie === 'luigiCookie=true') { document.cookie = 'luigiCookie=; Max-Age=-99999999; SameSite=None; Secure'; diff --git a/client/src/linkManager.js b/client/src/linkManager.js index 7c6ddcbc4d..24cd96b635 100644 --- a/client/src/linkManager.js +++ b/client/src/linkManager.js @@ -383,8 +383,8 @@ export class linkManager extends LuigiClientBase { const currentId = helpers.getRandomId(); const pathExistsPromises = this.getPromise('pathExistsPromises') || {}; pathExistsPromises[currentId] = { - resolveFn: function() {}, - then: function(resolveFn) { + resolveFn: function () {}, + then: function (resolveFn) { this.resolveFn = resolveFn; } }; @@ -393,7 +393,7 @@ export class linkManager extends LuigiClientBase { // register event listener, which will be cleaned up after this usage helpers.addEventListener( 'luigi.navigation.pathExists.answer', - function(e, listenerId) { + function (e, listenerId) { const data = e.data.data; const pathExistsPromises = this.getPromise('pathExistsPromises') || {}; if (data.correlationId === currentId) { @@ -499,8 +499,8 @@ export class linkManager extends LuigiClientBase { const currentRoutePromise = this.getPromise('getCurrentRoute') || {}; currentRoutePromise[currentId] = { - resolveFn: function() {}, - then: function(resolveFn) { + resolveFn: function () {}, + then: function (resolveFn) { this.resolveFn = resolveFn; } }; diff --git a/client/src/uxManager.js b/client/src/uxManager.js index 47d12f85c5..a98e402355 100644 --- a/client/src/uxManager.js +++ b/client/src/uxManager.js @@ -10,7 +10,7 @@ class UxManager extends LuigiClientBase { /** @private */ constructor() { super(); - helpers.addEventListener('luigi.current-locale-changed', e => { + helpers.addEventListener('luigi.current-locale-changed', (e) => { if (e.data.currentLocale && lifecycleManager.currentContext?.internal) { lifecycleManager.currentContext.internal.currentLocale = e.data.currentLocale; lifecycleManager._notifyUpdate(); @@ -183,7 +183,7 @@ class UxManager extends LuigiClientBase { const alertPromises = this.getPromise('alerts') || {}; alertPromises[settings.id] = {}; - alertPromises[settings.id].promise = new Promise(resolve => { + alertPromises[settings.id].promise = new Promise((resolve) => { alertPromises[settings.id].resolveFn = resolve; }); this.setPromise('alerts', alertPromises); @@ -297,13 +297,13 @@ class UxManager extends LuigiClientBase { * @example LuigiClient.uxManager().applyCSS(); */ applyCSS() { - document.querySelectorAll('head style[luigi-injected]').forEach(luigiInjectedStyleTag => { + document.querySelectorAll('head style[luigi-injected]').forEach((luigiInjectedStyleTag) => { luigiInjectedStyleTag.remove(); }); const vars = lifecycleManager.currentContext?.internal?.cssVariables; if (vars) { let cssString = ':root {\n'; - Object.keys(vars).forEach(key => { + Object.keys(vars).forEach((key) => { const val = vars[key]; cssString += (key.startsWith('--') ? '' : '--') + key + ':' + val + ';\n'; }); 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 9de337f9ff..e69f35bc8e 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 @@ -118,9 +118,7 @@ describe('Compound Container Tests', () => { }); it('LuigiClient API - getSkipInitCheck', () => { - cy.get(containerSelector) - .invoke('attr', 'skip-init-check') - .should('eq', 'true'); + cy.get(containerSelector).invoke('attr', 'skip-init-check').should('eq', 'true'); }); it('LuigiClient API - getActiveFeatureToggles', () => { @@ -188,7 +186,7 @@ describe('Compound Container Tests', () => { .contains('showConfirmationModal') .click() .then(() => { - cy.on('window:confirm', str => { + cy.on('window:confirm', (str) => { expect(str).to.equal('Are you sure you want to do this?'); }); expect(stub.getCall(0)).to.be.calledWith('LuigiClient.uxManager().showConfirmationModal()'); @@ -221,7 +219,7 @@ describe('Compound Container Tests', () => { cy.on('window:alert', stub); // Set up a spy on console.log - cy.window().then(win => { + cy.window().then((win) => { cy.spy(win.console, 'log').as('consoleLogSpy'); }); diff --git a/container/cypress/e2e/test-app/iframe/iframe-container.cy.js b/container/cypress/e2e/test-app/iframe/iframe-container.cy.js index 0b6b5013e1..fc502d46de 100644 --- a/container/cypress/e2e/test-app/iframe/iframe-container.cy.js +++ b/container/cypress/e2e/test-app/iframe/iframe-container.cy.js @@ -24,13 +24,11 @@ describe('Iframe Container Test', () => { cy.get(containerSelector) .shadow() .get('iframe') - .then(iframe => { + .then((iframe) => { const $body = iframe.contents().find('body'); - cy.wrap($body) - .contains('test navigate') - .click(); + cy.wrap($body).contains('test navigate').click(); - cy.location().should(loc => { + cy.location().should((loc) => { expect(loc.href).to.eq('http://localhost:8080/'); }); }); @@ -41,11 +39,9 @@ describe('Iframe Container Test', () => { cy.get(containerSelector) .shadow() .get('iframe') - .then(iframe => { + .then((iframe) => { const $body = iframe.contents().find('body'); - cy.wrap($body) - .find('#content') - .should('have.text', 'Received Custom Message: some data'); + cy.wrap($body).find('#content').should('have.text', 'Received Custom Message: some data'); }); }); @@ -55,7 +51,7 @@ describe('Iframe Container Test', () => { cy.get(containerSelector) .shadow() .get('iframe') - .then(iframe => { + .then((iframe) => { const $body = iframe.contents().find('body'); cy.wrap($body) .contains('test custom message') @@ -78,7 +74,7 @@ describe('Iframe Container Test', () => { cy.get(containerSelector) .shadow() .get('iframe') - .then(iframe => { + .then((iframe) => { const $body = iframe.contents().find('body'); cy.wrap($body) @@ -102,7 +98,7 @@ describe('Iframe Container Test', () => { cy.get(containerSelector) .shadow() .get('iframe') - .then(iframe => { + .then((iframe) => { const $body = iframe.contents().find('body'); cy.wrap($body) .contains('test get token') diff --git a/container/cypress/e2e/test-app/iframe/iframe-cookies.cy.js b/container/cypress/e2e/test-app/iframe/iframe-cookies.cy.js index e9e5e8af1a..42ecc9357b 100644 --- a/container/cypress/e2e/test-app/iframe/iframe-cookies.cy.js +++ b/container/cypress/e2e/test-app/iframe/iframe-cookies.cy.js @@ -10,9 +10,7 @@ describe('Iframe Cookies Test', () => { it('should not sent third party cookies request', () => { cy.on('window:alert', stub); - cy.get(containerSelector) - .should('have.attr', 'skip-cookie-check') - .and('match', /true/); + cy.get(containerSelector).should('have.attr', 'skip-cookie-check').and('match', /true/); cy.get(containerSelector) .shadow() .get('iframe') diff --git a/container/cypress/e2e/test-app/iframe/iframe-settings.cy.js b/container/cypress/e2e/test-app/iframe/iframe-settings.cy.js index 1cfa540c4d..8f5c5dcbe5 100644 --- a/container/cypress/e2e/test-app/iframe/iframe-settings.cy.js +++ b/container/cypress/e2e/test-app/iframe/iframe-settings.cy.js @@ -7,7 +7,7 @@ describe('Iframe Settings Test', () => { }); it('defer-init flag for iframe container', () => { - cy.get('#defer-init-test').then(iframe => { + cy.get('#defer-init-test').then((iframe) => { const $body = iframe.contents().find('main'); expect($body.children()).to.have.length(0); @@ -17,11 +17,9 @@ describe('Iframe Settings Test', () => { cy.get('#defer-init-test') .shadow() .get('iframe') - .then(iframe => { + .then((iframe) => { const $body = iframe.contents().find('body'); - cy.wrap($body) - .contains('defer-init test for iframes') - .should('exist'); + cy.wrap($body).contains('defer-init test for iframes').should('exist'); }); }); }); @@ -33,10 +31,8 @@ describe('Iframe Settings Test', () => { cy.get('#defer-init-test') .shadow() .get('iframe') - .then(elements => { - cy.get(elements.first()) - .invoke('attr', 'sandbox') - .should('eq', 'allow-modals allow-popups'); + .then((elements) => { + cy.get(elements.first()).invoke('attr', 'sandbox').should('eq', 'allow-modals allow-popups'); }); }); @@ -44,10 +40,8 @@ describe('Iframe Settings Test', () => { cy.get('#sandbox-rules-test') .shadow() .find('iframe') - .then(elements => { - cy.get(elements.last()) - .invoke('attr', 'sandbox') - .should('eq', 'allow-scripts allow-same-origin'); + .then((elements) => { + cy.get(elements.last()).invoke('attr', 'sandbox').should('eq', 'allow-scripts allow-same-origin'); }); }); @@ -76,15 +70,11 @@ describe('Iframe Settings Test', () => { it('should initialize Luigi Client', () => { cy.get('iframe').then(($iframe) => { const iframeBody = $iframe.contents().find('body'); - + const checkLuigiClientStatus = (expectedStatus) => { - cy.wrap(iframeBody) - .find('#luigiClientStatus') - .should('exist') - .invoke('text') - .should('eq', expectedStatus); + cy.wrap(iframeBody).find('#luigiClientStatus').should('exist').invoke('text').should('eq', expectedStatus); }; - + checkLuigiClientStatus('Luigi Client Initialized: Unknown'); cy.wait(2000); checkLuigiClientStatus('Luigi Client Initialized: true'); 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 65833119de..f13d71283d 100644 --- a/container/cypress/e2e/test-app/wc/wc-container.cy.js +++ b/container/cypress/e2e/test-app/wc/wc-container.cy.js @@ -133,10 +133,7 @@ describe('Web Container Test', () => { }); it('sendCustomMessage', () => { - cy.get(containerSelector) - .shadow() - .find('#customMessageDiv') - .should('have.text', 'Received Custom Message: '); + cy.get(containerSelector).shadow().find('#customMessageDiv').should('have.text', 'Received Custom Message: '); cy.get('#sendCustomMessageBtn').click(); cy.get(containerSelector) @@ -180,7 +177,7 @@ describe('Web Container Test', () => { .contains('showConfirmationModal') .click() .then(() => { - cy.on('window:confirm', str => { + cy.on('window:confirm', (str) => { expect(str).to.equal('Are you sure you want to do this?'); }); expect(stub.getCall(0)).to.be.calledWith('LuigiClient.uxManager().showConfirmationModal()'); diff --git a/container/public/LuigiCompoundContainer.js b/container/public/LuigiCompoundContainer.js index 35e5f15204..b5452a7715 100644 --- a/container/public/LuigiCompoundContainer.js +++ b/container/public/LuigiCompoundContainer.js @@ -1 +1 @@ -export { LuigiCompoundContainer as default } from "./bundle.js"; \ No newline at end of file +export { LuigiCompoundContainer as default } from './bundle.js'; diff --git a/container/public/LuigiContainer.js b/container/public/LuigiContainer.js index b9ee6e194f..a696559eb6 100644 --- a/container/public/LuigiContainer.js +++ b/container/public/LuigiContainer.js @@ -1 +1 @@ -export { LuigiContainer as default } from "./bundle.js"; \ No newline at end of file +export { LuigiContainer as default } from './bundle.js'; diff --git a/container/src/services/container.service.ts b/container/src/services/container.service.ts index dc953efb1b..f726eb27de 100644 --- a/container/src/services/container.service.ts +++ b/container/src/services/container.service.ts @@ -1,206 +1,206 @@ -import { Events } from '../constants/communication'; -import { LuigiInternalMessageID } from '../constants/internal-communication'; -import { GenericHelperFunctions } from '../utilities/helpers'; - -export class ContainerService { - /** - * Checks if the given HTML element is visible in the DOM by considering both - * its width/height and any client rectangles it may have. - * - * @param {HTMLElement} component - The HTML element to check for visibility. - * @returns {boolean} Returns true if the element is visible, otherwise false. - */ - isVisible(component: HTMLElement): boolean { - return !!(component.offsetWidth || component.offsetHeight || component.getClientRects().length); - } - - /** - * Sends a message to the iframe either with the custom keyword or any other message name - * @param iframeHandle the iframe to send the message to - * @param msg the message to be sent - * @param msgName the optional message name - */ - sendCustomMessageToIframe(iframeHandle: any, msg: any, msgName?: string) { - const messageName = msgName || 'custom'; - - if (iframeHandle.iframe.contentWindow) { - const iframeUrl = new URL(iframeHandle.iframe.src); - messageName === 'custom' - ? iframeHandle.iframe.contentWindow.postMessage({ msg: messageName, data: msg }, iframeUrl.origin) - : iframeHandle.iframe.contentWindow.postMessage({ msg: messageName, ...msg }, iframeUrl.origin); - } else { - console.error('Message target could not be resolved'); - } - } - - /** - * Dispatch an event to the given target container - * @param {string} msg the event message - * @param {HTMLElement} targetCnt the targeted HTML element onto which the event is dispatched - * @param {any} data custom data added to the event to be dispatched - * @param {Function} callback - * @param {string} callbackName - */ - dispatch(msg: string, targetCnt: HTMLElement, data: any, callback?: Function, callbackName?: string): void { - const customEvent = new CustomEvent(msg, { detail: data }); - - if (callback && GenericHelperFunctions.isFunction(callback) && callbackName) { - (customEvent as any)[callbackName] = data => { - callback(data); - }; - } - - targetCnt.dispatchEvent(customEvent); - } - - /** - * Retrieves the target container based on the event source. - * - * @param event The event object representing the source of the container. - * @returns {Object| undefined} The target container object or undefined if not found. - */ - getTargetContainer(event) { - let cnt; - - globalThis.__luigi_container_manager.container.forEach(element => { - if (element.iframeHandle?.iframe && element.iframeHandle.iframe.contentWindow === event.source) { - cnt = element; - } - }); - - return cnt; - } - - /** - * Initializes the Luigi Container Manager responsible for managing communication - * between microfrontends and dispatching events accordingly. Also adds 'message' listener to the window object with - * the defined messageListener list - * @returns __luigi_container_manager which has the added container array and message listeners - */ - getContainerManager() { - if (!globalThis.__luigi_container_manager) { - globalThis.__luigi_container_manager = { - container: [], - messageListener: event => { - // Handle incoming messages and dispatch events based on the message type - // (Custom messages, navigation requests, alert requests, etc.) - const targetCnt = this.getTargetContainer(event); - const target = targetCnt?.iframeHandle?.iframe?.contentWindow; - if (target && target === event.source) { - // messages emitted from microfrontends - const msg = event.data.msg; - - // dispatch an event depending on message - switch (msg) { - case LuigiInternalMessageID.CUSTOM_MESSAGE: - { - const evData = event.data.data; - const id = evData.id; - delete evData.id; - this.dispatch(Events.CUSTOM_MESSAGE, targetCnt, { - id: id, - _metaData: {}, - data: evData - }); - } - break; - case LuigiInternalMessageID.GET_CONTEXT: - // Automatically send a luigi.init message to complete the initial handshake with the microfrontend - target.postMessage( - { - msg: LuigiInternalMessageID.SEND_CONTEXT_HANDSHAKE, - context: targetCnt.context || {}, - internal: { - thirdPartyCookieCheck: { - disabled: targetCnt.skipCookieCheck === 'true' - } - }, - authData: targetCnt.authData || {} - }, - event.origin - ); - break; - case LuigiInternalMessageID.NAVIGATION_REQUEST: - this.dispatch(Events.NAVIGATION_REQUEST, targetCnt, event.data.params); - break; - case LuigiInternalMessageID.ALERT_REQUEST: - this.dispatch(Events.ALERT_REQUEST, targetCnt, event); - break; - case LuigiInternalMessageID.INITIALIZED: - this.dispatch(Events.INITIALIZED, targetCnt, event.data.params); - break; - case LuigiInternalMessageID.ADD_SEARCH_PARAMS_REQUEST: - this.dispatch(Events.ADD_SEARCH_PARAMS_REQUEST, targetCnt, { - data: event.data.data, - keepBrowserHistory: event.data.keepBrowserHistory - }); - break; - case LuigiInternalMessageID.ADD_NODE_PARAMS_REQUEST: - this.dispatch(Events.ADD_NODE_PARAMS_REQUEST, targetCnt, { - data: event.data.data, - keepBrowserHistory: event.data.keepBrowserHistory - }); - break; - case LuigiInternalMessageID.SHOW_CONFIRMATION_MODAL_REQUEST: - this.dispatch(Events.SHOW_CONFIRMATION_MODAL_REQUEST, targetCnt, event.data.data); - break; - case LuigiInternalMessageID.SHOW_LOADING_INDICATOR_REQUEST: - this.dispatch(Events.SHOW_LOADING_INDICATOR_REQUEST, targetCnt, event); - break; - case LuigiInternalMessageID.HIDE_LOADING_INDICATOR_REQUEST: - this.dispatch(Events.HIDE_LOADING_INDICATOR_REQUEST, targetCnt, event); - break; - case LuigiInternalMessageID.SET_CURRENT_LOCALE_REQUEST: - this.dispatch(Events.SET_CURRENT_LOCALE_REQUEST, targetCnt, event); - break; - case LuigiInternalMessageID.LOCAL_STORAGE_SET_REQUEST: - this.dispatch(Events.LOCAL_STORAGE_SET_REQUEST, targetCnt, event); - break; - case LuigiInternalMessageID.RUNTIME_ERROR_HANDLING_REQUEST: - this.dispatch(Events.RUNTIME_ERROR_HANDLING_REQUEST, targetCnt, event); - break; - case LuigiInternalMessageID.SET_ANCHOR_LINK_REQUEST: - this.dispatch(Events.SET_ANCHOR_LINK_REQUEST, targetCnt, event); - break; - case LuigiInternalMessageID.SET_THIRD_PARTY_COOKIES_REQUEST: - this.dispatch(Events.SET_THIRD_PARTY_COOKIES_REQUEST, targetCnt, event); - break; - case LuigiInternalMessageID.BACK_NAVIGATION_REQUEST: - this.dispatch(Events.BACK_NAVIGATION_REQUEST, targetCnt, event); - break; - case LuigiInternalMessageID.GET_CURRENT_ROUTE_REQUEST: - this.dispatch(Events.GET_CURRENT_ROUTE_REQUEST, targetCnt, event); - break; - case LuigiInternalMessageID.NAVIGATION_COMPLETED_REPORT: - this.dispatch(Events.NAVIGATION_COMPLETED_REPORT, targetCnt, event); - break; - case LuigiInternalMessageID.UPDATE_MODAL_PATH_DATA_REQUEST: - this.dispatch(Events.UPDATE_MODAL_PATH_DATA_REQUEST, targetCnt, event); - break; - case LuigiInternalMessageID.CHECK_PATH_EXISTS_REQUEST: - this.dispatch(Events.CHECK_PATH_EXISTS_REQUEST, targetCnt, event); - break; - case LuigiInternalMessageID.SET_DIRTY_STATUS_REQUEST: - this.dispatch(Events.SET_DIRTY_STATUS_REQUEST, targetCnt, event); - break; - } - } - } - }; - window.addEventListener('message', globalThis.__luigi_container_manager.messageListener); - } - - return globalThis.__luigi_container_manager; - } - - /** - * Adds thisComponent's object reference the the __luigi_container_manager container list - * - * @param {HTMLElement} thisComponent - The HTML element that represents the current rendered container (thisComponent) - */ - registerContainer(thisComponent: HTMLElement): void { - this.getContainerManager().container.push(thisComponent); - } -} - -export const containerService = new ContainerService(); +import { Events } from '../constants/communication'; +import { LuigiInternalMessageID } from '../constants/internal-communication'; +import { GenericHelperFunctions } from '../utilities/helpers'; + +export class ContainerService { + /** + * Checks if the given HTML element is visible in the DOM by considering both + * its width/height and any client rectangles it may have. + * + * @param {HTMLElement} component - The HTML element to check for visibility. + * @returns {boolean} Returns true if the element is visible, otherwise false. + */ + isVisible(component: HTMLElement): boolean { + return !!(component.offsetWidth || component.offsetHeight || component.getClientRects().length); + } + + /** + * Sends a message to the iframe either with the custom keyword or any other message name + * @param iframeHandle the iframe to send the message to + * @param msg the message to be sent + * @param msgName the optional message name + */ + sendCustomMessageToIframe(iframeHandle: any, msg: any, msgName?: string) { + const messageName = msgName || 'custom'; + + if (iframeHandle.iframe.contentWindow) { + const iframeUrl = new URL(iframeHandle.iframe.src); + messageName === 'custom' + ? iframeHandle.iframe.contentWindow.postMessage({ msg: messageName, data: msg }, iframeUrl.origin) + : iframeHandle.iframe.contentWindow.postMessage({ msg: messageName, ...msg }, iframeUrl.origin); + } else { + console.error('Message target could not be resolved'); + } + } + + /** + * Dispatch an event to the given target container + * @param {string} msg the event message + * @param {HTMLElement} targetCnt the targeted HTML element onto which the event is dispatched + * @param {any} data custom data added to the event to be dispatched + * @param {Function} callback + * @param {string} callbackName + */ + dispatch(msg: string, targetCnt: HTMLElement, data: any, callback?: Function, callbackName?: string): void { + const customEvent = new CustomEvent(msg, { detail: data }); + + if (callback && GenericHelperFunctions.isFunction(callback) && callbackName) { + (customEvent as any)[callbackName] = (data) => { + callback(data); + }; + } + + targetCnt.dispatchEvent(customEvent); + } + + /** + * Retrieves the target container based on the event source. + * + * @param event The event object representing the source of the container. + * @returns {Object| undefined} The target container object or undefined if not found. + */ + getTargetContainer(event) { + let cnt; + + globalThis.__luigi_container_manager.container.forEach((element) => { + if (element.iframeHandle?.iframe && element.iframeHandle.iframe.contentWindow === event.source) { + cnt = element; + } + }); + + return cnt; + } + + /** + * Initializes the Luigi Container Manager responsible for managing communication + * between microfrontends and dispatching events accordingly. Also adds 'message' listener to the window object with + * the defined messageListener list + * @returns __luigi_container_manager which has the added container array and message listeners + */ + getContainerManager() { + if (!globalThis.__luigi_container_manager) { + globalThis.__luigi_container_manager = { + container: [], + messageListener: (event) => { + // Handle incoming messages and dispatch events based on the message type + // (Custom messages, navigation requests, alert requests, etc.) + const targetCnt = this.getTargetContainer(event); + const target = targetCnt?.iframeHandle?.iframe?.contentWindow; + if (target && target === event.source) { + // messages emitted from microfrontends + const msg = event.data.msg; + + // dispatch an event depending on message + switch (msg) { + case LuigiInternalMessageID.CUSTOM_MESSAGE: + { + const evData = event.data.data; + const id = evData.id; + delete evData.id; + this.dispatch(Events.CUSTOM_MESSAGE, targetCnt, { + id: id, + _metaData: {}, + data: evData + }); + } + break; + case LuigiInternalMessageID.GET_CONTEXT: + // Automatically send a luigi.init message to complete the initial handshake with the microfrontend + target.postMessage( + { + msg: LuigiInternalMessageID.SEND_CONTEXT_HANDSHAKE, + context: targetCnt.context || {}, + internal: { + thirdPartyCookieCheck: { + disabled: targetCnt.skipCookieCheck === 'true' + } + }, + authData: targetCnt.authData || {} + }, + event.origin + ); + break; + case LuigiInternalMessageID.NAVIGATION_REQUEST: + this.dispatch(Events.NAVIGATION_REQUEST, targetCnt, event.data.params); + break; + case LuigiInternalMessageID.ALERT_REQUEST: + this.dispatch(Events.ALERT_REQUEST, targetCnt, event); + break; + case LuigiInternalMessageID.INITIALIZED: + this.dispatch(Events.INITIALIZED, targetCnt, event.data.params); + break; + case LuigiInternalMessageID.ADD_SEARCH_PARAMS_REQUEST: + this.dispatch(Events.ADD_SEARCH_PARAMS_REQUEST, targetCnt, { + data: event.data.data, + keepBrowserHistory: event.data.keepBrowserHistory + }); + break; + case LuigiInternalMessageID.ADD_NODE_PARAMS_REQUEST: + this.dispatch(Events.ADD_NODE_PARAMS_REQUEST, targetCnt, { + data: event.data.data, + keepBrowserHistory: event.data.keepBrowserHistory + }); + break; + case LuigiInternalMessageID.SHOW_CONFIRMATION_MODAL_REQUEST: + this.dispatch(Events.SHOW_CONFIRMATION_MODAL_REQUEST, targetCnt, event.data.data); + break; + case LuigiInternalMessageID.SHOW_LOADING_INDICATOR_REQUEST: + this.dispatch(Events.SHOW_LOADING_INDICATOR_REQUEST, targetCnt, event); + break; + case LuigiInternalMessageID.HIDE_LOADING_INDICATOR_REQUEST: + this.dispatch(Events.HIDE_LOADING_INDICATOR_REQUEST, targetCnt, event); + break; + case LuigiInternalMessageID.SET_CURRENT_LOCALE_REQUEST: + this.dispatch(Events.SET_CURRENT_LOCALE_REQUEST, targetCnt, event); + break; + case LuigiInternalMessageID.LOCAL_STORAGE_SET_REQUEST: + this.dispatch(Events.LOCAL_STORAGE_SET_REQUEST, targetCnt, event); + break; + case LuigiInternalMessageID.RUNTIME_ERROR_HANDLING_REQUEST: + this.dispatch(Events.RUNTIME_ERROR_HANDLING_REQUEST, targetCnt, event); + break; + case LuigiInternalMessageID.SET_ANCHOR_LINK_REQUEST: + this.dispatch(Events.SET_ANCHOR_LINK_REQUEST, targetCnt, event); + break; + case LuigiInternalMessageID.SET_THIRD_PARTY_COOKIES_REQUEST: + this.dispatch(Events.SET_THIRD_PARTY_COOKIES_REQUEST, targetCnt, event); + break; + case LuigiInternalMessageID.BACK_NAVIGATION_REQUEST: + this.dispatch(Events.BACK_NAVIGATION_REQUEST, targetCnt, event); + break; + case LuigiInternalMessageID.GET_CURRENT_ROUTE_REQUEST: + this.dispatch(Events.GET_CURRENT_ROUTE_REQUEST, targetCnt, event); + break; + case LuigiInternalMessageID.NAVIGATION_COMPLETED_REPORT: + this.dispatch(Events.NAVIGATION_COMPLETED_REPORT, targetCnt, event); + break; + case LuigiInternalMessageID.UPDATE_MODAL_PATH_DATA_REQUEST: + this.dispatch(Events.UPDATE_MODAL_PATH_DATA_REQUEST, targetCnt, event); + break; + case LuigiInternalMessageID.CHECK_PATH_EXISTS_REQUEST: + this.dispatch(Events.CHECK_PATH_EXISTS_REQUEST, targetCnt, event); + break; + case LuigiInternalMessageID.SET_DIRTY_STATUS_REQUEST: + this.dispatch(Events.SET_DIRTY_STATUS_REQUEST, targetCnt, event); + break; + } + } + } + }; + window.addEventListener('message', globalThis.__luigi_container_manager.messageListener); + } + + return globalThis.__luigi_container_manager; + } + + /** + * Adds thisComponent's object reference the the __luigi_container_manager container list + * + * @param {HTMLElement} thisComponent - The HTML element that represents the current rendered container (thisComponent) + */ + registerContainer(thisComponent: HTMLElement): void { + this.getContainerManager().container.push(thisComponent); + } +} + +export const containerService = new ContainerService(); diff --git a/container/src/services/webcomponents.service.ts b/container/src/services/webcomponents.service.ts index a9d9587c3b..142e33e280 100644 --- a/container/src/services/webcomponents.service.ts +++ b/container/src/services/webcomponents.service.ts @@ -1,679 +1,679 @@ -/* eslint no-prototype-builtins: 0 */ -import { - DefaultCompoundRenderer, - resolveRenderer, - registerEventListeners, - deSanitizeParamsMap -} from './web-component-helpers'; -import { ContainerService } from './container.service'; -import { Events } from '../constants/communication'; - -/** Methods for dealing with web components based micro frontend handling */ -export class WebComponentService { - containerService: ContainerService; - thisComponent: any; - - constructor() { - this.containerService = new ContainerService(); - } - - dynamicImport(viewUrl: string) { - // Object.freeze() used as potential marker for bundlers other than webpack - return Object.freeze(import(/* webpackIgnore: true */ viewUrl)); - } - - processViewUrl(viewUrl: string, data?: any): string { - return viewUrl; - } - - /** - * Attaches a web component with tagname wc_id and adds it to wcItemContainer, - * if attached to wc_container - * - * @param wc_id a tagname that is used when creating the web component element - * @param wcItemPlaceholder placeholder for web component container - * @param wc_container web component container element - * @param ctx context to be passed to the web component - * @param viewUrl url to render content from - * @param nodeId refers to an attribute of the web component to be identified from the rest - * @param isCompoundChild defines if rendered mf is a compound child or not - */ - attachWC( - wc_id: string, - wcItemPlaceholder: HTMLDivElement, - wc_container, - ctx, - viewUrl: string, - nodeId: string, - isCompoundChild?: boolean - ) { - if (wc_container && wc_container.contains(wcItemPlaceholder)) { - const wc = document.createElement(wc_id); - 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); - if (wc_container._luigi_node) { - wc_container._luigi_mfe_webcomponent = wc; - } - wc_container.dispatchEvent(new Event('wc_ready')); - } - } - - /** - * Function that uses the current instance of the containerService to dispatch a Luigi event to the current instance of the container - * that is 'thisComponent' - * @param msg the message to be delivered - * @param data the data to be sent - * @param callback the callback function to be called - */ - dispatchLuigiEvent(msg: string, data: any, callback?: Function) { - this.containerService.dispatch(msg, this.thisComponent, data, callback); - } - - /** - * This function is used to create the Luigi Client API for the web-component-based micro frontend. - * As the function expands with more functionality, it might be moved to a separate class. - * - * The client API here should be a reflection of the Core WC Client api from core/src/services/web-components.js - * - * @param eventBusElement the event bus to be used for cross web component communication, i.e.: for compound micro frontends container scenario - * @param nodeId refers to an attribute of the web component to be identified from the rest - * @param wc_id a tagname that is used when creating the web component element - * @param component - * @param isCompoundChild defines if rendered mf is a compound child or not - * @returns an object with the Luigi Client API - */ - createClientAPI(eventBusElement, nodeId: string, wc_id: string, component: HTMLElement, isCompoundChild?: boolean) { - return { - linkManager: () => { - let fromContext = null; - let fromClosestContext = false; - let fromVirtualTreeRoot = false; - let fromParent = false; - let nodeParams = {}; - - const linkManagerInstance = { - navigate: (route, settings = {}) => { - const options = { - fromContext, - fromClosestContext, - fromVirtualTreeRoot, - fromParent, - nodeParams, - ...settings - }; - this.dispatchLuigiEvent(Events.NAVIGATION_REQUEST, { - link: route, - ...options - }); - }, - navigateToIntent: (semanticSlug: string, params = {}): void => { - let newPath = '#?intent='; - - newPath += semanticSlug; - - if (params && Object.keys(params)?.length) { - const paramList = Object.entries(params); - - // append parameters to the path if any - if (paramList.length > 0) { - newPath += '?'; - - for (const [key, value] of paramList) { - newPath += key + '=' + value + '&'; - } - - // trim potential excessive ampersand & at the end - newPath = newPath.slice(0, -1); - } - } - - linkManagerInstance.navigate(newPath); - }, - fromClosestContext: () => { - fromClosestContext = true; - return linkManagerInstance; - }, - fromContext: navigationContext => { - fromContext = navigationContext; - return linkManagerInstance; - }, - fromVirtualTreeRoot: () => { - fromVirtualTreeRoot = true; - return linkManagerInstance; - }, - fromParent: () => { - fromParent = true; - return linkManagerInstance; - }, - getCurrentRoute: () => { - const options = { - fromContext, - fromClosestContext, - fromVirtualTreeRoot, - fromParent, - nodeParams - }; - return new Promise((resolve, reject) => { - this.containerService.dispatch( - Events.GET_CURRENT_ROUTE_REQUEST, - this.thisComponent, - { ...options }, - route => { - if (route) { - resolve(route); - } else { - reject('No current route received.'); - } - }, - 'callback' - ); - }); - }, - withParams: params => { - nodeParams = params; - return linkManagerInstance; - }, - updateTopNavigation: (): void => { - this.dispatchLuigiEvent(Events.UPDATE_TOP_NAVIGATION_REQUEST, {}); - }, - pathExists: () => { - return new Promise((resolve, reject) => { - this.containerService.dispatch( - Events.PATH_EXISTS_REQUEST, - this.thisComponent, - {}, - exists => { - if (exists) { - resolve(true); - } else { - reject(false); - } - }, - 'callback' - ); - }); - }, - openAsDrawer: (route, drawerSettings = {}) => { - linkManagerInstance.navigate(route, { drawer: drawerSettings }); - }, - openAsModal: (route, modalSettings = {}) => { - linkManagerInstance.navigate(route, { modal: modalSettings }); - }, - openAsSplitView: (route, splitViewSettings = {}) => { - linkManagerInstance.navigate(route, { - splitView: splitViewSettings - }); - }, - goBack: goBackContext => { - this.dispatchLuigiEvent(Events.GO_BACK_REQUEST, goBackContext); - }, - hasBack: () => { - return false; - } - }; - return linkManagerInstance; - }, - uxManager: () => { - return { - showAlert: alertSettings => { - this.dispatchLuigiEvent(Events.ALERT_REQUEST, alertSettings); - }, - showConfirmationModal: settings => { - return new Promise((resolve, reject) => { - this.containerService.dispatch( - Events.SHOW_CONFIRMATION_MODAL_REQUEST, - this.thisComponent, - settings, - data => { - if (data) { - resolve(data); - } else { - reject(new Error('No data')); - } - }, - 'callback' - ); - }); - }, - getCurrentTheme: (): string | undefined => { - return this.thisComponent.theme; - }, - closeUserSettings: () => { - this.dispatchLuigiEvent(Events.CLOSE_USER_SETTINGS_REQUEST, this.thisComponent.userSettings); - }, - openUserSettings: () => { - this.dispatchLuigiEvent(Events.OPEN_USER_SETTINGS_REQUEST, this.thisComponent.userSettings); - }, - collapseLeftSideNav: () => { - this.dispatchLuigiEvent(Events.COLLAPSE_LEFT_NAV_REQUEST, {}); - }, - getDirtyStatus: () => { - return this.thisComponent.dirtyStatus || false; - }, - getDocumentTitle: () => { - return this.thisComponent.documentTitle; - }, - setDocumentTitle: title => { - this.dispatchLuigiEvent(Events.SET_DOCUMENT_TITLE_REQUEST, title); - }, - removeBackdrop: () => { - this.dispatchLuigiEvent(Events.REMOVE_BACKDROP_REQUEST, {}); - }, - hideAppLoadingIndicator: () => { - this.dispatchLuigiEvent(Events.HIDE_LOADING_INDICATOR_REQUEST, {}); - } - }; - }, - getCurrentLocale: (): string | undefined => { - return this.thisComponent.locale; - }, - getActiveFeatureToggles: (): string[] => { - return this.thisComponent.activeFeatureToggleList || []; - }, - publishEvent: ev => { - if (eventBusElement && eventBusElement.eventBus) { - // compound component use case only - eventBusElement.eventBus.onPublishEvent(ev, nodeId, wc_id); - } - const payload = { - id: ev.type, - _metaData: { - nodeId, - wc_id, - src: component - }, - data: ev.detail - }; - this.dispatchLuigiEvent(Events.CUSTOM_MESSAGE, payload); - }, - luigiClientInit: () => { - this.dispatchLuigiEvent(Events.INITIALIZED, {}); - }, - addNodeParams: (params, keepBrowserHistory) => { - if (isCompoundChild) { - return; - } - this.dispatchLuigiEvent(Events.ADD_NODE_PARAMS_REQUEST, { - params, - keepBrowserHistory - }); - }, - getNodeParams: (shouldDesanitise: boolean): Object => { - if (isCompoundChild) { - return {}; - } - if (shouldDesanitise) { - return deSanitizeParamsMap(this.thisComponent.nodeParams); - } - return this.thisComponent.nodeParams || {}; - }, - setAnchor: anchor => { - if (isCompoundChild) { - return; - } - this.dispatchLuigiEvent(Events.SET_ANCHOR_LINK_REQUEST, anchor); - }, - getAnchor: (): string => { - return this.thisComponent.anchor || ''; - }, - getCoreSearchParams: (): Object => { - return this.thisComponent.searchParams || {}; - }, - getPathParams: (): Object => { - return this.thisComponent.pathParams || {}; - }, - getClientPermissions: (): Object => { - return this.thisComponent.clientPermissions || {}; - }, - getUserSettings: (): Object => { - return this.thisComponent.userSettings || {}; - }, - setViewGroupData: data => { - this.dispatchLuigiEvent(Events.SET_VIEW_GROUP_DATA_REQUEST, data); - } - }; - } - - /** - * Attaches Client Api to web component - * if __postProcess defined allow for custom setting of clientApi when developers want to decide how to add it to their mf - * otherwise just attach it to the wc webcomponent alongside the context directly. - * - * @param wc web component to attach to - * @param wc_id a tagname that is used when creating the web component element - * @param eventBusElement the event bus to be used for cross web component communication, i.e.: for compound micro frontends container scenario - * @param viewUrl url to render content from - * @param ctx context to be passed to the web component - * @param nodeId refers to an attribute of the web component to be identified from the rest - * @param isCompoundChild defines if rendered mf is a compound child or not - */ - initWC( - wc: HTMLElement | any, - wc_id, - eventBusElement, - viewUrl: string, - ctx, - nodeId: string, - isCompoundChild?: boolean - ) { - const clientAPI = this.createClientAPI(eventBusElement, nodeId, wc_id, wc, isCompoundChild); - - if (wc.__postProcess) { - const url = - new URL(document.baseURI).origin === new URL(viewUrl, document.baseURI).origin - ? new URL('./', new URL(viewUrl, document.baseURI)) - : new URL('./', viewUrl); - wc.__postProcess(ctx, clientAPI, url.origin + url.pathname); - } else { - wc.context = ctx; - wc.LuigiClient = clientAPI; - } - } - - /** - * Generates a unique web component id (tagname) based on the viewUrl - * returns a string that can be used as part of a tagname, only alphanumeric - * characters and no whitespaces. - */ - generateWCId(viewUrl: string) { - let charRep = ''; - const normalizedViewUrl = new URL(viewUrl, encodeURI(location.href)).href; - for (let i = 0; i < normalizedViewUrl.length; i++) { - charRep += normalizedViewUrl.charCodeAt(i).toString(16); - } - return 'luigi-wc-' + charRep; - } - - /** - * Does a module import from viewUrl and defines a new web component - * with the default export of the module or the first export extending HTMLElement if no default is - * specified. - * @param viewUrl url to render content from - * @param wc_id a tagname that is used when creating the web component element - * @returns a promise that gets resolved after successfull import - */ - registerWCFromUrl(viewUrl: string, wc_id: string) { - const i18nViewUrl = this.processViewUrl(viewUrl); - return new Promise((resolve, reject) => { - if (this.checkWCUrl(i18nViewUrl)) { - this.dynamicImport(i18nViewUrl) - .then(module => { - try { - if (!window.customElements.get(wc_id)) { - let cmpClazz = module.default; - if (!HTMLElement.isPrototypeOf(cmpClazz)) { - const props = Object.keys(module); - for (let i = 0; i < props.length; i++) { - cmpClazz = module[props[i]]; - if (HTMLElement.isPrototypeOf(cmpClazz)) { - break; - } - } - } - window.customElements.define(wc_id, cmpClazz); - } - resolve(1); - } catch (err) { - reject(err); - } - }) - .catch(err => { - reject(err); - }); - } else { - const message = `Error: View URL '${i18nViewUrl}' not allowed to be included`; - reject(message); - } - }); - } - - /** - * Handles the import of self registered web component bundles, i.e. the web component - * is added to the customElements registry by the bundle code rather than by luigi. - * - * @param {*} node the corresponding navigation node - * @param {*} viewUrl the source of the wc bundle - * @param {*} onload callback function executed after script attached and loaded - */ - includeSelfRegisteredWCFromUrl(node, viewUrl, onload) { - if (this.checkWCUrl(viewUrl)) { - /** Append reg function to luigi object if not present */ - if (!this.containerService.getContainerManager()._registerWebcomponent) { - this.containerService.getContainerManager()._registerWebcomponent = (srcString, el) => { - window.customElements.define(this.generateWCId(srcString), el); - }; - } - // @ts-ignore - if (!window.Luigi) { - // @ts-ignore - window.Luigi = {}; - // @ts-ignore - if (!window.Luigi._registerWebcomponent) { - // @ts-ignore - window.Luigi._registerWebcomponent = (src, element) => { - this.containerService.getContainerManager()._registerWebcomponent(src, element); - }; - } - } - const scriptTag = document.createElement('script'); - scriptTag.setAttribute('src', viewUrl); - if (node.webcomponent.type === 'module') { - scriptTag.setAttribute('type', 'module'); - } - scriptTag.setAttribute('defer', 'true'); - scriptTag.addEventListener('load', () => { - onload(); - }); - document.body.appendChild(scriptTag); - } else { - console.warn(`View URL '${viewUrl}' not allowed to be included`); - } - } - - /** - * Checks if a url is allowed to be included, based on 'navigation.validWebcomponentUrls' in luigi config. - * Returns true, if allowed. - * - * @param {*} url the url string to check - */ - checkWCUrl(url: string) { - // if (url.indexOf('://') > 0 || url.trim().indexOf('//') === 0) { - // const ur = new URL(url); - // if (ur.host === window.location.host) { - // return true; // same host is okay - // } - - // const valids = LuigiConfig.getConfigValue('navigation.validWebcomponentUrls'); - // if (valids && valids.length > 0) { - // for (let el of valids) { - // try { - // if (new RegExp(el).test(url)) { - // return true; - // } - // } catch (e) { - // console.error(e); - // } - // } - // } - // return false; - // } - // relative URL is okay - // if (url === 'test.js') { - // return false; - // } - return true; - } - - /** - * Adds a web component defined by viewUrl to the wc_container and sets the node context. - * If the web component is not defined yet, it gets imported. - * - * @param viewUrl url to render content from - * @param wc_container web component container element - * @param context luigi context - * @param node node to operate on - * @param nodeId id identifying the node - * @param isCompoundChild defines if rendered mf is a compound child or not - */ - renderWebComponent( - viewUrl: string, - wc_container: HTMLElement | any, - context: any, - node: any, - nodeId?: any, - isCompoundChild?: boolean - ) { - const i18nViewUrl = this.processViewUrl(viewUrl, { context }); - const wc_id = node?.webcomponent?.tagName || this.generateWCId(i18nViewUrl); - const wcItemPlaceholder = document.createElement('div'); - wc_container.appendChild(wcItemPlaceholder); - wc_container._luigi_node = node; - - if (window.customElements.get(wc_id)) { - this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, i18nViewUrl, nodeId, isCompoundChild); - } else { - /** Custom import function, if defined */ - if ((window as any).luigiWCFn) { - (window as any).luigiWCFn(i18nViewUrl, wc_id, wcItemPlaceholder, () => { - this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, i18nViewUrl, nodeId, isCompoundChild); - }); - } else if (node.webcomponent && node.webcomponent.selfRegistered) { - this.includeSelfRegisteredWCFromUrl(node, i18nViewUrl, () => { - this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, i18nViewUrl, nodeId, isCompoundChild); - }); - } else { - this.registerWCFromUrl(i18nViewUrl, wc_id) - .then(() => { - this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, i18nViewUrl, nodeId, isCompoundChild); - }) - .catch(error => { - console.warn('ERROR =>', error); - // dispatch an error event to be handled core side - this.containerService.dispatch(Events.RUNTIME_ERROR_HANDLING_REQUEST, this.thisComponent, error); - }); - } - } - } - - /** - * Creates a compound container according to the given renderer. - * Returns a promise that gets resolved with the created container DOM element. - * - * @param {DefaultCompoundRenderer} renderer - */ - createCompoundContainerAsync(renderer: any, ctx: any, navNode: any): Promise { - return new Promise((resolve, reject) => { - if (renderer.viewUrl) { - try { - const wc_id = navNode?.webcomponent?.tagName || this.generateWCId(renderer.viewUrl); - 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); - }); - } else { - 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); - }) - .catch(error => { - console.warn('Error: ', error); - // dispatch an error event to be handled core side - this.containerService.dispatch(Events.RUNTIME_ERROR_HANDLING_REQUEST, this.thisComponent, error); - }); - } - } catch (error) { - reject(error); - } - } else { - resolve(renderer.createCompoundContainer()); - } - }); - } - - /** - * Responsible for rendering web component compounds based on a renderer or a nesting - * micro frontend. - * - * @param {*} navNode the navigation node defining the compound - * @param {HTMLElement} wc_container the web component container dom element - * @param {*} context the luigi node context - */ - renderWebComponentCompound(navNode, wc_container: HTMLElement, context) { - let renderer; - if (navNode.webcomponent && navNode.viewUrl) { - renderer = new DefaultCompoundRenderer(); - renderer.viewUrl = this.processViewUrl(navNode.viewUrl, { context }); - renderer.createCompoundItemContainer = layoutConfig => { - const cnt = document.createElement('div'); - if (layoutConfig && layoutConfig.slot) { - cnt.setAttribute('slot', layoutConfig.slot); - } - return cnt; - }; - } else if (navNode.compound?.renderer) { - renderer = resolveRenderer(navNode.compound.renderer); - } - - renderer = renderer || new DefaultCompoundRenderer(); - return new Promise(resolve => { - this.createCompoundContainerAsync(renderer, context, navNode) - .then((compoundCnt: HTMLElement) => { - (wc_container as any)._luigi_mfe_webcomponent = compoundCnt; - (wc_container as any)._luigi_node = navNode; - const ebListeners = {}; - (compoundCnt as any).eventBus = { - listeners: ebListeners, - onPublishEvent: (event, srcNodeId, wcId) => { - const listeners = ebListeners[srcNodeId + '.' + event.type] || []; - listeners.push(...(ebListeners['*.' + event.type] || [])); - - listeners.forEach(listenerInfo => { - const target = - listenerInfo.wcElement || compoundCnt.querySelector('[nodeId=' + listenerInfo.wcElementId + ']'); - if (target) { - target.dispatchEvent( - new CustomEvent(listenerInfo.action, { - detail: listenerInfo.converter ? listenerInfo.converter(event.detail) : event.detail - }) - ); - } else { - console.debug('Could not find event target', listenerInfo); - } - }); - } - }; - navNode.compound?.children.forEach((wc, index) => { - const ctx = { ...context, ...wc.context }; - const compoundItemCnt = renderer.createCompoundItemContainer(wc.layoutConfig); - - compoundItemCnt.eventBus = (compoundCnt as any).eventBus; - renderer.attachCompoundItem(compoundCnt, compoundItemCnt); - - const nodeId = wc.id || 'gen_' + index; - this.renderWebComponent(wc.viewUrl, compoundItemCnt, ctx, wc, nodeId, true); - registerEventListeners(ebListeners, wc, nodeId); - }); - wc_container.appendChild(compoundCnt); - // listener for nesting wc - registerEventListeners(ebListeners, navNode.compound, '_root', compoundCnt); - resolve(compoundCnt); - }) - .catch(error => { - // dispatch an error event to be handled core sid - console.warn('Error: ', error); - this.containerService.dispatch(Events.RUNTIME_ERROR_HANDLING_REQUEST, this.thisComponent, error); - }); - }); - } -} +/* eslint no-prototype-builtins: 0 */ +import { + DefaultCompoundRenderer, + resolveRenderer, + registerEventListeners, + deSanitizeParamsMap +} from './web-component-helpers'; +import { ContainerService } from './container.service'; +import { Events } from '../constants/communication'; + +/** Methods for dealing with web components based micro frontend handling */ +export class WebComponentService { + containerService: ContainerService; + thisComponent: any; + + constructor() { + this.containerService = new ContainerService(); + } + + dynamicImport(viewUrl: string) { + // Object.freeze() used as potential marker for bundlers other than webpack + return Object.freeze(import(/* webpackIgnore: true */ viewUrl)); + } + + processViewUrl(viewUrl: string, data?: any): string { + return viewUrl; + } + + /** + * Attaches a web component with tagname wc_id and adds it to wcItemContainer, + * if attached to wc_container + * + * @param wc_id a tagname that is used when creating the web component element + * @param wcItemPlaceholder placeholder for web component container + * @param wc_container web component container element + * @param ctx context to be passed to the web component + * @param viewUrl url to render content from + * @param nodeId refers to an attribute of the web component to be identified from the rest + * @param isCompoundChild defines if rendered mf is a compound child or not + */ + attachWC( + wc_id: string, + wcItemPlaceholder: HTMLDivElement, + wc_container, + ctx, + viewUrl: string, + nodeId: string, + isCompoundChild?: boolean + ) { + if (wc_container && wc_container.contains(wcItemPlaceholder)) { + const wc = document.createElement(wc_id); + 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); + if (wc_container._luigi_node) { + wc_container._luigi_mfe_webcomponent = wc; + } + wc_container.dispatchEvent(new Event('wc_ready')); + } + } + + /** + * Function that uses the current instance of the containerService to dispatch a Luigi event to the current instance of the container + * that is 'thisComponent' + * @param msg the message to be delivered + * @param data the data to be sent + * @param callback the callback function to be called + */ + dispatchLuigiEvent(msg: string, data: any, callback?: Function) { + this.containerService.dispatch(msg, this.thisComponent, data, callback); + } + + /** + * This function is used to create the Luigi Client API for the web-component-based micro frontend. + * As the function expands with more functionality, it might be moved to a separate class. + * + * The client API here should be a reflection of the Core WC Client api from core/src/services/web-components.js + * + * @param eventBusElement the event bus to be used for cross web component communication, i.e.: for compound micro frontends container scenario + * @param nodeId refers to an attribute of the web component to be identified from the rest + * @param wc_id a tagname that is used when creating the web component element + * @param component + * @param isCompoundChild defines if rendered mf is a compound child or not + * @returns an object with the Luigi Client API + */ + createClientAPI(eventBusElement, nodeId: string, wc_id: string, component: HTMLElement, isCompoundChild?: boolean) { + return { + linkManager: () => { + let fromContext = null; + let fromClosestContext = false; + let fromVirtualTreeRoot = false; + let fromParent = false; + let nodeParams = {}; + + const linkManagerInstance = { + navigate: (route, settings = {}) => { + const options = { + fromContext, + fromClosestContext, + fromVirtualTreeRoot, + fromParent, + nodeParams, + ...settings + }; + this.dispatchLuigiEvent(Events.NAVIGATION_REQUEST, { + link: route, + ...options + }); + }, + navigateToIntent: (semanticSlug: string, params = {}): void => { + let newPath = '#?intent='; + + newPath += semanticSlug; + + if (params && Object.keys(params)?.length) { + const paramList = Object.entries(params); + + // append parameters to the path if any + if (paramList.length > 0) { + newPath += '?'; + + for (const [key, value] of paramList) { + newPath += key + '=' + value + '&'; + } + + // trim potential excessive ampersand & at the end + newPath = newPath.slice(0, -1); + } + } + + linkManagerInstance.navigate(newPath); + }, + fromClosestContext: () => { + fromClosestContext = true; + return linkManagerInstance; + }, + fromContext: (navigationContext) => { + fromContext = navigationContext; + return linkManagerInstance; + }, + fromVirtualTreeRoot: () => { + fromVirtualTreeRoot = true; + return linkManagerInstance; + }, + fromParent: () => { + fromParent = true; + return linkManagerInstance; + }, + getCurrentRoute: () => { + const options = { + fromContext, + fromClosestContext, + fromVirtualTreeRoot, + fromParent, + nodeParams + }; + return new Promise((resolve, reject) => { + this.containerService.dispatch( + Events.GET_CURRENT_ROUTE_REQUEST, + this.thisComponent, + { ...options }, + (route) => { + if (route) { + resolve(route); + } else { + reject('No current route received.'); + } + }, + 'callback' + ); + }); + }, + withParams: (params) => { + nodeParams = params; + return linkManagerInstance; + }, + updateTopNavigation: (): void => { + this.dispatchLuigiEvent(Events.UPDATE_TOP_NAVIGATION_REQUEST, {}); + }, + pathExists: () => { + return new Promise((resolve, reject) => { + this.containerService.dispatch( + Events.PATH_EXISTS_REQUEST, + this.thisComponent, + {}, + (exists) => { + if (exists) { + resolve(true); + } else { + reject(false); + } + }, + 'callback' + ); + }); + }, + openAsDrawer: (route, drawerSettings = {}) => { + linkManagerInstance.navigate(route, { drawer: drawerSettings }); + }, + openAsModal: (route, modalSettings = {}) => { + linkManagerInstance.navigate(route, { modal: modalSettings }); + }, + openAsSplitView: (route, splitViewSettings = {}) => { + linkManagerInstance.navigate(route, { + splitView: splitViewSettings + }); + }, + goBack: (goBackContext) => { + this.dispatchLuigiEvent(Events.GO_BACK_REQUEST, goBackContext); + }, + hasBack: () => { + return false; + } + }; + return linkManagerInstance; + }, + uxManager: () => { + return { + showAlert: (alertSettings) => { + this.dispatchLuigiEvent(Events.ALERT_REQUEST, alertSettings); + }, + showConfirmationModal: (settings) => { + return new Promise((resolve, reject) => { + this.containerService.dispatch( + Events.SHOW_CONFIRMATION_MODAL_REQUEST, + this.thisComponent, + settings, + (data) => { + if (data) { + resolve(data); + } else { + reject(new Error('No data')); + } + }, + 'callback' + ); + }); + }, + getCurrentTheme: (): string | undefined => { + return this.thisComponent.theme; + }, + closeUserSettings: () => { + this.dispatchLuigiEvent(Events.CLOSE_USER_SETTINGS_REQUEST, this.thisComponent.userSettings); + }, + openUserSettings: () => { + this.dispatchLuigiEvent(Events.OPEN_USER_SETTINGS_REQUEST, this.thisComponent.userSettings); + }, + collapseLeftSideNav: () => { + this.dispatchLuigiEvent(Events.COLLAPSE_LEFT_NAV_REQUEST, {}); + }, + getDirtyStatus: () => { + return this.thisComponent.dirtyStatus || false; + }, + getDocumentTitle: () => { + return this.thisComponent.documentTitle; + }, + setDocumentTitle: (title) => { + this.dispatchLuigiEvent(Events.SET_DOCUMENT_TITLE_REQUEST, title); + }, + removeBackdrop: () => { + this.dispatchLuigiEvent(Events.REMOVE_BACKDROP_REQUEST, {}); + }, + hideAppLoadingIndicator: () => { + this.dispatchLuigiEvent(Events.HIDE_LOADING_INDICATOR_REQUEST, {}); + } + }; + }, + getCurrentLocale: (): string | undefined => { + return this.thisComponent.locale; + }, + getActiveFeatureToggles: (): string[] => { + return this.thisComponent.activeFeatureToggleList || []; + }, + publishEvent: (ev) => { + if (eventBusElement && eventBusElement.eventBus) { + // compound component use case only + eventBusElement.eventBus.onPublishEvent(ev, nodeId, wc_id); + } + const payload = { + id: ev.type, + _metaData: { + nodeId, + wc_id, + src: component + }, + data: ev.detail + }; + this.dispatchLuigiEvent(Events.CUSTOM_MESSAGE, payload); + }, + luigiClientInit: () => { + this.dispatchLuigiEvent(Events.INITIALIZED, {}); + }, + addNodeParams: (params, keepBrowserHistory) => { + if (isCompoundChild) { + return; + } + this.dispatchLuigiEvent(Events.ADD_NODE_PARAMS_REQUEST, { + params, + keepBrowserHistory + }); + }, + getNodeParams: (shouldDesanitise: boolean): Object => { + if (isCompoundChild) { + return {}; + } + if (shouldDesanitise) { + return deSanitizeParamsMap(this.thisComponent.nodeParams); + } + return this.thisComponent.nodeParams || {}; + }, + setAnchor: (anchor) => { + if (isCompoundChild) { + return; + } + this.dispatchLuigiEvent(Events.SET_ANCHOR_LINK_REQUEST, anchor); + }, + getAnchor: (): string => { + return this.thisComponent.anchor || ''; + }, + getCoreSearchParams: (): Object => { + return this.thisComponent.searchParams || {}; + }, + getPathParams: (): Object => { + return this.thisComponent.pathParams || {}; + }, + getClientPermissions: (): Object => { + return this.thisComponent.clientPermissions || {}; + }, + getUserSettings: (): Object => { + return this.thisComponent.userSettings || {}; + }, + setViewGroupData: (data) => { + this.dispatchLuigiEvent(Events.SET_VIEW_GROUP_DATA_REQUEST, data); + } + }; + } + + /** + * Attaches Client Api to web component + * if __postProcess defined allow for custom setting of clientApi when developers want to decide how to add it to their mf + * otherwise just attach it to the wc webcomponent alongside the context directly. + * + * @param wc web component to attach to + * @param wc_id a tagname that is used when creating the web component element + * @param eventBusElement the event bus to be used for cross web component communication, i.e.: for compound micro frontends container scenario + * @param viewUrl url to render content from + * @param ctx context to be passed to the web component + * @param nodeId refers to an attribute of the web component to be identified from the rest + * @param isCompoundChild defines if rendered mf is a compound child or not + */ + initWC( + wc: HTMLElement | any, + wc_id, + eventBusElement, + viewUrl: string, + ctx, + nodeId: string, + isCompoundChild?: boolean + ) { + const clientAPI = this.createClientAPI(eventBusElement, nodeId, wc_id, wc, isCompoundChild); + + if (wc.__postProcess) { + const url = + new URL(document.baseURI).origin === new URL(viewUrl, document.baseURI).origin + ? new URL('./', new URL(viewUrl, document.baseURI)) + : new URL('./', viewUrl); + wc.__postProcess(ctx, clientAPI, url.origin + url.pathname); + } else { + wc.context = ctx; + wc.LuigiClient = clientAPI; + } + } + + /** + * Generates a unique web component id (tagname) based on the viewUrl + * returns a string that can be used as part of a tagname, only alphanumeric + * characters and no whitespaces. + */ + generateWCId(viewUrl: string) { + let charRep = ''; + const normalizedViewUrl = new URL(viewUrl, encodeURI(location.href)).href; + for (let i = 0; i < normalizedViewUrl.length; i++) { + charRep += normalizedViewUrl.charCodeAt(i).toString(16); + } + return 'luigi-wc-' + charRep; + } + + /** + * Does a module import from viewUrl and defines a new web component + * with the default export of the module or the first export extending HTMLElement if no default is + * specified. + * @param viewUrl url to render content from + * @param wc_id a tagname that is used when creating the web component element + * @returns a promise that gets resolved after successfull import + */ + registerWCFromUrl(viewUrl: string, wc_id: string) { + const i18nViewUrl = this.processViewUrl(viewUrl); + return new Promise((resolve, reject) => { + if (this.checkWCUrl(i18nViewUrl)) { + this.dynamicImport(i18nViewUrl) + .then((module) => { + try { + if (!window.customElements.get(wc_id)) { + let cmpClazz = module.default; + if (!HTMLElement.isPrototypeOf(cmpClazz)) { + const props = Object.keys(module); + for (let i = 0; i < props.length; i++) { + cmpClazz = module[props[i]]; + if (HTMLElement.isPrototypeOf(cmpClazz)) { + break; + } + } + } + window.customElements.define(wc_id, cmpClazz); + } + resolve(1); + } catch (err) { + reject(err); + } + }) + .catch((err) => { + reject(err); + }); + } else { + const message = `Error: View URL '${i18nViewUrl}' not allowed to be included`; + reject(message); + } + }); + } + + /** + * Handles the import of self registered web component bundles, i.e. the web component + * is added to the customElements registry by the bundle code rather than by luigi. + * + * @param {*} node the corresponding navigation node + * @param {*} viewUrl the source of the wc bundle + * @param {*} onload callback function executed after script attached and loaded + */ + includeSelfRegisteredWCFromUrl(node, viewUrl, onload) { + if (this.checkWCUrl(viewUrl)) { + /** Append reg function to luigi object if not present */ + if (!this.containerService.getContainerManager()._registerWebcomponent) { + this.containerService.getContainerManager()._registerWebcomponent = (srcString, el) => { + window.customElements.define(this.generateWCId(srcString), el); + }; + } + // @ts-ignore + if (!window.Luigi) { + // @ts-ignore + window.Luigi = {}; + // @ts-ignore + if (!window.Luigi._registerWebcomponent) { + // @ts-ignore + window.Luigi._registerWebcomponent = (src, element) => { + this.containerService.getContainerManager()._registerWebcomponent(src, element); + }; + } + } + const scriptTag = document.createElement('script'); + scriptTag.setAttribute('src', viewUrl); + if (node.webcomponent.type === 'module') { + scriptTag.setAttribute('type', 'module'); + } + scriptTag.setAttribute('defer', 'true'); + scriptTag.addEventListener('load', () => { + onload(); + }); + document.body.appendChild(scriptTag); + } else { + console.warn(`View URL '${viewUrl}' not allowed to be included`); + } + } + + /** + * Checks if a url is allowed to be included, based on 'navigation.validWebcomponentUrls' in luigi config. + * Returns true, if allowed. + * + * @param {*} url the url string to check + */ + checkWCUrl(url: string) { + // if (url.indexOf('://') > 0 || url.trim().indexOf('//') === 0) { + // const ur = new URL(url); + // if (ur.host === window.location.host) { + // return true; // same host is okay + // } + + // const valids = LuigiConfig.getConfigValue('navigation.validWebcomponentUrls'); + // if (valids && valids.length > 0) { + // for (let el of valids) { + // try { + // if (new RegExp(el).test(url)) { + // return true; + // } + // } catch (e) { + // console.error(e); + // } + // } + // } + // return false; + // } + // relative URL is okay + // if (url === 'test.js') { + // return false; + // } + return true; + } + + /** + * Adds a web component defined by viewUrl to the wc_container and sets the node context. + * If the web component is not defined yet, it gets imported. + * + * @param viewUrl url to render content from + * @param wc_container web component container element + * @param context luigi context + * @param node node to operate on + * @param nodeId id identifying the node + * @param isCompoundChild defines if rendered mf is a compound child or not + */ + renderWebComponent( + viewUrl: string, + wc_container: HTMLElement | any, + context: any, + node: any, + nodeId?: any, + isCompoundChild?: boolean + ) { + const i18nViewUrl = this.processViewUrl(viewUrl, { context }); + const wc_id = node?.webcomponent?.tagName || this.generateWCId(i18nViewUrl); + const wcItemPlaceholder = document.createElement('div'); + wc_container.appendChild(wcItemPlaceholder); + wc_container._luigi_node = node; + + if (window.customElements.get(wc_id)) { + this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, i18nViewUrl, nodeId, isCompoundChild); + } else { + /** Custom import function, if defined */ + if ((window as any).luigiWCFn) { + (window as any).luigiWCFn(i18nViewUrl, wc_id, wcItemPlaceholder, () => { + this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, i18nViewUrl, nodeId, isCompoundChild); + }); + } else if (node.webcomponent && node.webcomponent.selfRegistered) { + this.includeSelfRegisteredWCFromUrl(node, i18nViewUrl, () => { + this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, i18nViewUrl, nodeId, isCompoundChild); + }); + } else { + this.registerWCFromUrl(i18nViewUrl, wc_id) + .then(() => { + this.attachWC(wc_id, wcItemPlaceholder, wc_container, context, i18nViewUrl, nodeId, isCompoundChild); + }) + .catch((error) => { + console.warn('ERROR =>', error); + // dispatch an error event to be handled core side + this.containerService.dispatch(Events.RUNTIME_ERROR_HANDLING_REQUEST, this.thisComponent, error); + }); + } + } + } + + /** + * Creates a compound container according to the given renderer. + * Returns a promise that gets resolved with the created container DOM element. + * + * @param {DefaultCompoundRenderer} renderer + */ + createCompoundContainerAsync(renderer: any, ctx: any, navNode: any): Promise { + return new Promise((resolve, reject) => { + if (renderer.viewUrl) { + try { + const wc_id = navNode?.webcomponent?.tagName || this.generateWCId(renderer.viewUrl); + 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); + }); + } else { + 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); + }) + .catch((error) => { + console.warn('Error: ', error); + // dispatch an error event to be handled core side + this.containerService.dispatch(Events.RUNTIME_ERROR_HANDLING_REQUEST, this.thisComponent, error); + }); + } + } catch (error) { + reject(error); + } + } else { + resolve(renderer.createCompoundContainer()); + } + }); + } + + /** + * Responsible for rendering web component compounds based on a renderer or a nesting + * micro frontend. + * + * @param {*} navNode the navigation node defining the compound + * @param {HTMLElement} wc_container the web component container dom element + * @param {*} context the luigi node context + */ + renderWebComponentCompound(navNode, wc_container: HTMLElement, context) { + let renderer; + if (navNode.webcomponent && navNode.viewUrl) { + renderer = new DefaultCompoundRenderer(); + renderer.viewUrl = this.processViewUrl(navNode.viewUrl, { context }); + renderer.createCompoundItemContainer = (layoutConfig) => { + const cnt = document.createElement('div'); + if (layoutConfig && layoutConfig.slot) { + cnt.setAttribute('slot', layoutConfig.slot); + } + return cnt; + }; + } else if (navNode.compound?.renderer) { + renderer = resolveRenderer(navNode.compound.renderer); + } + + renderer = renderer || new DefaultCompoundRenderer(); + return new Promise((resolve) => { + this.createCompoundContainerAsync(renderer, context, navNode) + .then((compoundCnt: HTMLElement) => { + (wc_container as any)._luigi_mfe_webcomponent = compoundCnt; + (wc_container as any)._luigi_node = navNode; + const ebListeners = {}; + (compoundCnt as any).eventBus = { + listeners: ebListeners, + onPublishEvent: (event, srcNodeId, wcId) => { + const listeners = ebListeners[srcNodeId + '.' + event.type] || []; + listeners.push(...(ebListeners['*.' + event.type] || [])); + + listeners.forEach((listenerInfo) => { + const target = + listenerInfo.wcElement || compoundCnt.querySelector('[nodeId=' + listenerInfo.wcElementId + ']'); + if (target) { + target.dispatchEvent( + new CustomEvent(listenerInfo.action, { + detail: listenerInfo.converter ? listenerInfo.converter(event.detail) : event.detail + }) + ); + } else { + console.debug('Could not find event target', listenerInfo); + } + }); + } + }; + navNode.compound?.children.forEach((wc, index) => { + const ctx = { ...context, ...wc.context }; + const compoundItemCnt = renderer.createCompoundItemContainer(wc.layoutConfig); + + compoundItemCnt.eventBus = (compoundCnt as any).eventBus; + renderer.attachCompoundItem(compoundCnt, compoundItemCnt); + + const nodeId = wc.id || 'gen_' + index; + this.renderWebComponent(wc.viewUrl, compoundItemCnt, ctx, wc, nodeId, true); + registerEventListeners(ebListeners, wc, nodeId); + }); + wc_container.appendChild(compoundCnt); + // listener for nesting wc + registerEventListeners(ebListeners, navNode.compound, '_root', compoundCnt); + resolve(compoundCnt); + }) + .catch((error) => { + // dispatch an error event to be handled core sid + console.warn('Error: ', error); + this.containerService.dispatch(Events.RUNTIME_ERROR_HANDLING_REQUEST, this.thisComponent, error); + }); + }); + } +} diff --git a/container/test-app/compound/helloWorldWC.js b/container/test-app/compound/helloWorldWC.js index 0bd8936510..6d00600d59 100644 --- a/container/test-app/compound/helloWorldWC.js +++ b/container/test-app/compound/helloWorldWC.js @@ -284,21 +284,11 @@ export default class extends HTMLElement { const path = 'hello-world-wc'; const ctx = { ctx: 123 }; - this.LuigiClient.linkManager() - .fromContext(ctx) - .navigate(); - this.LuigiClient.linkManager() - .fromClosestContext() - .navigate(path); - this.LuigiClient.linkManager() - .fromVirtualTreeRoot() - .navigate(path); - this.LuigiClient.linkManager() - .fromParent(ctx) - .navigate(path); - this.LuigiClient.linkManager() - .withParams('my-params') - .navigate(path); + this.LuigiClient.linkManager().fromContext(ctx).navigate(); + this.LuigiClient.linkManager().fromClosestContext().navigate(path); + this.LuigiClient.linkManager().fromVirtualTreeRoot().navigate(path); + this.LuigiClient.linkManager().fromParent(ctx).navigate(path); + this.LuigiClient.linkManager().withParams('my-params').navigate(path); this.LuigiClient.linkManager().navigate(path); this.LuigiClient.uxManager().showAlert({ text: 'LuigiClient.linkManager().navigate()', @@ -318,7 +308,7 @@ export default class extends HTMLElement { this.LuigiClient.linkManager().updateTopNavigation(); this.LuigiClient.linkManager() .pathExists() - .then(result => { + .then((result) => { console.log('PATH EXISTS'); this.LuigiClient.uxManager().showAlert({ text: diff --git a/container/test-app/iframe/iframe-cookies.html b/container/test-app/iframe/iframe-cookies.html index 27a7d05bc1..0cacfa4c07 100644 --- a/container/test-app/iframe/iframe-cookies.html +++ b/container/test-app/iframe/iframe-cookies.html @@ -6,10 +6,11 @@

- This page is used to test **skip-cookie-check** feature for iFrame based LuigiContainer + This page is used to test **skip-cookie-check** feature for iFrame based + LuigiContainer

-
+
import Events from '../bundle.js'; const luigiContainer = document.querySelector( - '[data-test-id="iframe-based-container-test"]' + '[data-test-id="iframe-based-container-test"]', ); // SET_THIRD_PARTY_COOKIES_REQUEST - called on microfrontend startup - luigiContainer.addEventListener(Events.SET_THIRD_PARTY_COOKIES_REQUEST, event => { + luigiContainer.addEventListener(Events.SET_THIRD_PARTY_COOKIES_REQUEST, (event) => { console.log(Events.SET_THIRD_PARTY_COOKIES_REQUEST, event); alert( Events.SET_THIRD_PARTY_COOKIES_REQUEST, - 'message received: ' + JSON.stringify(event.detail.data.data) + 'message received: ' + JSON.stringify(event.detail.data.data), ); }); diff --git a/container/test-app/iframe/iframe-settings.html b/container/test-app/iframe/iframe-settings.html index 9a5b804938..1ce5062df8 100644 --- a/container/test-app/iframe/iframe-settings.html +++ b/container/test-app/iframe/iframe-settings.html @@ -55,7 +55,7 @@

const deferInitContainer = document.getElementById('defer-init-test'); const deferInitButton = document.getElementById('defer-init-button'); - deferInitButton.addEventListener('click', function() { + deferInitButton.addEventListener('click', function () { deferInitContainer.init(); }); @@ -72,7 +72,7 @@

deferInitContainer.allowRules = [ 'microphone', "camera 'none'", - "geolocation 'self' https://a.example.com https://b.example.com" + "geolocation 'self' https://a.example.com https://b.example.com", ]; }); diff --git a/container/test-app/iframe/microfrontend-luigi-client-init.html b/container/test-app/iframe/microfrontend-luigi-client-init.html index dc66e55b08..ee01c659a5 100644 --- a/container/test-app/iframe/microfrontend-luigi-client-init.html +++ b/container/test-app/iframe/microfrontend-luigi-client-init.html @@ -1,7 +1,7 @@ - + - + Luigi Client Init Test @@ -16,19 +16,26 @@ const isInitialized = LuigiClient.isLuigiClientInitialized(); console.log('isInitialized: ' + isInitialized); if (document.getElementById('luigiClientStatus')) { - document.getElementById('luigiClientStatus').innerText = `Luigi Client Initialized: ${isInitialized}`; - + document.getElementById('luigiClientStatus').innerText = + `Luigi Client Initialized: ${isInitialized}`; } return isInitialized; } - - + +
-

Luigi Client Init Test for Iframes

- - -

Luigi Client Initialized: Unknown

+

Luigi Client Init Test for Iframes

+ + +

Luigi Client Initialized: Unknown

- + diff --git a/container/test-app/iframe/microfrontend.html b/container/test-app/iframe/microfrontend.html index b69fc1a4e9..44461f5b9b 100644 --- a/container/test-app/iframe/microfrontend.html +++ b/container/test-app/iframe/microfrontend.html @@ -1,5 +1,5 @@ - + @@ -32,7 +32,7 @@ - +

Multi purpose demo page

@@ -85,7 +85,7 @@

Multi purpose demo page

header: 'Confirmation', body: 'Are you sure you want to do this?', buttonConfirm: 'Yes', - buttonDismiss: 'No' + buttonDismiss: 'No', }; LuigiClient.uxManager().showConfirmationModal(settings); } @@ -101,7 +101,7 @@

Multi purpose demo page

function testGetContext() { LuigiClient.sendCustomMessage({ id: 'my.contextMessage', - ...LuigiClient.getContext() + ...LuigiClient.getContext(), }); } @@ -110,9 +110,7 @@

Multi purpose demo page

} function testSetLocalStorage() { - LuigiClient.storageManager() - .setItem('keyExample', 'valueExample') - .then(); + LuigiClient.storageManager().setItem('keyExample', 'valueExample').then(); } // this is ran only when client is initialized and an error has occurred @@ -155,7 +153,7 @@

Multi purpose demo page

// send back message with get Token value LuigiClient.sendCustomMessage({ id: 'token.updated', - value: LuigiClient.getToken() + value: LuigiClient.getToken(), }); } @@ -174,14 +172,14 @@

Multi purpose demo page

relativePath: { text: 'relative hide side nav', url: 'hideSideNav' }, neverShowItAgain: { text: "Don't show this again", - dismissKey: 'neverShowItAgain' - } + dismissKey: 'neverShowItAgain', + }, }, - closeAfter: 300000 + closeAfter: 300000, }; LuigiClient.uxManager() .showAlert(settings) - .then(param => { + .then((param) => { console.log('Callback called on microfrontend', param); }); } @@ -213,14 +211,14 @@

Multi purpose demo page

} LuigiClient.addInitListener(updateFn); LuigiClient.addContextUpdateListener(updateFn); - LuigiClient.addCustomMessageListener('update', msg => { + LuigiClient.addCustomMessageListener('update', (msg) => { console.log('Custom Message Received inside iframe Container', msg); document.getElementById('content').innerHTML = 'Received Custom Message: ' + msg.dataToSend; }); // fallback visibility if no initlistener called for 3 seconds - setTimeout(function() { + setTimeout(function () { document.getElementById('textCnt').classList.add('visible'); }, 3000); diff --git a/container/test-app/index.html b/container/test-app/index.html index 6982499a7d..afd1539ad5 100644 --- a/container/test-app/index.html +++ b/container/test-app/index.html @@ -1,4 +1,4 @@ - + diff --git a/container/test-app/wc/clientAPI.html b/container/test-app/wc/clientAPI.html index 78552f1113..35c1f11ffe 100644 --- a/container/test-app/wc/clientAPI.html +++ b/container/test-app/wc/clientAPI.html @@ -14,7 +14,7 @@

sendCustomMessage -
+
import MFEventID from '../bundle.js'; const deferInitContainer = document.getElementById('defer-init-test'); const deferInitButton = document.getElementById('defer-init-button'); - deferInitButton.addEventListener('click', function() { + deferInitButton.addEventListener('click', function () { deferInitContainer.init(); }); // document.querySelector('luigi-container'); const container = document.querySelector( - '[data-test-id="luigi-client-api-test-01"]' + '[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' + dataToSend: 'cool custom Message', }); }); - [...document.querySelectorAll('luigi-container')].forEach(luigiContainer => { - luigiContainer.addEventListener(MFEventID.NAVIGATION_REQUEST, event => { + [...document.querySelectorAll('luigi-container')].forEach((luigiContainer) => { + luigiContainer.addEventListener(MFEventID.NAVIGATION_REQUEST, (event) => { console.log(event.detail); window.location.hash = event.detail.link; }); - luigiContainer.addEventListener(MFEventID.ALERT_REQUEST, event => { + luigiContainer.addEventListener(MFEventID.ALERT_REQUEST, (event) => { console.log(event.detail); alert(event.detail.text); }); luigiContainer.addEventListener( MFEventID.SHOW_CONFIRMATION_MODAL_REQUEST, - event => { + (event) => { const data = event.detail; console.log(data); const val = confirm(data.body); @@ -102,9 +102,9 @@

if (event.callback) { event.callback(val); } - } + }, ); - luigiContainer.addEventListener(MFEventID.CUSTOM_MESSAGE, event => { + luigiContainer.addEventListener(MFEventID.CUSTOM_MESSAGE, (event) => { if (event.detail.id !== 'timer') { alert(event.detail.data); } @@ -112,48 +112,51 @@

luigiContainer.addEventListener( MFEventID.RUNTIME_ERROR_HANDLING_REQUEST, - event => { + (event) => { console.log(event.detail); - } + }, ); - luigiContainer.addEventListener(MFEventID.ADD_NODE_PARAMS_REQUEST, event => { + luigiContainer.addEventListener(MFEventID.ADD_NODE_PARAMS_REQUEST, (event) => { const params = event.detail.params; console.log('params', params); }); - luigiContainer.addEventListener(MFEventID.SET_ANCHOR_LINK_REQUEST, event => { + luigiContainer.addEventListener(MFEventID.SET_ANCHOR_LINK_REQUEST, (event) => { console.log('anchor', event.detail); }); - luigiContainer.addEventListener(MFEventID.OPEN_USER_SETTINGS_REQUEST, event => { + luigiContainer.addEventListener(MFEventID.OPEN_USER_SETTINGS_REQUEST, (event) => { console.log('Open User Settings Request received', event.detail); alert('LuigiClient.uxManager().openUserSettings()'); }); - luigiContainer.addEventListener(MFEventID.CLOSE_USER_SETTINGS_REQUEST, event => { - console.log('Close User Settings Request received', event.detail); - alert('LuigiClient.uxManager().closeUserSettings()'); - }); - luigiContainer.addEventListener(MFEventID.REMOVE_BACKDROP_REQUEST, event => { + luigiContainer.addEventListener( + MFEventID.CLOSE_USER_SETTINGS_REQUEST, + (event) => { + console.log('Close User Settings Request received', event.detail); + alert('LuigiClient.uxManager().closeUserSettings()'); + }, + ); + luigiContainer.addEventListener(MFEventID.REMOVE_BACKDROP_REQUEST, (event) => { console.log('Remove Backdrop Request received', event.detail); alert('LuigiClient.uxManager().removeBackdrop()'); }); - luigiContainer.addEventListener(MFEventID.COLLAPSE_LEFT_NAV_REQUEST, event => { + luigiContainer.addEventListener(MFEventID.COLLAPSE_LEFT_NAV_REQUEST, (event) => { console.log('Collapse Left Side Nav Request received', event.detail); alert('LuigiClient.uxManager().collapseLeftSideNav()'); }); - luigiContainer.addEventListener(MFEventID.SET_DOCUMENT_TITLE_REQUEST, event => { + luigiContainer.addEventListener(MFEventID.SET_DOCUMENT_TITLE_REQUEST, (event) => { console.log('Set Document Title Request received', event.detail); luigiContainer.documentTitle = event.detail; }); luigiContainer.addEventListener( MFEventID.HIDE_LOADING_INDICATOR_REQUEST, - event => { + (event) => { console.log('Hide Loading Indicator Request received', event.detail); alert('LuigiClient.uxManager().hideAppLoadingIndicator()'); - } + }, ); // linkManager listeners: // path exists - luigiContainer.addEventListener(MFEventID.PATH_EXISTS_REQUEST, event => { + luigiContainer.addEventListener(MFEventID.PATH_EXISTS_REQUEST, (event) => { console.log('Path Exists Request received', event.detail, event); // send back result with defined 'callback' // event: MFEventID.PathExistsEvent can be used as an event type to get the callback function @@ -161,11 +164,14 @@

}); // setViewGroup Data listener - luigiContainer.addEventListener(MFEventID.SET_VIEW_GROUP_DATA_REQUEST, event => { - console.log('Set View Group Data Request received', event.detail, event); - }); + luigiContainer.addEventListener( + MFEventID.SET_VIEW_GROUP_DATA_REQUEST, + (event) => { + console.log('Set View Group Data Request received', event.detail, event); + }, + ); - luigiContainer.addEventListener(MFEventID.GET_CURRENT_ROUTE_REQUEST, event => { + luigiContainer.addEventListener(MFEventID.GET_CURRENT_ROUTE_REQUEST, (event) => { event.callback(window.location.pathname); }); }); diff --git a/container/test-app/wc/helloWorldWC.js b/container/test-app/wc/helloWorldWC.js index 2ecaa8d9fb..5cff4238bd 100644 --- a/container/test-app/wc/helloWorldWC.js +++ b/container/test-app/wc/helloWorldWC.js @@ -251,21 +251,11 @@ export default class extends HTMLElement { const path = 'hello-world-wc'; const ctx = { ctx: 123 }; - this.LuigiClient.linkManager() - .fromContext(ctx) - .navigate(); - this.LuigiClient.linkManager() - .fromClosestContext() - .navigate(path); - this.LuigiClient.linkManager() - .fromVirtualTreeRoot() - .navigate(path); - this.LuigiClient.linkManager() - .fromParent(ctx) - .navigate(path); - this.LuigiClient.linkManager() - .withParams('my-params') - .navigate(path); + this.LuigiClient.linkManager().fromContext(ctx).navigate(); + this.LuigiClient.linkManager().fromClosestContext().navigate(path); + this.LuigiClient.linkManager().fromVirtualTreeRoot().navigate(path); + this.LuigiClient.linkManager().fromParent(ctx).navigate(path); + this.LuigiClient.linkManager().withParams('my-params').navigate(path); this.LuigiClient.linkManager().navigate(path); this.LuigiClient.uxManager().showAlert({ text: 'LuigiClient.linkManager().navigate()', @@ -291,7 +281,7 @@ export default class extends HTMLElement { this.LuigiClient.linkManager().updateTopNavigation(); this.LuigiClient.linkManager() .pathExists() - .then(result => { + .then((result) => { console.log('PATH EXISTS'); this.LuigiClient.uxManager().showAlert({ text: @@ -314,7 +304,7 @@ export default class extends HTMLElement { this.$getCurrentRoute.addEventListener('click', () => { this.LuigiClient.linkManager() .getCurrentRoute() - .then(result => { + .then((result) => { console.log(result); alert('current route: ' + result); }); @@ -328,7 +318,7 @@ export default class extends HTMLElement { } }); - this.addEventListener('custom-message-id', event => { + 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}`; diff --git a/container/test/services/container.service.spec.ts b/container/test/services/container.service.spec.ts index d18108c95c..3a922af00f 100644 --- a/container/test/services/container.service.spec.ts +++ b/container/test/services/container.service.spec.ts @@ -1,675 +1,675 @@ -import { LuigiInternalMessageID } from '../../src/constants/internal-communication'; -import { Events } from '../../src/constants/communication'; -import { ContainerService } from '../../src/services/container.service'; - -describe('getContainerManager messageListener', () => { - let service: ContainerService; - let gtcSpy; - let cw: any = {}; - let cm; - let dispatchedEvent; - service = new ContainerService(); - cm = service.getContainerManager(); - - beforeEach(() => { - // only get context scenario relies on postMessage, so we need special case handling for it - const testName = expect.getState().currentTestName; - if (testName === 'test get context message') { - cw = { postMessage: () => {} }; - } - - gtcSpy = jest.spyOn(service, 'getTargetContainer').mockImplementation(() => { - return { - iframeHandle: { - iframe: { - contentWindow: cw - } - }, - dispatchEvent: customEvent => { - dispatchedEvent = customEvent; - } - }; - }); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('test alert request', () => { - const event = { - source: cw, - data: { - msg: LuigiInternalMessageID.ALERT_REQUEST, - data: { - id: 'navRequest' - } - } - }; - cm.messageListener(event); - expect(dispatchedEvent.type).toEqual(Events.ALERT_REQUEST); - expect(dispatchedEvent.detail).toEqual({ - data: { data: { id: 'navRequest' }, msg: 'luigi.ux.alert.show' }, - source: {} - }); - }); - - it('test custom message', () => { - const event = { - source: cw, - data: { - msg: LuigiInternalMessageID.CUSTOM_MESSAGE, - data: { - id: 'custMsgId', - foo: 'bar' - } - } - }; - cm.messageListener(event); - expect(dispatchedEvent.type).toEqual(Events.CUSTOM_MESSAGE); - expect(dispatchedEvent.detail).toEqual({ id: 'custMsgId', _metaData: {}, data: { foo: 'bar' } }); - gtcSpy.mockRestore(); - }); - - it('test get context message', () => { - const event = { - origin: '*', - source: cw, - data: { - msg: LuigiInternalMessageID.GET_CONTEXT, - data: { - id: 'custMsgId', - foo: 'bar' - } - } - }; - - // Create a mock for the postMessage method - const postMessageMock = jest.fn(); - - // Replace the real postMessage with the mock - cw.postMessage = postMessageMock; - - // Define the message to send and target Origin - const message = { - authData: {}, - context: {}, - internal: { - thirdPartyCookieCheck: { - disabled: false - } - }, - msg: 'luigi.init' - }; - const targetOrigin = '*'; - - // Call the method that should trigger postMessage - cm.messageListener(event); - - // Assert that postMessage was called with the expected parameters - expect(postMessageMock).toHaveBeenCalledWith(message, targetOrigin); - - // Clean up by restoring the original postMessage function - cw.postMessage = () => {}; - cw.origin = undefined; - }); - - it('test initialized request', () => { - const event = { - source: cw, - data: { - msg: LuigiInternalMessageID.INITIALIZED, - params: 'init' - } - }; - cm.messageListener(event); - expect(dispatchedEvent.type).toEqual(Events.INITIALIZED); - expect(dispatchedEvent.detail).toEqual('init'); - }); - - it('test add search params request', () => { - const event = { - source: cw, - data: { - msg: LuigiInternalMessageID.ADD_SEARCH_PARAMS_REQUEST, - keepBrowserHistory: true, - data: 'some-data' - } - }; - cm.messageListener(event); - expect(dispatchedEvent.type).toEqual(Events.ADD_SEARCH_PARAMS_REQUEST); - expect(dispatchedEvent.detail).toEqual({ data: 'some-data', keepBrowserHistory: true }); - }); - - it('test add node params request', () => { - const event = { - source: cw, - data: { - msg: LuigiInternalMessageID.ADD_NODE_PARAMS_REQUEST, - keepBrowserHistory: false, - data: 'some-data' - } - }; - cm.messageListener(event); - expect(dispatchedEvent.type).toEqual(Events.ADD_NODE_PARAMS_REQUEST); - expect(dispatchedEvent.detail).toEqual({ data: 'some-data', keepBrowserHistory: false }); - }); - - it('test confirmationModal show request', () => { - const event = { - source: cw, - data: { - msg: LuigiInternalMessageID.SHOW_CONFIRMATION_MODAL_REQUEST, - params: 'modal-show' - } - }; - cm.messageListener(event); - expect(dispatchedEvent.type).toEqual(Events.SHOW_CONFIRMATION_MODAL_REQUEST); - }); - - it('test loading indicator show request', () => { - const event = { - source: cw, - data: { - msg: LuigiInternalMessageID.SHOW_LOADING_INDICATOR_REQUEST, - params: 'loading-indicator-show' - } - }; - cm.messageListener(event); - expect(dispatchedEvent.type).toEqual(Events.SHOW_LOADING_INDICATOR_REQUEST); - }); - - it('test loading indicator hide request', () => { - const event = { - source: cw, - data: { - msg: LuigiInternalMessageID.HIDE_LOADING_INDICATOR_REQUEST, - params: 'loading-indicator-hide' - } - }; - cm.messageListener(event); - expect(dispatchedEvent.type).toEqual(Events.HIDE_LOADING_INDICATOR_REQUEST); - }); - - it('test set locale request', () => { - const event = { - source: cw, - data: { - msg: LuigiInternalMessageID.SET_CURRENT_LOCALE_REQUEST, - params: 'set-locale' - } - }; - cm.messageListener(event); - expect(dispatchedEvent.type).toEqual(Events.SET_CURRENT_LOCALE_REQUEST); - }); - - it('test set local storage request', () => { - const event = { - source: cw, - data: { - msg: LuigiInternalMessageID.LOCAL_STORAGE_SET_REQUEST, - params: 'set-local-storage' - } - }; - cm.messageListener(event); - expect(dispatchedEvent.type).toEqual(Events.LOCAL_STORAGE_SET_REQUEST); - }); - - it('test runtime error handling request', () => { - const event = { - source: cw, - data: { - msg: LuigiInternalMessageID.RUNTIME_ERROR_HANDLING_REQUEST, - params: 'set-runtime-error-request' - } - }; - cm.messageListener(event); - expect(dispatchedEvent.type).toEqual(Events.RUNTIME_ERROR_HANDLING_REQUEST); - }); - - it('test anchor link request', () => { - const event = { - source: cw, - data: { - msg: LuigiInternalMessageID.SET_ANCHOR_LINK_REQUEST, - params: 'set-anchor-link-request' - } - }; - cm.messageListener(event); - expect(dispatchedEvent.type).toEqual(Events.SET_ANCHOR_LINK_REQUEST); - }); - - it('test third party cookies request', () => { - const event = { - source: cw, - data: { - msg: LuigiInternalMessageID.SET_THIRD_PARTY_COOKIES_REQUEST, - params: 'set-thirdparty-cookies-request' - } - }; - cm.messageListener(event); - expect(dispatchedEvent.type).toEqual(Events.SET_THIRD_PARTY_COOKIES_REQUEST); - }); - - it('test navigation back request', () => { - const event = { - source: cw, - data: { - msg: LuigiInternalMessageID.BACK_NAVIGATION_REQUEST, - params: 'back-navigation-request' - } - }; - cm.messageListener(event); - expect(dispatchedEvent.type).toEqual(Events.BACK_NAVIGATION_REQUEST); - }); - - it('test navigation request', () => { - const event = { - source: cw, - data: { - msg: LuigiInternalMessageID.NAVIGATION_REQUEST, - params: 'navigation-request' - } - }; - cm.messageListener(event); - expect(dispatchedEvent.type).toEqual(Events.NAVIGATION_REQUEST); - }); - - it('test getCurrentRoute request', () => { - const event = { - source: cw, - data: { - msg: LuigiInternalMessageID.GET_CURRENT_ROUTE_REQUEST, - params: 'get-currentroute-request' - } - }; - cm.messageListener(event); - expect(dispatchedEvent.type).toEqual(Events.GET_CURRENT_ROUTE_REQUEST); - }); - - it('test getCurrentRoute request', () => { - const event = { - source: cw, - data: { - msg: LuigiInternalMessageID.GET_CURRENT_ROUTE_REQUEST, - params: 'get-currentroute-request' - } - }; - cm.messageListener(event); - expect(dispatchedEvent.type).toEqual(Events.GET_CURRENT_ROUTE_REQUEST); - }); - - it('test navigation completed request', () => { - const event = { - source: cw, - data: { - msg: LuigiInternalMessageID.NAVIGATION_COMPLETED_REPORT, - params: 'nav-completed-request' - } - }; - cm.messageListener(event); - expect(dispatchedEvent.type).toEqual(Events.NAVIGATION_COMPLETED_REPORT); - }); - - it('test update modalPath data request', () => { - const event = { - source: cw, - data: { - msg: LuigiInternalMessageID.UPDATE_MODAL_PATH_DATA_REQUEST, - params: 'update-modalpathdata-request' - } - }; - cm.messageListener(event); - expect(dispatchedEvent.type).toEqual(Events.UPDATE_MODAL_PATH_DATA_REQUEST); - }); - - it('test check pathExists request', () => { - const event = { - source: cw, - data: { - msg: LuigiInternalMessageID.CHECK_PATH_EXISTS_REQUEST, - params: 'update-check-pathexists-request' - } - }; - cm.messageListener(event); - expect(dispatchedEvent.type).toEqual(Events.CHECK_PATH_EXISTS_REQUEST); - }); - - it('test set dirty status request', () => { - const event = { - source: cw, - data: { - msg: LuigiInternalMessageID.SET_DIRTY_STATUS_REQUEST, - params: 'set-dirtystatus-request' - } - }; - cm.messageListener(event); - expect(dispatchedEvent.type).toEqual(Events.SET_DIRTY_STATUS_REQUEST); - }); - - it('test default', () => { - const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(jest.fn()); - const event = { - source: cw, - data: { - msg: 'no-func' - } - }; - cm.messageListener(event); - expect(consoleWarnSpy).not.toHaveBeenCalled(); - }); -}); - -describe('isVisible', () => { - let service: ContainerService; - service = new ContainerService(); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should return true for a visible element', () => { - // Arrange - const visibleElement = document.createElement('div'); - jest.spyOn(visibleElement, 'offsetWidth', 'get').mockImplementation(() => 200); - document.body.appendChild(visibleElement); - - // Act - const result = service.isVisible(visibleElement); - - // Assert - expect(result).toBe(true); - }); - - it('should return false for a hidden element', () => { - // Arrange - const hiddenElement = document.createElement('div'); - hiddenElement.style.display = 'none'; - document.body.appendChild(hiddenElement); - - // Act - const result = service.isVisible(hiddenElement); - - // Assert - expect(result).toBe(false); - }); - - it('should return false for an element with zero dimensions', () => { - // Arrange - const zeroSizeElement = document.createElement('div'); - zeroSizeElement.style.width = '0'; - zeroSizeElement.style.height = '0'; - document.body.appendChild(zeroSizeElement); - - // Act - const result = service.isVisible(zeroSizeElement); - - // Assert - expect(result).toBe(false); - }); -}); - -describe('sendCustomMessageToIframe', () => { - let service: ContainerService; - service = new ContainerService(); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should send a custom message to the iframe', () => { - // Arrange - const iframeHandle = { - iframe: { - contentWindow: { - postMessage: jest.fn() - }, - src: 'https://example.com' - } - }; - const message = { key: 'value' }; - - // Act - service.sendCustomMessageToIframe(iframeHandle, message); - - // Assert - expect(iframeHandle.iframe.contentWindow.postMessage).toHaveBeenCalledWith( - { msg: 'custom', data: message }, - 'https://example.com' - ); - }); - - it('should send a named message to the iframe', () => { - // Arrange - const iframeHandle = { - iframe: { - contentWindow: { - postMessage: jest.fn() - }, - src: 'https://example.com' - } - }; - const message = { key: 'value' }; - - // Act - service.sendCustomMessageToIframe(iframeHandle, message, 'namedMessage'); - - // Assert - expect(iframeHandle.iframe.contentWindow.postMessage).toHaveBeenCalledWith( - { msg: 'namedMessage', key: 'value' }, - 'https://example.com' - ); - }); - - it('should log an error if contentWindow is not available', () => { - // Arrange - const iframeHandle = { - iframe: {} - }; - const message = { key: 'value' }; - - // Spy on console.error to capture the log message - const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); - - // Act - service.sendCustomMessageToIframe(iframeHandle, message); - - // Assert - expect(consoleErrorSpy).toHaveBeenCalledWith('Message target could not be resolved'); - - // Restore the original console.error function - consoleErrorSpy.mockRestore(); - }); -}); - -describe('dispatch', () => { - let service: ContainerService; - service = new ContainerService(); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should dispatch a custom event to the target container, no Callback', () => { - // Arrange - const targetContainer = document.createElement('div'); - const eventName = 'customEvent'; - const eventData = { key: 'value' }; - targetContainer.dispatchEvent = jest.fn(); - - // Act - service.dispatch(eventName, targetContainer, eventData); - - // Assert - const dispatchedEvent = new CustomEvent(eventName, { detail: eventData }); - expect(targetContainer.dispatchEvent).toHaveBeenCalledWith(dispatchedEvent); - }); - - it('should execute the callback when provided', () => { - // Arrange - const targetContainer = document.createElement('div'); - const eventName = 'customEvent'; - const eventData = { key: 'value' }; - targetContainer.dispatchEvent = jest.fn(); - - // Define a callback function - const callbackFunction = data => { - // This function should not be called in this test - }; - - // Act - service.dispatch(eventName, targetContainer, eventData, callbackFunction, 'onCallback'); - - // Assert - globalThis.CustomEvent = jest - .fn() - .mockImplementation((type, eventInit) => ({ isTrusted: false, onCallback: callbackFunction })); - - const dispatchedEventMock = { isTrusted: false, onCallback: expect.any(Function) }; - expect(targetContainer.dispatchEvent).toHaveBeenCalledWith(expect.objectContaining(dispatchedEventMock)); - }); -}); - -describe('getTargetContainer', () => { - let service: ContainerService; - service = new ContainerService(); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should return the correct container when a matching container is found', () => { - // Arrange - const mockContainer1 = { - iframeHandle: { - iframe: { - contentWindow: 'source1' - } - } - }; - const mockContainer2 = { - iframeHandle: { - iframe: { - contentWindow: 'source2' - } - } - }; - - globalThis.__luigi_container_manager = { - container: [mockContainer1, mockContainer2] - }; - - const mockEvent = { - source: 'source2' // Matched with mockContainer2 - }; - - // Act - const targetContainer = service.getTargetContainer(mockEvent); - - // Assert - expect(targetContainer).toBe(mockContainer2); - }); - - it('should return undefined when no matching container is found', () => { - // Arrange - const mockContainer1 = { - iframeHandle: { - iframe: { - contentWindow: 'source1' - } - } - }; - const mockContainer2 = { - iframeHandle: { - iframe: { - contentWindow: 'source2' - } - } - }; - - globalThis.__luigi_container_manager = { - container: [mockContainer1, mockContainer2] - }; - - const mockEvent = { - source: 'source3' // No matching container - }; - - // Act - const targetContainer = service.getTargetContainer(mockEvent); - - // Assert - expect(targetContainer).toBe(undefined); - }); -}); - -describe('getContainerManager branch', () => { - let service: ContainerService; - service = new ContainerService(); - - beforeEach(() => { - globalThis.__luigi_container_manager = undefined; - jest.resetAllMocks(); - }); - - afterEach(() => { - // Reset the global state after each test - globalThis.__luigi_container_manager = undefined; - window.removeEventListener('message', globalThis.__luigi_container_manager?.messageListener); - }); - - it('should initialize and return the container manager', () => { - const containerManager = service.getContainerManager(); - expect(containerManager).toBeDefined(); - expect(containerManager.container).toEqual([]); - expect(containerManager.messageListener).toBeDefined(); - }); - - it('should return the existing container manager if it has been initialized', () => { - const existingManager = { - container: ['existingData'], - messageListener: jest.fn() - }; - globalThis.__luigi_container_manager = existingManager; - - const containerManager = service.getContainerManager(); - expect(containerManager).toBe(existingManager); - }); - - it('should add a message event listener when initializing', () => { - globalThis.__luigi_container_manager = undefined; - const spy = jest.spyOn(window, 'addEventListener'); - - const containerManager = service.getContainerManager(); // Initialize the manager - - // Verify that addEventListener was called with 'message' event type - expect(containerManager).toBeDefined(); - expect(containerManager.container).toEqual([]); - expect(spy).toHaveBeenCalledWith('message', expect.any(Function)); - }); -}); - -describe('registerContainer', () => { - let service: ContainerService; - service = new ContainerService(); - - beforeEach(() => { - jest.resetAllMocks(); - }); - - it('should add an HTMLElement to the container', () => { - // Arrange - const containerManager = { - container: [] - }; - const container = document.createElement('div'); - service.getContainerManager = jest.fn().mockReturnValue(containerManager); - - // Act - service.registerContainer(container); - - // Assert - expect(containerManager.container).toContain(container); - expect(service.getContainerManager).toHaveBeenCalled(); - }); -}); +import { LuigiInternalMessageID } from '../../src/constants/internal-communication'; +import { Events } from '../../src/constants/communication'; +import { ContainerService } from '../../src/services/container.service'; + +describe('getContainerManager messageListener', () => { + let service: ContainerService; + let gtcSpy; + let cw: any = {}; + let cm; + let dispatchedEvent; + service = new ContainerService(); + cm = service.getContainerManager(); + + beforeEach(() => { + // only get context scenario relies on postMessage, so we need special case handling for it + const testName = expect.getState().currentTestName; + if (testName === 'test get context message') { + cw = { postMessage: () => {} }; + } + + gtcSpy = jest.spyOn(service, 'getTargetContainer').mockImplementation(() => { + return { + iframeHandle: { + iframe: { + contentWindow: cw + } + }, + dispatchEvent: (customEvent) => { + dispatchedEvent = customEvent; + } + }; + }); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('test alert request', () => { + const event = { + source: cw, + data: { + msg: LuigiInternalMessageID.ALERT_REQUEST, + data: { + id: 'navRequest' + } + } + }; + cm.messageListener(event); + expect(dispatchedEvent.type).toEqual(Events.ALERT_REQUEST); + expect(dispatchedEvent.detail).toEqual({ + data: { data: { id: 'navRequest' }, msg: 'luigi.ux.alert.show' }, + source: {} + }); + }); + + it('test custom message', () => { + const event = { + source: cw, + data: { + msg: LuigiInternalMessageID.CUSTOM_MESSAGE, + data: { + id: 'custMsgId', + foo: 'bar' + } + } + }; + cm.messageListener(event); + expect(dispatchedEvent.type).toEqual(Events.CUSTOM_MESSAGE); + expect(dispatchedEvent.detail).toEqual({ id: 'custMsgId', _metaData: {}, data: { foo: 'bar' } }); + gtcSpy.mockRestore(); + }); + + it('test get context message', () => { + const event = { + origin: '*', + source: cw, + data: { + msg: LuigiInternalMessageID.GET_CONTEXT, + data: { + id: 'custMsgId', + foo: 'bar' + } + } + }; + + // Create a mock for the postMessage method + const postMessageMock = jest.fn(); + + // Replace the real postMessage with the mock + cw.postMessage = postMessageMock; + + // Define the message to send and target Origin + const message = { + authData: {}, + context: {}, + internal: { + thirdPartyCookieCheck: { + disabled: false + } + }, + msg: 'luigi.init' + }; + const targetOrigin = '*'; + + // Call the method that should trigger postMessage + cm.messageListener(event); + + // Assert that postMessage was called with the expected parameters + expect(postMessageMock).toHaveBeenCalledWith(message, targetOrigin); + + // Clean up by restoring the original postMessage function + cw.postMessage = () => {}; + cw.origin = undefined; + }); + + it('test initialized request', () => { + const event = { + source: cw, + data: { + msg: LuigiInternalMessageID.INITIALIZED, + params: 'init' + } + }; + cm.messageListener(event); + expect(dispatchedEvent.type).toEqual(Events.INITIALIZED); + expect(dispatchedEvent.detail).toEqual('init'); + }); + + it('test add search params request', () => { + const event = { + source: cw, + data: { + msg: LuigiInternalMessageID.ADD_SEARCH_PARAMS_REQUEST, + keepBrowserHistory: true, + data: 'some-data' + } + }; + cm.messageListener(event); + expect(dispatchedEvent.type).toEqual(Events.ADD_SEARCH_PARAMS_REQUEST); + expect(dispatchedEvent.detail).toEqual({ data: 'some-data', keepBrowserHistory: true }); + }); + + it('test add node params request', () => { + const event = { + source: cw, + data: { + msg: LuigiInternalMessageID.ADD_NODE_PARAMS_REQUEST, + keepBrowserHistory: false, + data: 'some-data' + } + }; + cm.messageListener(event); + expect(dispatchedEvent.type).toEqual(Events.ADD_NODE_PARAMS_REQUEST); + expect(dispatchedEvent.detail).toEqual({ data: 'some-data', keepBrowserHistory: false }); + }); + + it('test confirmationModal show request', () => { + const event = { + source: cw, + data: { + msg: LuigiInternalMessageID.SHOW_CONFIRMATION_MODAL_REQUEST, + params: 'modal-show' + } + }; + cm.messageListener(event); + expect(dispatchedEvent.type).toEqual(Events.SHOW_CONFIRMATION_MODAL_REQUEST); + }); + + it('test loading indicator show request', () => { + const event = { + source: cw, + data: { + msg: LuigiInternalMessageID.SHOW_LOADING_INDICATOR_REQUEST, + params: 'loading-indicator-show' + } + }; + cm.messageListener(event); + expect(dispatchedEvent.type).toEqual(Events.SHOW_LOADING_INDICATOR_REQUEST); + }); + + it('test loading indicator hide request', () => { + const event = { + source: cw, + data: { + msg: LuigiInternalMessageID.HIDE_LOADING_INDICATOR_REQUEST, + params: 'loading-indicator-hide' + } + }; + cm.messageListener(event); + expect(dispatchedEvent.type).toEqual(Events.HIDE_LOADING_INDICATOR_REQUEST); + }); + + it('test set locale request', () => { + const event = { + source: cw, + data: { + msg: LuigiInternalMessageID.SET_CURRENT_LOCALE_REQUEST, + params: 'set-locale' + } + }; + cm.messageListener(event); + expect(dispatchedEvent.type).toEqual(Events.SET_CURRENT_LOCALE_REQUEST); + }); + + it('test set local storage request', () => { + const event = { + source: cw, + data: { + msg: LuigiInternalMessageID.LOCAL_STORAGE_SET_REQUEST, + params: 'set-local-storage' + } + }; + cm.messageListener(event); + expect(dispatchedEvent.type).toEqual(Events.LOCAL_STORAGE_SET_REQUEST); + }); + + it('test runtime error handling request', () => { + const event = { + source: cw, + data: { + msg: LuigiInternalMessageID.RUNTIME_ERROR_HANDLING_REQUEST, + params: 'set-runtime-error-request' + } + }; + cm.messageListener(event); + expect(dispatchedEvent.type).toEqual(Events.RUNTIME_ERROR_HANDLING_REQUEST); + }); + + it('test anchor link request', () => { + const event = { + source: cw, + data: { + msg: LuigiInternalMessageID.SET_ANCHOR_LINK_REQUEST, + params: 'set-anchor-link-request' + } + }; + cm.messageListener(event); + expect(dispatchedEvent.type).toEqual(Events.SET_ANCHOR_LINK_REQUEST); + }); + + it('test third party cookies request', () => { + const event = { + source: cw, + data: { + msg: LuigiInternalMessageID.SET_THIRD_PARTY_COOKIES_REQUEST, + params: 'set-thirdparty-cookies-request' + } + }; + cm.messageListener(event); + expect(dispatchedEvent.type).toEqual(Events.SET_THIRD_PARTY_COOKIES_REQUEST); + }); + + it('test navigation back request', () => { + const event = { + source: cw, + data: { + msg: LuigiInternalMessageID.BACK_NAVIGATION_REQUEST, + params: 'back-navigation-request' + } + }; + cm.messageListener(event); + expect(dispatchedEvent.type).toEqual(Events.BACK_NAVIGATION_REQUEST); + }); + + it('test navigation request', () => { + const event = { + source: cw, + data: { + msg: LuigiInternalMessageID.NAVIGATION_REQUEST, + params: 'navigation-request' + } + }; + cm.messageListener(event); + expect(dispatchedEvent.type).toEqual(Events.NAVIGATION_REQUEST); + }); + + it('test getCurrentRoute request', () => { + const event = { + source: cw, + data: { + msg: LuigiInternalMessageID.GET_CURRENT_ROUTE_REQUEST, + params: 'get-currentroute-request' + } + }; + cm.messageListener(event); + expect(dispatchedEvent.type).toEqual(Events.GET_CURRENT_ROUTE_REQUEST); + }); + + it('test getCurrentRoute request', () => { + const event = { + source: cw, + data: { + msg: LuigiInternalMessageID.GET_CURRENT_ROUTE_REQUEST, + params: 'get-currentroute-request' + } + }; + cm.messageListener(event); + expect(dispatchedEvent.type).toEqual(Events.GET_CURRENT_ROUTE_REQUEST); + }); + + it('test navigation completed request', () => { + const event = { + source: cw, + data: { + msg: LuigiInternalMessageID.NAVIGATION_COMPLETED_REPORT, + params: 'nav-completed-request' + } + }; + cm.messageListener(event); + expect(dispatchedEvent.type).toEqual(Events.NAVIGATION_COMPLETED_REPORT); + }); + + it('test update modalPath data request', () => { + const event = { + source: cw, + data: { + msg: LuigiInternalMessageID.UPDATE_MODAL_PATH_DATA_REQUEST, + params: 'update-modalpathdata-request' + } + }; + cm.messageListener(event); + expect(dispatchedEvent.type).toEqual(Events.UPDATE_MODAL_PATH_DATA_REQUEST); + }); + + it('test check pathExists request', () => { + const event = { + source: cw, + data: { + msg: LuigiInternalMessageID.CHECK_PATH_EXISTS_REQUEST, + params: 'update-check-pathexists-request' + } + }; + cm.messageListener(event); + expect(dispatchedEvent.type).toEqual(Events.CHECK_PATH_EXISTS_REQUEST); + }); + + it('test set dirty status request', () => { + const event = { + source: cw, + data: { + msg: LuigiInternalMessageID.SET_DIRTY_STATUS_REQUEST, + params: 'set-dirtystatus-request' + } + }; + cm.messageListener(event); + expect(dispatchedEvent.type).toEqual(Events.SET_DIRTY_STATUS_REQUEST); + }); + + it('test default', () => { + const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(jest.fn()); + const event = { + source: cw, + data: { + msg: 'no-func' + } + }; + cm.messageListener(event); + expect(consoleWarnSpy).not.toHaveBeenCalled(); + }); +}); + +describe('isVisible', () => { + let service: ContainerService; + service = new ContainerService(); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should return true for a visible element', () => { + // Arrange + const visibleElement = document.createElement('div'); + jest.spyOn(visibleElement, 'offsetWidth', 'get').mockImplementation(() => 200); + document.body.appendChild(visibleElement); + + // Act + const result = service.isVisible(visibleElement); + + // Assert + expect(result).toBe(true); + }); + + it('should return false for a hidden element', () => { + // Arrange + const hiddenElement = document.createElement('div'); + hiddenElement.style.display = 'none'; + document.body.appendChild(hiddenElement); + + // Act + const result = service.isVisible(hiddenElement); + + // Assert + expect(result).toBe(false); + }); + + it('should return false for an element with zero dimensions', () => { + // Arrange + const zeroSizeElement = document.createElement('div'); + zeroSizeElement.style.width = '0'; + zeroSizeElement.style.height = '0'; + document.body.appendChild(zeroSizeElement); + + // Act + const result = service.isVisible(zeroSizeElement); + + // Assert + expect(result).toBe(false); + }); +}); + +describe('sendCustomMessageToIframe', () => { + let service: ContainerService; + service = new ContainerService(); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should send a custom message to the iframe', () => { + // Arrange + const iframeHandle = { + iframe: { + contentWindow: { + postMessage: jest.fn() + }, + src: 'https://example.com' + } + }; + const message = { key: 'value' }; + + // Act + service.sendCustomMessageToIframe(iframeHandle, message); + + // Assert + expect(iframeHandle.iframe.contentWindow.postMessage).toHaveBeenCalledWith( + { msg: 'custom', data: message }, + 'https://example.com' + ); + }); + + it('should send a named message to the iframe', () => { + // Arrange + const iframeHandle = { + iframe: { + contentWindow: { + postMessage: jest.fn() + }, + src: 'https://example.com' + } + }; + const message = { key: 'value' }; + + // Act + service.sendCustomMessageToIframe(iframeHandle, message, 'namedMessage'); + + // Assert + expect(iframeHandle.iframe.contentWindow.postMessage).toHaveBeenCalledWith( + { msg: 'namedMessage', key: 'value' }, + 'https://example.com' + ); + }); + + it('should log an error if contentWindow is not available', () => { + // Arrange + const iframeHandle = { + iframe: {} + }; + const message = { key: 'value' }; + + // Spy on console.error to capture the log message + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + // Act + service.sendCustomMessageToIframe(iframeHandle, message); + + // Assert + expect(consoleErrorSpy).toHaveBeenCalledWith('Message target could not be resolved'); + + // Restore the original console.error function + consoleErrorSpy.mockRestore(); + }); +}); + +describe('dispatch', () => { + let service: ContainerService; + service = new ContainerService(); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should dispatch a custom event to the target container, no Callback', () => { + // Arrange + const targetContainer = document.createElement('div'); + const eventName = 'customEvent'; + const eventData = { key: 'value' }; + targetContainer.dispatchEvent = jest.fn(); + + // Act + service.dispatch(eventName, targetContainer, eventData); + + // Assert + const dispatchedEvent = new CustomEvent(eventName, { detail: eventData }); + expect(targetContainer.dispatchEvent).toHaveBeenCalledWith(dispatchedEvent); + }); + + it('should execute the callback when provided', () => { + // Arrange + const targetContainer = document.createElement('div'); + const eventName = 'customEvent'; + const eventData = { key: 'value' }; + targetContainer.dispatchEvent = jest.fn(); + + // Define a callback function + const callbackFunction = (data) => { + // This function should not be called in this test + }; + + // Act + service.dispatch(eventName, targetContainer, eventData, callbackFunction, 'onCallback'); + + // Assert + globalThis.CustomEvent = jest + .fn() + .mockImplementation((type, eventInit) => ({ isTrusted: false, onCallback: callbackFunction })); + + const dispatchedEventMock = { isTrusted: false, onCallback: expect.any(Function) }; + expect(targetContainer.dispatchEvent).toHaveBeenCalledWith(expect.objectContaining(dispatchedEventMock)); + }); +}); + +describe('getTargetContainer', () => { + let service: ContainerService; + service = new ContainerService(); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should return the correct container when a matching container is found', () => { + // Arrange + const mockContainer1 = { + iframeHandle: { + iframe: { + contentWindow: 'source1' + } + } + }; + const mockContainer2 = { + iframeHandle: { + iframe: { + contentWindow: 'source2' + } + } + }; + + globalThis.__luigi_container_manager = { + container: [mockContainer1, mockContainer2] + }; + + const mockEvent = { + source: 'source2' // Matched with mockContainer2 + }; + + // Act + const targetContainer = service.getTargetContainer(mockEvent); + + // Assert + expect(targetContainer).toBe(mockContainer2); + }); + + it('should return undefined when no matching container is found', () => { + // Arrange + const mockContainer1 = { + iframeHandle: { + iframe: { + contentWindow: 'source1' + } + } + }; + const mockContainer2 = { + iframeHandle: { + iframe: { + contentWindow: 'source2' + } + } + }; + + globalThis.__luigi_container_manager = { + container: [mockContainer1, mockContainer2] + }; + + const mockEvent = { + source: 'source3' // No matching container + }; + + // Act + const targetContainer = service.getTargetContainer(mockEvent); + + // Assert + expect(targetContainer).toBe(undefined); + }); +}); + +describe('getContainerManager branch', () => { + let service: ContainerService; + service = new ContainerService(); + + beforeEach(() => { + globalThis.__luigi_container_manager = undefined; + jest.resetAllMocks(); + }); + + afterEach(() => { + // Reset the global state after each test + globalThis.__luigi_container_manager = undefined; + window.removeEventListener('message', globalThis.__luigi_container_manager?.messageListener); + }); + + it('should initialize and return the container manager', () => { + const containerManager = service.getContainerManager(); + expect(containerManager).toBeDefined(); + expect(containerManager.container).toEqual([]); + expect(containerManager.messageListener).toBeDefined(); + }); + + it('should return the existing container manager if it has been initialized', () => { + const existingManager = { + container: ['existingData'], + messageListener: jest.fn() + }; + globalThis.__luigi_container_manager = existingManager; + + const containerManager = service.getContainerManager(); + expect(containerManager).toBe(existingManager); + }); + + it('should add a message event listener when initializing', () => { + globalThis.__luigi_container_manager = undefined; + const spy = jest.spyOn(window, 'addEventListener'); + + const containerManager = service.getContainerManager(); // Initialize the manager + + // Verify that addEventListener was called with 'message' event type + expect(containerManager).toBeDefined(); + expect(containerManager.container).toEqual([]); + expect(spy).toHaveBeenCalledWith('message', expect.any(Function)); + }); +}); + +describe('registerContainer', () => { + let service: ContainerService; + service = new ContainerService(); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should add an HTMLElement to the container', () => { + // Arrange + const containerManager = { + container: [] + }; + const container = document.createElement('div'); + service.getContainerManager = jest.fn().mockReturnValue(containerManager); + + // Act + service.registerContainer(container); + + // Assert + expect(containerManager.container).toContain(container); + expect(service.getContainerManager).toHaveBeenCalled(); + }); +}); diff --git a/core/src/core-api/config.js b/core/src/core-api/config.js index 2dadc375f9..61a44995df 100644 --- a/core/src/core-api/config.js +++ b/core/src/core-api/config.js @@ -18,7 +18,7 @@ class LuigiConfig { * @memberof Configuration */ constructor() { - this.configReadyCallback = function() {}; + this.configReadyCallback = function () {}; this.initialized = false; this.USER_SETTINGS_KEY = 'luigi.preferences.userSettings'; } @@ -104,11 +104,11 @@ class LuigiConfig { configChanged(...scope) { const optimizedScope = StateHelpers.optimizeScope(scope); if (optimizedScope.length > 0) { - optimizedScope.forEach(s => { + optimizedScope.forEach((s) => { window.Luigi._store.fire(s, { current: window.Luigi._store }); }); } else { - window.Luigi._store.update(config => config); + window.Luigi._store.update((config) => config); } } @@ -299,9 +299,9 @@ class LuigiConfig { clearNavigationCache() { NodeDataManagementStorage.deleteCache(); - const clearTitleResolverCache = nodes => { + const clearTitleResolverCache = (nodes) => { if (nodes && nodes.forEach) { - nodes.forEach(node => { + nodes.forEach((node) => { if (node.titleResolver && node.titleResolver._cache) { node.titleResolver._cache = undefined; } @@ -350,7 +350,7 @@ class LuigiConfig { updateContextValues(ctx) { const visibleIframes = IframeHelpers.getMicrofrontendIframes(); if (visibleIframes && visibleIframes.length > 0) { - visibleIframes.forEach(iframe => { + visibleIframes.forEach((iframe) => { // luigi configuration data about the mf which is rendered in the iframe if (iframe.luigi) { IframeHelpers.sendMessageToIframe(iframe, { @@ -368,7 +368,7 @@ class LuigiConfig { } if (document.querySelector('.wcContainer')) { let luiWebComponents = document.querySelectorAll('[lui_web_component=true]'); - luiWebComponents.forEach(luiWebComponent => { + luiWebComponents.forEach((luiWebComponent) => { luiWebComponent.context = Object.assign({}, luiWebComponent.context, ctx); }); } diff --git a/core/src/main.js b/core/src/main.js index e5c5decaaf..c37dbdbf23 100644 --- a/core/src/main.js +++ b/core/src/main.js @@ -10,7 +10,7 @@ const createConfigStore = () => { const scopeSubscribers = {}; let unSubscriptions = []; return { - subscribe: fn => { + subscribe: (fn) => { //subscribe fn returns unsubscription fn unSubscriptions.push(subscribe(fn)); }, @@ -27,13 +27,13 @@ const createConfigStore = () => { fire: (scope, data) => { let subscribers = scopeSubscribers[scope]; if (subscribers) { - [...subscribers].forEach(fn => { + [...subscribers].forEach((fn) => { fn(data); }); } }, clear: () => { - unSubscriptions.forEach(sub => { + unSubscriptions.forEach((sub) => { sub(); }); unSubscriptions = []; @@ -49,7 +49,7 @@ export const getTranslation = readable((key, interpolations, locale) => { Luigi._store = store; const configReadyCallback = () => { - return new Promise(resolve => { + return new Promise((resolve) => { LuigiI18N._init(); AuthLayerSvc.init().then(() => { @@ -69,11 +69,11 @@ const configReadyCallback = () => { } }); - Luigi.showAlert = settings => { + Luigi.showAlert = (settings) => { return app.showAlert(settings); }; - Luigi.showConfirmationModal = settings => { + Luigi.showConfirmationModal = (settings) => { return app.showModal(settings); }; @@ -88,11 +88,11 @@ const configReadyCallback = () => { return app.getGlobalSearchString(); }; - Luigi.setGlobalSearchString = searchString => { + Luigi.setGlobalSearchString = (searchString) => { app.setGlobalSearchString(searchString); }; - Luigi.showSearchResult = arr => { + Luigi.showSearchResult = (arr) => { return app.showSearchResult(arr); }; @@ -115,7 +115,7 @@ const configReadyCallback = () => { } }; - Luigi.pathExists = path => { + Luigi.pathExists = (path) => { return app.pathExists(path); }; diff --git a/core/src/services/routing.js b/core/src/services/routing.js index 85328853f7..e8050811a4 100644 --- a/core/src/services/routing.js +++ b/core/src/services/routing.js @@ -137,10 +137,7 @@ class RoutingClass { } const params = window.location.search ? window.location.search : ''; const path = (window.history.state && window.history.state.path) || window.location.pathname + params; - return path - .split('/') - .slice(1) - .join('/'); + return path.split('/').slice(1).join('/'); } getCurrentPath() { @@ -159,8 +156,8 @@ class RoutingClass { return LuigiConfig.getConfigValue('routing.useHashRouting') ? window.location.hash.replace('#', '') // TODO: GenericHelpers.getPathWithoutHash(window.location.hash) fails in ContextSwitcher : window.location.search - ? GenericHelpers.trimLeadingSlash(window.location.pathname) + window.location.search - : GenericHelpers.trimLeadingSlash(window.location.pathname); + ? GenericHelpers.trimLeadingSlash(window.location.pathname) + window.location.search + : GenericHelpers.trimLeadingSlash(window.location.pathname); } /** @@ -180,7 +177,7 @@ class RoutingClass { const defaultPattern = [/access_token=/, /id_token=/]; const patterns = LuigiConfig.getConfigValue('routing.skipRoutingForUrlPatterns') || defaultPattern; - return patterns.filter(p => location.href.match(p)).length !== 0; + return patterns.filter((p) => location.href.match(p)).length !== 0; } /** @@ -688,7 +685,7 @@ class RoutingClass { this.removeLastChildFromWCContainer(); if (compound && compound.children) { - compound.children = compound.children.filter(c => NavigationHelpers.checkVisibleForFeatureToggles(c)); + compound.children = compound.children.filter((c) => NavigationHelpers.checkVisibleForFeatureToggles(c)); } WebComponentService.renderWebComponentCompound(navNode, wc_container, componentData); wc_container._luigi_pathParams = componentData.pathParams; @@ -822,7 +819,7 @@ class RoutingClass { searchParams.delete(modalParamName); searchParams.delete(`${modalParamName}Params`); let finalUrl = ''; - Array.from(searchParams.keys()).forEach(searchParamKey => { + Array.from(searchParams.keys()).forEach((searchParamKey) => { finalUrl += (finalUrl === '' ? '?' : '&') + searchParamKey + '=' + searchParams.get(searchParamKey); }); url.search = finalUrl; @@ -834,7 +831,7 @@ class RoutingClass { let isModalHistoryHigherThanHistoryLength = false; window.addEventListener( 'popstate', - e => { + (e) => { if (isModalHistoryHigherThanHistoryLength) { //replace the url with saved path and get rid of modal data in url history.replaceState({}, '', path); diff --git a/core/src/utilities/helpers/navigation-helpers.js b/core/src/utilities/helpers/navigation-helpers.js index b4e87c15c4..d03c1f54c1 100644 --- a/core/src/utilities/helpers/navigation-helpers.js +++ b/core/src/utilities/helpers/navigation-helpers.js @@ -41,16 +41,9 @@ class NavigationHelpersClass { prepareForTests(...parts) { let result = ''; - parts.forEach(p => { + parts.forEach((p) => { if (p) { - result += - (result ? '_' : '') + - encodeURIComponent( - p - .toLowerCase() - .split(' ') - .join('') - ); + result += (result ? '_' : '') + encodeURIComponent(p.toLowerCase().split(' ').join('')); } }); return result; @@ -115,7 +108,7 @@ class NavigationHelpersClass { let groupCounter = 0; let virtualGroupCounter = 0; - const orderNodes = nodes => { + const orderNodes = (nodes) => { nodes.sort((a, b) => { const oa = a.order || 0; const ob = b.order || 0; @@ -123,7 +116,7 @@ class NavigationHelpersClass { }); }; - nodes.forEach(node => { + nodes.forEach((node) => { let key; let metaInfo; const category = node[property]; @@ -173,7 +166,7 @@ class NavigationHelpersClass { // Previously we skipped pushing hideFromNav nodes, but we need them now for TabNav logic arr.push(node); }); - Object.keys(result).forEach(category => { + Object.keys(result).forEach((category) => { const metaInfo = result[category].metaInfo; if (metaInfo && metaInfo.id) { result[metaInfo.label] = result[metaInfo.id]; @@ -181,7 +174,7 @@ class NavigationHelpersClass { } }); - Object.keys(result).forEach(category => { + Object.keys(result).forEach((category) => { orderNodes(result[category]); if (result[category].length === 0) { delete result[category]; @@ -215,7 +208,7 @@ class NavigationHelpersClass { let badgeCountsToSumUp = []; for (const node of rawChildren) { - pathData.forEach(n => { + pathData.forEach((n) => { if (!selectedNode && n === node) { selectedNode = node; } @@ -327,7 +320,7 @@ class NavigationHelpersClass { let collapsedList = JSON.parse(localStorage.getItem(this.COLLAPSED_SUPER_CATEGORIES_KEY)) || []; if (value) { - collapsedList = collapsedList.filter(item => item !== key); + collapsedList = collapsedList.filter((item) => item !== key); } else { if (!collapsedList.includes(key)) { collapsedList.push(key); @@ -350,7 +343,7 @@ class NavigationHelpersClass { if (value) { if (replace) { // Filter out other categories - expandedList = expandedList.filter(f => f.indexOf(context + ':') === -1); + expandedList = expandedList.filter((f) => f.indexOf(context + ':') === -1); } if (expandedList.indexOf(key) < 0) { @@ -449,7 +442,7 @@ class NavigationHelpersClass { substituteVars(resolver, context) { const resolverString = JSON.stringify(resolver); - const resString = resolverString.replace(/\$\{[a-zA-Z0-9$_.]+\}/g, match => { + const resString = resolverString.replace(/\$\{[a-zA-Z0-9$_.]+\}/g, (match) => { const chain = match.substr(2, match.length - 3); return this.getPropertyChainValue(context, chain) || match; }); @@ -501,8 +494,8 @@ class NavigationHelpersClass { headers: requestOptions.headers, body: JSON.stringify(requestOptions.body) }) - .then(response => { - response.json().then(data => { + .then((response) => { + response.json().then((data) => { try { const titleData = this.processTitleData(data, resolver, node); node.titleResolver._cache = { @@ -515,10 +508,10 @@ class NavigationHelpersClass { } }); }) - .catch(error => { + .catch((error) => { reject(error); }); - }).catch(error => { + }).catch((error) => { reject(error); }); } diff --git a/core/src/utilities/helpers/routing-helpers.js b/core/src/utilities/helpers/routing-helpers.js index 495da36dc7..c14925bc28 100644 --- a/core/src/utilities/helpers/routing-helpers.js +++ b/core/src/utilities/helpers/routing-helpers.js @@ -22,21 +22,21 @@ class RoutingHelpersClass { const children = childrenResolverFn ? await childrenResolverFn(lastElement, pathData.context) : await AsyncHelpers.getConfigValueFromObjectAsync(lastElement, 'children', pathData.context); - const pathExists = children.find(childNode => childNode.pathSegment === lastElement.defaultChildNode); + const pathExists = children.find((childNode) => childNode.pathSegment === lastElement.defaultChildNode); if (lastElement.defaultChildNode && pathExists) { return lastElement.defaultChildNode; } else if (children && children.length) { const rootPath = pathData.navigationPath.length === 1; if (rootPath) { - const firstNodeWithPathSegment = children.find(child => child.pathSegment); + const firstNodeWithPathSegment = children.find((child) => child.pathSegment); return ( (firstNodeWithPathSegment && firstNodeWithPathSegment.pathSegment) || console.error('At least one navigation node in the root hierarchy must have a pathSegment.') ); } const validChild = children.find( - child => + (child) => child.pathSegment && (child.viewUrl || child.compound || (child.externalLink && child.externalLink.url)) ); if (validChild) return validChild.pathSegment; @@ -51,7 +51,7 @@ class RoutingHelpersClass { const viewParamString = paramsString.replace(/\+/g, ' '); const pairs = viewParamString ? viewParamString.split('&') : null; if (pairs) { - pairs.forEach(pairString => { + pairs.forEach((pairString) => { const keyValue = pairString.split('='); if (keyValue && keyValue.length > 0) { result[decodeURIComponent(keyValue[0])] = decodeURIComponent(keyValue[1]); @@ -73,7 +73,7 @@ class RoutingHelpersClass { const result = {}; const paramPrefix = this.getContentViewParamPrefix(); if (params) { - Object.entries(params).forEach(entry => { + Object.entries(params).forEach((entry) => { if (entry[0].startsWith(paramPrefix)) { const paramName = entry[0].substr(paramPrefix.length); result[paramName] = entry[1]; @@ -174,14 +174,14 @@ class RoutingHelpersClass { addRouteChangeListener(callback) { const hashRoutingActive = LuigiConfig.getConfigValue('routing.useHashRouting'); - EventListenerHelpers.addEventListener('message', e => { + EventListenerHelpers.addEventListener('message', (e) => { if (e.data.msg === 'refreshRoute' && e.origin === window.origin) { const path = hashRoutingActive ? Routing.getHashPath() : Routing.getModifiedPathname(); callback(path); } }); - EventListenerHelpers.addEventListener('popstate', e => { + EventListenerHelpers.addEventListener('popstate', (e) => { const path = hashRoutingActive ? Routing.getHashPath(location.href) : Routing.getModifiedPathname(); callback(path, e.detail); }); @@ -260,8 +260,8 @@ class RoutingHelpersClass { return Object.entries(object) .map(([key, value]) => { const foundKey = contains - ? Object.keys(paramMap).find(key2 => value && value.indexOf(paramPrefix + key2) >= 0) - : Object.keys(paramMap).find(key2 => value === paramPrefix + key2); + ? Object.keys(paramMap).find((key2) => value && value.indexOf(paramPrefix + key2) >= 0) + : Object.keys(paramMap).find((key2) => value === paramPrefix + key2); return [ key, foundKey ? (contains ? value.replace(paramPrefix + foundKey, paramMap[foundKey]) : paramMap[foundKey]) : value @@ -375,7 +375,7 @@ class RoutingHelpersClass { } const featureToggleList = featureTogglesFromUrl.split(','); if (featureToggleList.length > 0 && featureToggleList[0] !== '') { - featureToggleList.forEach(ft => LuigiFeatureToggles.setFeatureToggle(ft, true)); + featureToggleList.forEach((ft) => LuigiFeatureToggles.setFeatureToggle(ft, true)); } } @@ -457,7 +457,7 @@ class RoutingHelpersClass { const intentObject = this.getIntentObject(caseInsensitiveLink); if (intentObject) { let realPath = mappings.find( - item => item.semanticObject === intentObject.semanticObject && item.action === intentObject.action + (item) => item.semanticObject === intentObject.semanticObject && item.action === intentObject.action ); if (!realPath) { return false; @@ -527,7 +527,7 @@ class RoutingHelpersClass { prepareSearchParamsForClient(currentNode) { const filteredObj = {}; if (currentNode && currentNode.clientPermissions && currentNode.clientPermissions.urlParameters) { - Object.keys(currentNode.clientPermissions.urlParameters).forEach(key => { + Object.keys(currentNode.clientPermissions.urlParameters).forEach((key) => { if (key in LuigiRouting.getSearchParams() && currentNode.clientPermissions.urlParameters[key].read === true) { filteredObj[key] = LuigiRouting.getSearchParams()[key]; } @@ -544,7 +544,7 @@ class RoutingHelpersClass { if (currentNode && currentNode.clientPermissions && currentNode.clientPermissions.urlParameters) { const filteredObj = {}; - Object.keys(currentNode.clientPermissions.urlParameters).forEach(key => { + Object.keys(currentNode.clientPermissions.urlParameters).forEach((key) => { if (key in localSearchParams && currentNode.clientPermissions.urlParameters[key].write === true) { filteredObj[key] = localSearchParams[key]; delete localSearchParams[key]; diff --git a/core/test/core-api/config.spec.js b/core/test/core-api/config.spec.js index cd1f09adcb..3a255f37a9 100644 --- a/core/test/core-api/config.spec.js +++ b/core/test/core-api/config.spec.js @@ -78,10 +78,7 @@ describe('updateContextValues', () => { querySelectorStub.withArgs('.wcContainer').returns(mockContainer); - sinon - .stub(document, 'querySelectorAll') - .withArgs('[lui_web_component=true]') - .returns(mockLuiWebComponents); + sinon.stub(document, 'querySelectorAll').withArgs('[lui_web_component=true]').returns(mockLuiWebComponents); const newContext = { updatedContext: 'updated' }; @@ -90,7 +87,7 @@ describe('updateContextValues', () => { sinon.assert.calledWith(querySelectorStub, '.wcContainer'); sinon.assert.calledWith(document.querySelectorAll, '[lui_web_component=true]'); - mockLuiWebComponents.forEach(component => { + mockLuiWebComponents.forEach((component) => { assert.deepEqual(component.context, { initialContext: 'initial', updatedContext: 'updated' }); }); }); @@ -103,7 +100,7 @@ describe('updateContextValues', () => { }; let containerStub = sinon.stub(LuigiElements, 'getLuigiContainer').returns({ firstChild: {}, - removeChild: sinon.spy(function() { + removeChild: sinon.spy(function () { this.firstChild = null; }) }); diff --git a/core/test/utilities/helpers/navigation-helpers.spec.js b/core/test/utilities/helpers/navigation-helpers.spec.js index 5ce9c71d86..11d4fe0ee3 100644 --- a/core/test/utilities/helpers/navigation-helpers.spec.js +++ b/core/test/utilities/helpers/navigation-helpers.spec.js @@ -455,7 +455,7 @@ describe('Navigation-helpers', () => { } }; - it('should reject if no titleResolver set', done => { + it('should reject if no titleResolver set', (done) => { NavigationHelpers.fetchNodeTitleData({ titleResolver: undefined }, {}) .then(() => { assert.fail('Should not be here'); @@ -466,7 +466,7 @@ describe('Navigation-helpers', () => { }); }); - it('should get data from cache', done => { + it('should get data from cache', (done) => { const node = { titleResolver: { url: 'http://localhost' @@ -482,13 +482,13 @@ describe('Navigation-helpers', () => { value: value }; - NavigationHelpers.fetchNodeTitleData(node, node.context).then(data => { + NavigationHelpers.fetchNodeTitleData(node, node.context).then((data) => { assert.equal(data, value); done(); }); }); - it('should use correct request data and properly process response data', done => { + it('should use correct request data and properly process response data', (done) => { const node = JSON.parse(JSON.stringify(samplenode)); let fetchUrl, fetchOptions; @@ -516,7 +516,7 @@ describe('Navigation-helpers', () => { assertRequestData(); done(); }) - .catch(e => { + .catch((e) => { assertRequestData(); done(); }); diff --git a/core/third-party-cookies/init.html b/core/third-party-cookies/init.html index 01014781cc..4c3d9420c2 100644 --- a/core/third-party-cookies/init.html +++ b/core/third-party-cookies/init.html @@ -5,8 +5,8 @@ if (cookies) { luigiCookie = cookies .split(';') - .map(cookie => cookie.trim()) - .find(cookie => cookie == 'luigiCookie=true'); + .map((cookie) => cookie.trim()) + .find((cookie) => cookie == 'luigiCookie=true'); } if (luigiCookie === 'luigiCookie=true') { document.cookie = 'luigiCookie=; Max-Age=-99999999; SameSite=None; Secure'; @@ -16,8 +16,8 @@ if (cookies) { luigiCookie = cookies .split(';') - .map(cookie => cookie.trim()) - .find(cookie => cookie == 'luigiCookie=true'); + .map((cookie) => cookie.trim()) + .find((cookie) => cookie == 'luigiCookie=true'); } if (luigiCookie === 'luigiCookie=true') { window.parent.postMessage('luigi.tpcEnabled', '*'); diff --git a/scripts/tools/publish-nightly.js b/scripts/tools/publish-nightly.js index f42aca38c2..7f6b08b496 100644 --- a/scripts/tools/publish-nightly.js +++ b/scripts/tools/publish-nightly.js @@ -15,9 +15,9 @@ import color from 'cli-color'; /** * COLORS */ -const logHeadline = str => console.log(color.bold.cyan(str)); -const logWarning = str => console.log(color.yellow.bold(str)); -const logError = str => console.log(color.redBright.bold(str)); +const logHeadline = (str) => console.log(color.bold.cyan(str)); +const logWarning = (str) => console.log(color.yellow.bold(str)); +const logError = (str) => console.log(color.redBright.bold(str)); const logStep = (s1, s2, s3) => { if (s3) { console.log(color.cyan(s1), color.cyan(s2), color.cyan(s3)); @@ -58,10 +58,7 @@ if (!process.env.NIGHTLY_VERSION) { } function execTrim(cmd) { - return require('child_process') - .execSync(cmd) - .toString() - .trim(); + return require('child_process').execSync(cmd).toString().trim(); } let LATEST_TAG = execTrim('git tag -l | tail -1'); @@ -82,10 +79,10 @@ const FILES_CHANGED = execTrim(`git diff --name-only HEAD ${LATEST_TAG}`); * Checks if files have been changed since last git tag. * @returns changed, a list of packagePaths keys */ - const changed = Object.entries(packagePaths).filter(val => { + const changed = Object.entries(packagePaths).filter((val) => { return ( forcedRelease || - FILES_CHANGED.split('\n').some(file => { + FILES_CHANGED.split('\n').some((file) => { return file.indexOf(val[1].join('/')) !== -1; }) ); @@ -95,11 +92,11 @@ const FILES_CHANGED = execTrim(`git diff --name-only HEAD ${LATEST_TAG}`); logHeadline('\nNothing to publish.'); } else { logHeadline('\nPackages to publish:\n'); - const packagesToUpdate = Object.entries(packagePaths).map(c => c[0]); + const packagesToUpdate = Object.entries(packagePaths).map((c) => c[0]); logStep(packagesToUpdate.join(', ')); logStep('\n'); - packagesToUpdate.forEach(pkg => { + packagesToUpdate.forEach((pkg) => { const publicPath = `${base}/${publishPaths[pkg].join('/')}`; const pkgJson = require(publicPath + '/package.json'); logStep(`Publishing ${pkgJson.name}@${pkgJson.version}`); diff --git a/scripts/tools/release-cli/release-cli.js b/scripts/tools/release-cli/release-cli.js index 9ceb5eaa42..83b7f3b258 100644 --- a/scripts/tools/release-cli/release-cli.js +++ b/scripts/tools/release-cli/release-cli.js @@ -17,9 +17,9 @@ import color from 'cli-color'; /** * COLORS */ -const logHeadline = str => console.log(color.bold.cyan(str)); -const logWarning = str => console.log(color.yellow.bold(str)); -const logError = str => console.log(color.redBright.bold(str)); +const logHeadline = (str) => console.log(color.bold.cyan(str)); +const logWarning = (str) => console.log(color.yellow.bold(str)); +const logError = (str) => console.log(color.redBright.bold(str)); const logStep = (s1, s2, s3) => { if (s3) { console.log(color.cyan(s1), color.cyan(s2), color.cyan(s3)); @@ -82,7 +82,7 @@ async function getReleases() { } }); return JSON.parse(input.body) - .map(r => r.tag_name) + .map((r) => r.tag_name) .filter((t, i) => i <= 8); } @@ -169,7 +169,7 @@ function addToChangelog(versionText, changelog, lastline) { type: 'text', name: 'version', message: 'Version you want to release (current: ' + getVersion('core') + ')?', - validate: str => (semver.valid(str) ? true : 'Invalid version (no valid semver)'), + validate: (str) => (semver.valid(str) ? true : 'Invalid version (no valid semver)'), initial: nextVersion }, { @@ -179,7 +179,7 @@ function addToChangelog(versionText, changelog, lastline) { initial: true }, { - type: prev => (prev == true ? 'select' : null), + type: (prev) => (prev == true ? 'select' : null), name: 'prevVersion', message: 'Previous version to generate from?', choices: releases diff --git a/test/e2e-test-application/cypress/e2e/tests/1-angular/login-flow-nav-dropdown.cy.js b/test/e2e-test-application/cypress/e2e/tests/1-angular/login-flow-nav-dropdown.cy.js index 15dd22ba19..664833db7c 100644 --- a/test/e2e-test-application/cypress/e2e/tests/1-angular/login-flow-nav-dropdown.cy.js +++ b/test/e2e-test-application/cypress/e2e/tests/1-angular/login-flow-nav-dropdown.cy.js @@ -16,9 +16,7 @@ describe('Login Flow', () => { cy.get('[data-testid="luigi-topnav-profile"]').click(); cy.get('[data-testid="luigi-topnav-profile-item"]').contains('Project One'); - cy.get('[data-testid="luigi-topnav-profile-item"]') - .eq(1) - .click(); + cy.get('[data-testid="luigi-topnav-profile-item"]').eq(1).click(); cy.expectPathToBe('/projects/pr1'); @@ -55,7 +53,7 @@ describe('Login Flow', () => { cy.get('[data-testid="luigi-topnav-title"]').should('contain', 'Luigi Demo'); cy.get('[data-testid="luigi-topnav-title"]').should('not.have.attr', 'src', testLogo); - cy.window().then(win => { + cy.window().then((win) => { const config = win.Luigi.getConfig(); config.settings.header.title = testTitle; config.settings.header.logo = testLogo; @@ -101,9 +99,7 @@ describe('TopNavDropDown', () => { cy.get('[data-testid="opengoogleinthistab"]').contains('Open Google in this tab'); - cy.get('[data-testid="all-users_visibleforallusers"]') - .contains('Visible for all users') - .click(); + cy.get('[data-testid="all-users_visibleforallusers"]').contains('Visible for all users').click(); cy.expectPathToBe('/all-users'); }); @@ -120,9 +116,7 @@ describe('TopNavDropDown', () => { //open mobile topnav dropdown cy.get('[data-e2e="mobile-topnav-dropdown-category"][title="Misc"]').click(); - cy.get('[data-e2e="mobile-topnav-item"]') - .contains('Visible for all users') - .click(); + cy.get('[data-e2e="mobile-topnav-item"]').contains('Visible for all users').click(); cy.expectPathToBe('/all-users'); }); diff --git a/test/e2e-test-application/src/logout.html b/test/e2e-test-application/src/logout.html index ffe6bb7931..fb48cc6b70 100644 --- a/test/e2e-test-application/src/logout.html +++ b/test/e2e-test-application/src/logout.html @@ -1,4 +1,4 @@ - + diff --git a/website/docs/src/app.html b/website/docs/src/app.html index 7275db21d4..8ee15127e7 100644 --- a/website/docs/src/app.html +++ b/website/docs/src/app.html @@ -1,4 +1,4 @@ - + @@ -15,7 +15,7 @@
%sveltekit.body%
diff --git a/container/test-app/iframe/microfrontend.html b/container/test-app/iframe/microfrontend.html index 34cd1f106b..7a865ce525 100644 --- a/container/test-app/iframe/microfrontend.html +++ b/container/test-app/iframe/microfrontend.html @@ -69,22 +69,22 @@

Multi purpose demo page