diff --git a/api/src/client/package-lock.json b/api/src/client/package-lock.json index a04334e0ba..904cda5474 100644 --- a/api/src/client/package-lock.json +++ b/api/src/client/package-lock.json @@ -17,12 +17,13 @@ "babel-plugin-syntax-dynamic-import": "^6.18.0", "chai": "^4.3.7", "compression-webpack-plugin": "^10.0.0", + "copy-webpack-plugin": "^14.0.0", "cross-env": "^7.0.3", "fs-extra": "^11.1.1", "global-jsdom": "^29.0.0", "html-webpack-plugin": "^5.5.1", "jsdom": "^29.1.0", - "jsf.js_next_gen": "4.1.0-beta.14", + "jsf.js_next_gen": "4.1.0-beta.16", "mocha": "^11.7.5", "npm-check-updates": "^22.0.1", "nyc": "^18.0.0", @@ -2464,9 +2465,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", "dev": true, "license": "MIT", "dependencies": { @@ -2478,7 +2479,7 @@ "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", - "qs": "~6.14.0", + "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" @@ -3074,6 +3075,43 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true }, + "node_modules/copy-webpack-plugin": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-14.0.0.tgz", + "integrity": "sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-parent": "^6.0.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^7.0.3", + "tinyglobby": "^0.2.12" + }, + "engines": { + "node": ">= 20.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -3691,15 +3729,15 @@ } }, "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", "dev": true, "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "~1.20.3", + "body-parser": "~1.20.5", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", @@ -3718,7 +3756,7 @@ "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "~6.14.0", + "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", @@ -5052,13 +5090,13 @@ } }, "node_modules/jsf.js_next_gen": { - "version": "4.1.0-beta.14", - "resolved": "https://registry.npmjs.org/jsf.js_next_gen/-/jsf.js_next_gen-4.1.0-beta.14.tgz", - "integrity": "sha512-fdFD6+7brgddZe1ZWVEJxHstuKe4qLOenjI33ZPBqNpQJVa7ghTkt+vHgkkruV6hw68UY65Dmmu/GvTpdI7P2g==", + "version": "4.1.0-beta.16", + "resolved": "https://registry.npmjs.org/jsf.js_next_gen/-/jsf.js_next_gen-4.1.0-beta.16.tgz", + "integrity": "sha512-YxO3gAvPxEG6gHN0S2Yxd1YG7/ULfR7LnED11lEC8pKkALvm6Ez2Ize/P/BGL6lVEJTCx6XkFBcUHeWev3jjmg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "mona-dish": "0.40.0-beta.4" + "mona-dish": "0.50.0-beta.2" } }, "node_modules/json-schema-traverse": { @@ -5529,9 +5567,9 @@ } }, "node_modules/mona-dish": { - "version": "0.40.0-beta.4", - "resolved": "https://registry.npmjs.org/mona-dish/-/mona-dish-0.40.0-beta.4.tgz", - "integrity": "sha512-Cx735lpHSYtQeDNKwE+Gy7Z3AckJr6kcst74d0+TDM5w6up4yDLUFfSj4g6KaH4jkYLMEVT5vIhZK4gzV36sBw==", + "version": "0.50.0-beta.2", + "resolved": "https://registry.npmjs.org/mona-dish/-/mona-dish-0.50.0-beta.2.tgz", + "integrity": "sha512-GTn/ZOrGMlemVcq+Q7FOqREs8j5IfuoESqXmi37tPKnV4GkjpXc7cQNELj9ZLWRF0BSKAQ/NrSRJqG2EPAg5yg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -5705,9 +5743,9 @@ } }, "node_modules/nyc/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { @@ -6378,9 +6416,9 @@ } }, "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -6677,9 +6715,9 @@ } }, "node_modules/rimraf/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { @@ -7532,9 +7570,9 @@ } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { @@ -7619,6 +7657,54 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tldts": { "version": "7.0.29", "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.29.tgz", @@ -7905,9 +7991,9 @@ } }, "node_modules/typedoc/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { @@ -8494,10 +8580,11 @@ } }, "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -10335,9 +10422,9 @@ "dev": true }, "body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", "dev": true, "requires": { "bytes": "~3.1.2", @@ -10348,7 +10435,7 @@ "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", - "qs": "~6.14.0", + "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" @@ -10775,6 +10862,30 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true }, + "copy-webpack-plugin": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-14.0.0.tgz", + "integrity": "sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==", + "dev": true, + "requires": { + "glob-parent": "^6.0.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": ">=7.0.5", + "tinyglobby": "^0.2.12" + }, + "dependencies": { + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + } + } + }, "core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -11210,14 +11321,14 @@ "dev": true }, "express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", "dev": true, "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "~1.20.3", + "body-parser": "~1.20.5", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", @@ -11236,7 +11347,7 @@ "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "~6.14.0", + "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", @@ -12166,12 +12277,12 @@ "dev": true }, "jsf.js_next_gen": { - "version": "4.1.0-beta.14", - "resolved": "https://registry.npmjs.org/jsf.js_next_gen/-/jsf.js_next_gen-4.1.0-beta.14.tgz", - "integrity": "sha512-fdFD6+7brgddZe1ZWVEJxHstuKe4qLOenjI33ZPBqNpQJVa7ghTkt+vHgkkruV6hw68UY65Dmmu/GvTpdI7P2g==", + "version": "4.1.0-beta.16", + "resolved": "https://registry.npmjs.org/jsf.js_next_gen/-/jsf.js_next_gen-4.1.0-beta.16.tgz", + "integrity": "sha512-YxO3gAvPxEG6gHN0S2Yxd1YG7/ULfR7LnED11lEC8pKkALvm6Ez2Ize/P/BGL6lVEJTCx6XkFBcUHeWev3jjmg==", "dev": true, "requires": { - "mona-dish": "0.40.0-beta.4" + "mona-dish": "0.50.0-beta.2" } }, "json-schema-traverse": { @@ -12514,9 +12625,9 @@ } }, "mona-dish": { - "version": "0.40.0-beta.4", - "resolved": "https://registry.npmjs.org/mona-dish/-/mona-dish-0.40.0-beta.4.tgz", - "integrity": "sha512-Cx735lpHSYtQeDNKwE+Gy7Z3AckJr6kcst74d0+TDM5w6up4yDLUFfSj4g6KaH4jkYLMEVT5vIhZK4gzV36sBw==", + "version": "0.50.0-beta.2", + "resolved": "https://registry.npmjs.org/mona-dish/-/mona-dish-0.50.0-beta.2.tgz", + "integrity": "sha512-GTn/ZOrGMlemVcq+Q7FOqREs8j5IfuoESqXmi37tPKnV4GkjpXc7cQNELj9ZLWRF0BSKAQ/NrSRJqG2EPAg5yg==", "dev": true, "requires": { "token": "^0.1.0" @@ -12651,9 +12762,9 @@ "dev": true }, "brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "requires": { "balanced-match": "^4.0.2" @@ -13148,9 +13259,9 @@ "dev": true }, "qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "dev": true, "requires": { "side-channel": "^1.1.0" @@ -13357,9 +13468,9 @@ "dev": true }, "brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "requires": { "balanced-match": "^4.0.2" @@ -13981,9 +14092,9 @@ "dev": true }, "brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "requires": { "balanced-match": "^4.0.2" @@ -14034,6 +14145,31 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, + "tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "requires": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "dependencies": { + "fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "requires": {} + }, + "picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true + } + } + }, "tldts": { "version": "7.0.29", "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.29.tgz", @@ -14222,9 +14358,9 @@ "dev": true }, "brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "requires": { "balanced-match": "^4.0.2" @@ -14612,9 +14748,9 @@ } }, "ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "dev": true, "requires": {} }, diff --git a/api/src/client/package.json b/api/src/client/package.json index 0a9a67f536..ffa2863146 100644 --- a/api/src/client/package.json +++ b/api/src/client/package.json @@ -19,12 +19,13 @@ "babel-plugin-syntax-dynamic-import": "^6.18.0", "chai": "^4.3.7", "compression-webpack-plugin": "^10.0.0", + "copy-webpack-plugin": "^14.0.0", "cross-env": "^7.0.3", "fs-extra": "^11.1.1", - "html-webpack-plugin": "^5.5.1", "global-jsdom": "^29.0.0", + "html-webpack-plugin": "^5.5.1", "jsdom": "^29.1.0", - "jsf.js_next_gen": "4.1.0-beta.14", + "jsf.js_next_gen": "4.1.0-beta.16", "mocha": "^11.7.5", "npm-check-updates": "^22.0.1", "nyc": "^18.0.0", diff --git a/api/src/client/typescript/faces/@types/definitions/faces.d.ts b/api/src/client/typescript/faces/@types/definitions/faces.d.ts new file mode 100644 index 0000000000..340ea3e62f --- /dev/null +++ b/api/src/client/typescript/faces/@types/definitions/faces.d.ts @@ -0,0 +1,293 @@ +/*! Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +declare namespace faces { + /** + * Project stage values, mirroring jakarta.faces.application.ProjectStage. + */ + export type ProjectStage = "Development" | "UnitTest" | "SystemTest" | "Production"; + /** + * Status values sent to ajax event callbacks. + */ + export type AjaxEventStatus = "begin" | "complete" | "success"; + /** + * Status values sent to ajax error callbacks. + */ + export type AjaxErrorStatus = "httpError" | "emptyResponse" | "malformedXML" | "serverError"; + /** + * Common shape for the data passed to ajax callbacks. + */ + export interface AjaxData { + source?: Element; + responseCode?: number; + responseText?: string; + responseXML?: XMLDocument; + } + /** + * Data passed to ajax event callbacks. + */ + export interface AjaxEvent extends AjaxData { + type: "event"; + status: AjaxEventStatus; + } + /** + * Data passed to ajax error callbacks. + */ + export interface AjaxError extends AjaxData { + type: "error"; + status: AjaxErrorStatus | "clientError" | "timeout"; + errorName?: string; + errorMessage?: string; + /** @deprecated MyFaces compatibility alias. */ + serverErrorName?: string; + /** @deprecated MyFaces compatibility alias. */ + serverErrorMessage?: string; + /** MyFaces compatibility detail. */ + description?: string; + /** MyFaces compatibility detail. */ + typeDetails?: unknown; + } + /** + * Version of the implementation for the faces.ts. + *

+ * as specified within the jsf specifications faces.html: + *

+ */ + export const specversion: number; + /** + * Implementation version as specified within the jsf specification. + *

+ * A number increased with every implementation version + * and reset by moving to a new spec release number + * + */ + export const implversion: number; + /** + * SeparatorChar as defined by facesContext.getNamingContainerSeparatorChar() + */ + export const separatorchar: string; + /** + * Context Path as defined externalContext.requestContextPath + */ + export const contextpath: string; + /** + * This method is responsible for the return of a given project stage as defined + * by the jsf specification. + *

+ * Valid return values are: + *

*/ - export var specversion = 400000; + export const specversion: number = 400000; /** * Implementation version as specified within the jsf specification. *

@@ -43,18 +92,18 @@ export namespace faces { * and reset by moving to a new spec release number * */ - export var implversion = 0; + export const implversion: number = 0; /** * SeparatorChar as defined by facesContext.getNamingContainerSeparatorChar() */ - export var separatorchar: string = getSeparatorChar(); + export const separatorchar: string = getSeparatorChar(); // noinspection JSUnusedGlobalSymbols /** * Context Path as defined externalContext.requestContextPath */ - export var contextpath: string = '#{facesContext.externalContext.requestContextPath}'; + export const contextpath: string = '#{facesContext.externalContext.requestContextPath}'; // we do not have a fallback here, for now /** @@ -72,8 +121,8 @@ export namespace faces { * @return {String} the current project state emitted by the server side method: * jakarta.faces.application.Application.getProjectStage() */ - export function getProjectStage(): string | null { - return Implementation.getProjectStage(); + export function getProjectStage(): ProjectStage { + return Implementation.getProjectStage() as ProjectStage; } /** @@ -112,6 +161,42 @@ export namespace faces { export namespace ajax { "use strict"; + /** + * Callback signature for ajax lifecycle events. + */ + export type OnEventCallback = (data: AjaxEvent) => void; + + /** + * Callback signature for ajax errors. + */ + export type OnErrorCallback = (data: AjaxError) => void; + + /** + * Options object for faces.ajax.request. + */ + export interface RequestOptions { + execute?: string; + render?: string; + onevent?: OnEventCallback; + onerror?: OnErrorCallback; + params?: Record; + delay?: number | "none"; + resetValues?: boolean; + /** MyFaces extension/pass-through compatibility. */ + [key: string]: any; + } + + /** + * Per-request context object passed to faces.ajax.response. + */ + export interface RequestContext { + sourceid?: string; + onerror?: OnErrorCallback; + onevent?: OnEventCallback; + /** MyFaces extension/pass-through compatibility. */ + [key: string]: any; + } + /** * this function has to send the ajax requests * @@ -127,8 +212,8 @@ export namespace faces { * @param {EVENT} event: any javascript event supported by that object * @param {Map} options : map of options being pushed into the ajax cycle */ - export function request(element: Element, event?: Event, options?: Options): void { - Implementation.request(element, event, options) + export function request(element: Element | string, event?: Event | null, options?: RequestOptions): void { + Implementation.request(element, event as any, options as any) } /** @@ -137,7 +222,7 @@ export namespace faces { * @param context the request context * */ - export function response(request: XMLHttpRequest, context?: Context): void { + export function response(request: XMLHttpRequest, context?: RequestContext): void { Implementation.response(request, context as any); } @@ -158,7 +243,7 @@ export namespace faces { * * @param errorFunc error handler must be of the format function errorListener(<errorData>) */ - export function addOnError(errorFunc: (data: ErrorData) => void): void { + export function addOnError(errorFunc: OnErrorCallback): void { Implementation.addOnError(errorFunc as any); } @@ -168,7 +253,7 @@ export namespace faces { * * @param eventFunc event must be of the format function eventListener(<eventData>) */ - export function addOnEvent(eventFunc: (data: EventData) => void): void { + export function addOnEvent(eventFunc: OnEventCallback): void { Implementation.addOnEvent(eventFunc as any); } } @@ -186,12 +271,32 @@ export namespace faces { * @param funcs ... arbitrary array of functions or strings * @returns true if the chain has succeeded false otherwise */ - export function chain(source: HTMLElement | string, event: Event | null, ...funcs: Array): boolean { - return Implementation.chain(source, event, ...(funcs as EvalFuncs)); + export function chain(source: HTMLElement | string, event?: Event | null, ...funcs: Array): boolean { + return Implementation.chain(source, event ?? null, ...(funcs as EvalFuncs)); } } export namespace push { + /** + * Invoked when the websocket is opened. + */ + export type OnOpenHandler = (channel: string) => void; + + /** + * Invoked when a message is received from the server. + */ + export type OnMessageHandler = (message: unknown, channel: string, event: MessageEvent) => void; + + /** + * Invoked when a connection error occurs and the websocket will attempt to reconnect. + */ + export type OnErrorHandler = (code: number, channel: string, event: CloseEvent) => void; + + /** + * Invoked when the websocket is closed and will not attempt to reconnect. + */ + export type OnCloseHandler = (code: number, channel: string, event: CloseEvent) => void; + /** * @param socketClientId the sockets client identifier * @param url the uri to reach the socket @@ -206,13 +311,13 @@ export namespace faces { export function init(socketClientId: string, url: string, channel: string, - onopen: PushImpl.OpenCallback | string, - onmessage: PushImpl.MessageCallback | string, - onerror: PushImpl.ErrorCallback | string, - onclose: PushImpl.CloseCallback | string, - behaviors: any, + onopen: OnOpenHandler | string | null, + onmessage: OnMessageHandler | string | null, + onerror: OnErrorHandler | string | null, + onclose: OnCloseHandler | string | null, + behaviors: Record void>>, autoConnect: boolean): void { - PushImpl.init(socketClientId, url, channel, onopen, onmessage, onerror, onclose, behaviors, autoConnect); + PushImpl.init(socketClientId, url, channel, onopen as any, onmessage as any, onerror as any, onclose as any, behaviors, autoConnect); } /** @@ -250,7 +355,7 @@ export namespace myfaces { * @param options the options which need to be merged in * @param userParameters a set of user parameters which go into the final options under params, they can override whatever is passed via options */ - export function ab(source: Element, event: Event, eventName: string, execute: string, render: string, options: Options = {}, userParameters: Options = {}): void { + export function ab(source: Element, event: Event, eventName: string, execute: string, render: string, options: faces.ajax.RequestOptions = {}, userParameters: faces.ajax.RequestOptions = {}): void { if(!options) { options = {}; } @@ -277,7 +382,7 @@ export namespace myfaces { options["params"][key] = userParameters[key]; } - (window?.faces ?? window.jsf).ajax.request(source, event, options); + (window?.faces ?? window.jsf).ajax.request(source, event, options as any); } diff --git a/api/src/client/typescript/faces/api/_api_ae_stub.d.ts b/api/src/client/typescript/faces/api/_api_ae_stub.d.ts new file mode 100644 index 0000000000..4ced4c3ef1 --- /dev/null +++ b/api/src/client/typescript/faces/api/_api_ae_stub.d.ts @@ -0,0 +1,21 @@ + +/*! Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// Stub file consumed only by tsconfig.ae.json (api-extractor's compiler config). +// api-extractor uses mainEntryPointFilePath (target/dts/_api.d.ts) as the real +// entry — this file exists solely to satisfy TypeScript's "at least one input" +// requirement without pulling any source .ts files into the compiler program. +export {}; diff --git a/api/src/client/typescript/faces/impl/PushImpl.ts b/api/src/client/typescript/faces/impl/PushImpl.ts index c23ad9035c..69dd4371fe 100644 --- a/api/src/client/typescript/faces/impl/PushImpl.ts +++ b/api/src/client/typescript/faces/impl/PushImpl.ts @@ -49,13 +49,17 @@ export namespace PushImpl { autoconnect: boolean; }; + /** @internal */ export let sockets: {[key: string]: Socket} = {}; /* component attributes by clientId */ + /** @internal */ export let components: {[key: string]: ComponentData} = {}; /* client ids by token (share websocket connection) */ + /** @internal */ export let clientIdsByTokens: {[key: string]: string[]} = {}; // needed for testing + /** @internal */ export function reset() { Object.values(sockets).forEach(s => { try { s.close(); } catch(e) { /* ignore */ } }); sockets = {}; diff --git a/api/src/client/typescript/faces/impl/core/Const.ts b/api/src/client/typescript/faces/impl/core/Const.ts index 5768dd4c98..8bb8acd81b 100644 --- a/api/src/client/typescript/faces/impl/core/Const.ts +++ b/api/src/client/typescript/faces/impl/core/Const.ts @@ -206,8 +206,8 @@ export const UNKNOWN = "UNKNOWN"; * changed to a simple value passthrough */ -export function $faces(): FacesAPI { - return (window?.faces ?? window?.jsf) as FacesAPI; +export function $faces(): typeof faces { + return window?.faces ?? window?.jsf; } export function $nsp(inputNamespace?: any): any { diff --git a/api/src/client/typescript/faces/impl/xhrCore/ErrorData.ts b/api/src/client/typescript/faces/impl/xhrCore/ErrorData.ts index 9eddce5b9a..1832024cb3 100644 --- a/api/src/client/typescript/faces/impl/xhrCore/ErrorData.ts +++ b/api/src/client/typescript/faces/impl/xhrCore/ErrorData.ts @@ -46,7 +46,7 @@ export enum ErrorType { * everything into the same attributes, * I will add deprecated myfaces backwards compatibility attributes as well */ -export class ErrorData extends EventData implements IErrorData { +export class ErrorData extends EventData { type: string = "error"; source: string | Element; @@ -64,6 +64,7 @@ export class ErrorData extends EventData implements IErrorData { serverErrorMessage!: string; description: string; + /** @internal */ constructor(source: string | Element, errorName: string, errorMessage: string, responseText: string | null = null, responseXML: Document | null = null, responseCode: number = -1, statusOverride: string | null = null, type = ErrorType.CLIENT_ERROR) { super(); @@ -91,15 +92,18 @@ export class ErrorData extends EventData implements IErrorData { } } + /** @internal */ static fromClient(e: Error): ErrorData { return new ErrorData((e as any)?.source ?? "client", e?.name ?? EMPTY_STR, e?.message ?? EMPTY_STR, e?.stack ?? EMPTY_STR); } + /** @internal */ static fromHttpConnection(source: any, name: string, message: string, responseText: string, responseXML: Document | null, responseCode: number, status: string = EMPTY_STR): ErrorData { return new ErrorData(source, name, message, responseText, responseXML, responseCode, status, ErrorType.HTTP_ERROR); } - static fromGeneric(context: Config, errorCode: number, errorType: ErrorType = ErrorType.SERVER_ERROR): ErrorData { + /** @internal */ + static fromGeneric(context: any, errorCode: number, errorType: ErrorType = ErrorType.SERVER_ERROR): ErrorData { let getMsg = this.getMsg; @@ -114,11 +118,12 @@ export class ErrorData extends EventData implements IErrorData { return new ErrorData(source, errorName, errorMessage, responseText, responseXML, errorCode, status, errorType); } - private static getMsg(context: Config, param: string) { + private static getMsg(context: any, param: string) { return getMessage(context.getIf(param).orElse(EMPTY_STR).value); } - static fromServerError(context: Config): ErrorData { + /** @internal */ + static fromServerError(context: any): ErrorData { return this.fromGeneric(context, -1); } diff --git a/api/src/client/typescript/faces/impl/xhrCore/EventData.ts b/api/src/client/typescript/faces/impl/xhrCore/EventData.ts index 20a64521db..51bb8ca509 100644 --- a/api/src/client/typescript/faces/impl/xhrCore/EventData.ts +++ b/api/src/client/typescript/faces/impl/xhrCore/EventData.ts @@ -16,7 +16,7 @@ import {Config, DQ} from "mona-dish"; import {BEGIN, CTX_PARAM_REQ_PASS_THR, EVENT, P_AJAX_SOURCE, SOURCE} from "../core/Const"; -export class EventData implements IEventData{ +export class EventData { type!: string; status!: string | null; source: any; @@ -24,7 +24,8 @@ export class EventData implements IEventData{ responseText!: string; responseXML!: Document | null; - static createFromRequest(request: XMLHttpRequest, internalContext: Config, context: Config, /*event name*/ name: string): EventData { + /** @internal */ + static createFromRequest(request: XMLHttpRequest, internalContext: any, context: any, /*event name*/ name: string): EventData { let eventData = new EventData(); let internalSource = "_internal._source"; diff --git a/api/src/client/typescript/faces/myfaces/OamSubmit.ts b/api/src/client/typescript/faces/myfaces/OamSubmit.ts index fec6444acf..9bf6b33df0 100644 --- a/api/src/client/typescript/faces/myfaces/OamSubmit.ts +++ b/api/src/client/typescript/faces/myfaces/OamSubmit.ts @@ -78,7 +78,7 @@ export namespace oam { * @param target * @param params */ - export const submitForm = function (formName: string, linkId: string | null = null, target: string |null = null, params: AssocArr | Tuples | null = {} ): boolean { + export const submitForm = function (formName: string, linkId: string | null = null, target: string |null = null, params: Record | [string, any][] | null = {} ): boolean { //handle a possible incoming null, not sure if this is used that way anywhere, but we allow it diff --git a/api/src/client/typescript/faces/test/api/PushTypeCompatibility.ts b/api/src/client/typescript/faces/test/api/PushTypeCompatibility.ts index 7e568160da..290555d568 100644 --- a/api/src/client/typescript/faces/test/api/PushTypeCompatibility.ts +++ b/api/src/client/typescript/faces/test/api/PushTypeCompatibility.ts @@ -15,16 +15,34 @@ */ // AI-generated: this file was created with assistance from Claude (Anthropic) — see AI_CONTRIBUTIONS.md +import type { faces } from '../../api/_api'; + /** - * Compile-time type compatibility check for Push.init() overloads. - * - * This file is never executed. Its only purpose is to make the TypeScript - * compiler verify that both supported call signatures of Push.init() are - * accepted: the 4-callback form (onopen, onmessage, onerror, onclose) and - * the legacy 3-callback form (onmessage, onerror, onclose). If either - * overload is accidentally broken, tsc will reject this file. + * Compatibility interface covering both the faces 9-param form (with onerror) + * and the JSF 2.3 8-param form (without onerror). Both must be callable so + * that the runtime implementation works correctly in both the faces and JSF realms. + * This file is never executed — tsc rejecting it signals a broken signature. */ -function verifyPushInitTypeCompatibility(push: Push): void { +interface PushCompat { + init(socketClientId: string, url: string, channel: string, + onopen: faces.push.OnOpenHandler | string | null, + onmessage: faces.push.OnMessageHandler | string | null, + onerror: faces.push.OnErrorHandler | string | null, + onclose: faces.push.OnCloseHandler | string | null, + behaviors: Record void>>, + autoConnect: boolean): void; + init(socketClientId: string, url: string, channel: string, + onopen: faces.push.OnOpenHandler | string | null, + onmessage: faces.push.OnMessageHandler | string | null, + onclose: faces.push.OnCloseHandler | string | null, + behaviors: Record void>>, + autoConnect: boolean): void; + open(socketClientId: string): void; + close(socketClientId: string): void; +} + +function verifyPushInitTypeCompatibility(push: PushCompat): void { + // faces 4.0 form — with onerror push.init("clientId1", "booga.ws", "mychannel", () => {}, () => {}, @@ -34,6 +52,7 @@ function verifyPushInitTypeCompatibility(push: Push): void { true ); + // JSF 2.3 legacy form — without onerror push.init("clientId1", "booga.ws", "mychannel", () => {}, () => {}, diff --git a/api/src/client/typescript/faces/test/impl/ImplTest.spec.ts b/api/src/client/typescript/faces/test/impl/ImplTest.spec.ts index 1c4091c794..bd68da94cc 100644 --- a/api/src/client/typescript/faces/test/impl/ImplTest.spec.ts +++ b/api/src/client/typescript/faces/test/impl/ImplTest.spec.ts @@ -20,7 +20,8 @@ import {expect} from 'chai'; import * as sinon from 'sinon'; import {StandardInits} from "../frameworkBase/_ext/shared/StandardInits"; -import {CTX_PARAM_REQ_PASS_THR, P_EXECUTE, P_RENDER} from "../../impl/core/Const"; +import {CTX_PARAM_REQ_PASS_THR, IDENT_NONE, P_EXECUTE, P_RENDER} from "../../impl/core/Const"; +import {StateHolder} from "../../impl/core/ImplTypes"; const defaultMyFaces = StandardInits.defaultMyFaces; import {_Es2019Array} from "mona-dish"; @@ -170,6 +171,22 @@ describe('faces.ajax.request test suite', () => { }) + it("execute @none must delete P_EXECUTE from the request pass-through context", () => { + const addRequestToQueue = sinon.stub(Implementation.queueHandler, "addRequestToQueue"); + try { + DomQuery.byId("input_2").addEventListener("click", (event: Event) => { + faces.ajax.request(null, event, {execute: IDENT_NONE, render: IDENT_NONE}); + }).click(); + + expect(addRequestToQueue.called).to.be.true; + const context = addRequestToQueue.args[0][2] as Config; + // remapDefaultConstants must have deleted P_EXECUTE when execute="@none" + expect(context.getIf(CTX_PARAM_REQ_PASS_THR, P_EXECUTE).isAbsent()).to.be.true; + } finally { + addRequestToQueue.restore(); + } + }); + it("sidebehavior chain on undefined must not break the chain only a dedicated false does", function() { let called = {}; window.called = called; @@ -230,3 +247,60 @@ describe('faces.ajax.request test suite', () => { }); +describe('AjaxImpl.getClientWindow differenceCheck', () => { + let oldFlatMap = null; + + beforeEach(async () => { + return await StandardInits.defaultMyFaces().then(() => { + oldFlatMap = Array.prototype["flatMap"]; + (window as any)["Es2019Array"] = _Es2019Array; + delete Array.prototype["flatMap"]; + }); + }); + + afterEach(() => { + if (oldFlatMap) { + Array.prototype["flatMap"] = oldFlatMap; + oldFlatMap = null; + } + }); + + it("must throw when two forms have different windowIds", () => { + document.body.innerHTML += ` +

+ + +
+
+ + +
`; + + expect(() => faces.getClientWindow(document.body)).to.throw(); + }); + + it("must return the windowId when all forms agree", () => { + document.body.innerHTML = ` +
+ + +
`; + + const result = faces.getClientWindow(document.body); + expect(result).to.eq("samewin"); + }); +}); + +describe('StateHolder.hasNameSpace', () => { + it('returns false when id has no namespace prefix', () => { + const s = new StateHolder("jakarta.faces.ViewState", "val"); + expect(s.hasNameSpace).to.be.false; + expect(s.nameSpace).to.eq(""); + }); + + it('returns true when id has a namespace prefix', () => { + const s = new StateHolder("myForm:jakarta.faces.ViewState", "val"); + expect(s.hasNameSpace).to.be.true; + expect(s.nameSpace).to.eq("myForm"); + }); +}); diff --git a/api/src/client/typescript/faces/test/impl/util/ExtDomQueryTest.spec.ts b/api/src/client/typescript/faces/test/impl/util/ExtDomQueryTest.spec.ts index 2aef1186a6..8b455d3308 100644 --- a/api/src/client/typescript/faces/test/impl/util/ExtDomQueryTest.spec.ts +++ b/api/src/client/typescript/faces/test/impl/util/ExtDomQueryTest.spec.ts @@ -85,6 +85,36 @@ describe('ExtDomQuery test suite', () => { const text = document.createTextNode("inline text"); expect(() => new ExtDomQuery(text as any).runHeadInserts()).not.to.throw(); }); + + it('runHeadInserts with suppressDoubleIncludes=false inserts duplicate link resources', () => { + const href = "/test-res-no-dedup.css"; + const existing = document.createElement("link"); + existing.setAttribute("href", href); + document.head.appendChild(existing); + + const before = document.head.querySelectorAll(`link[href="${href}"]`).length; + + const dup = document.createElement("link"); + dup.setAttribute("href", href); + new ExtDomQuery(dup as any).runHeadInserts(false); + + expect(document.head.querySelectorAll(`link[href="${href}"]`).length).to.be.gt(before); + }); + + it('runHeadInserts with suppressDoubleIncludes=true skips duplicate link resources', () => { + const href = "/test-res-with-dedup.css"; + const existing = document.createElement("link"); + existing.setAttribute("href", href); + document.head.appendChild(existing); + + const before = document.head.querySelectorAll(`link[href="${href}"]`).length; + + const dup = document.createElement("link"); + dup.setAttribute("href", href); + new ExtDomQuery(dup as any).runHeadInserts(true); + + expect(document.head.querySelectorAll(`link[href="${href}"]`).length).to.eq(before); + }); }); describe('ExtConfig', () => { @@ -124,4 +154,5 @@ describe('ExtConfig', () => { copy.assign("name").value = "Bob"; expect(config.getIf("name").value).to.eq("Alice"); }); + }); diff --git a/api/src/client/typescript/faces/test/impl/util/ExtLangTest.spec.ts b/api/src/client/typescript/faces/test/impl/util/ExtLangTest.spec.ts index ee35f15eff..128604324c 100644 --- a/api/src/client/typescript/faces/test/impl/util/ExtLangTest.spec.ts +++ b/api/src/client/typescript/faces/test/impl/util/ExtLangTest.spec.ts @@ -39,6 +39,11 @@ describe("ExtLang", function () { expect(ExtLang.getMessage("missing.key")).to.eq("missing.key"); }); + it("must return a message string when the key exists in Messages", function () { + // MSG_TEST is defined in Messages.ts as "Testmessage" — covers the left branch of ?? defaultMessage ?? key + expect(ExtLang.getMessage("MSG_TEST")).to.eq("Testmessage"); + }); + it("must read global and local myfaces config with local priority", function () { window.myfaces.config = {...window.myfaces.config, delay: 50, timeout: 100}; diff --git a/api/src/client/typescript/faces/test/myfaces/OamSubmit.spec.ts b/api/src/client/typescript/faces/test/myfaces/OamSubmit.spec.ts index e99a52f742..5ff852467e 100644 --- a/api/src/client/typescript/faces/test/myfaces/OamSubmit.spec.ts +++ b/api/src/client/typescript/faces/test/myfaces/OamSubmit.spec.ts @@ -151,5 +151,44 @@ describe('Tests on the xhr core when it starts to call the request', function () expect(submit_spy.called).to.eq(false); }) + it('setHiddenInput must update the value when the field already exists', function () { + let FORM_ID = "blarg"; + setHiddenInput(FORM_ID, "existing_field", "first_value"); + setHiddenInput(FORM_ID, "existing_field", "updated_value"); + const inputs = DomQuery.byId(FORM_ID).querySelectorAll("input[name='existing_field']"); + expect(inputs.length).to.eq(1, "must not create a duplicate input"); + expect(inputs.inputValue.value).to.eq("updated_value", "must update the value in-place"); + }); + + it('clearHiddenInput must not throw when the field does not exist', function () { + let FORM_ID = "blarg"; + expect(() => clearHiddenInput(FORM_ID, "nonexistent_field")).not.to.throw(); + }); + + it('submitForm must work with array-style params', function () { + let FORM_ID = "blarg"; + let form = DomQuery.byId(FORM_ID); + const submit_spy = Sinon.spy(() => {}); + (form.value.value as any).submit = submit_spy; + + submitForm(FORM_ID, 'mylink', null, [["arrKey1", "arrVal1"], ["arrKey2", "arrVal2"]]); + + expect(submit_spy.called).to.eq(true); + form = DomQuery.byId(FORM_ID); + expect(form.querySelectorAll("input[name='arrKey1']").isAbsent()).to.eq(true, "params must be cleaned up after submit"); + expect(form.querySelectorAll("input[name='arrKey2']").isAbsent()).to.eq(true, "params must be cleaned up after submit"); + }); + + it('submitForm must work when params is null', function () { + let FORM_ID = "blarg"; + let form = DomQuery.byId(FORM_ID); + const submit_spy = Sinon.spy(() => {}); + (form.value.value as any).submit = submit_spy; + + submitForm(FORM_ID, null, null, null); + + expect(submit_spy.called).to.eq(true); + }); + // further tests will follow if needed, for now the namespace must be restored }); diff --git a/api/src/client/typescript/faces/test/queue/AsynchronousQueueTest.spec.ts b/api/src/client/typescript/faces/test/queue/AsynchronousQueueTest.spec.ts index 3bfbfb883f..e3fd3f1d92 100644 --- a/api/src/client/typescript/faces/test/queue/AsynchronousQueueTest.spec.ts +++ b/api/src/client/typescript/faces/test/queue/AsynchronousQueueTest.spec.ts @@ -25,7 +25,7 @@ import {StandardInits} from "../frameworkBase/_ext/shared/StandardInits"; import {Implementation} from "../../impl/AjaxImpl"; const defaultMyFaces = StandardInits.defaultMyFaces; import {XhrQueueController} from "../../impl/util/XhrQueueController"; -import {IAsyncRunnable} from "../../impl/util/AsyncRunnable"; +import {AsyncRunnable, IAsyncRunnable} from "../../impl/util/AsyncRunnable"; class ControlledRunnable implements IAsyncRunnable { started = 0; @@ -247,3 +247,51 @@ describe('Asynchronous Queue tests', () => { } }); }); + +class SimpleRunnable extends AsyncRunnable { + private _shouldResolve: boolean; + constructor(resolve = true) { super(); this._shouldResolve = resolve; } + cancel() {} + start() { + if (this._shouldResolve) this.resolve(true); + else this.reject(new Error("rejected")); + } +} + +describe('AsyncRunnable.finally() tests', () => { + it('finally callback must be called on resolve', function () { + const r = new SimpleRunnable(true); + let finallyCalled = false; + r.finally(() => { finallyCalled = true; }); + r.start(); + expect(finallyCalled).to.be.true; + }); + + it('finally callback must be called on reject', function () { + const r = new SimpleRunnable(false); + let finallyCalled = false; + r.catch(() => {}).finally(() => { finallyCalled = true; }); + r.start(); + expect(finallyCalled).to.be.true; + }); + + it('finally callback must be called in addition to then', function () { + const r = new SimpleRunnable(true); + let thenCalled = false; + let finallyCalled = false; + r.then(() => { thenCalled = true; }).finally(() => { finallyCalled = true; }); + r.start(); + expect(thenCalled).to.be.true; + expect(finallyCalled).to.be.true; + }); + + it('finally callback must be called in addition to catch', function () { + const r = new SimpleRunnable(false); + let catchCalled = false; + let finallyCalled = false; + r.catch(() => { catchCalled = true; }).finally(() => { finallyCalled = true; }); + r.start(); + expect(catchCalled).to.be.true; + expect(finallyCalled).to.be.true; + }); +}); diff --git a/api/src/client/typescript/faces/test/xhrCore/RequestTest.spec.ts b/api/src/client/typescript/faces/test/xhrCore/RequestTest.spec.ts index a062347659..07855e1a55 100644 --- a/api/src/client/typescript/faces/test/xhrCore/RequestTest.spec.ts +++ b/api/src/client/typescript/faces/test/xhrCore/RequestTest.spec.ts @@ -18,7 +18,7 @@ import {describe, it} from "mocha"; import * as sinon from "sinon"; import {expect} from "chai"; import {StandardInits} from "../frameworkBase/_ext/shared/StandardInits"; -import {_Es2019Array, DomQuery, DQ$} from "mona-dish"; +import {_Es2019Array, Config, DomQuery, DQ$} from "mona-dish"; import { COMPLETE, EMPTY_STR, EMPTY_RESPONSE, @@ -31,8 +31,17 @@ import { P_VIEWSTATE, P_WINDOW_ID, STATE_EVT_TIMEOUT, - SUCCESS + SUCCESS, + CTX_PARAM_SRC_FRM_ID, + CTX_PARAM_REQ_PASS_THR, + CTX_PARAM_PPS, + IDENT_NONE, + REQ_TYPE_GET, + SOURCE, } from "../../impl/core/Const"; +import {XhrRequest} from "../../impl/xhrCore/XhrRequest"; +import {ExtConfig} from "../../impl/util/ExtDomQuery"; +import {EventData} from "../../impl/xhrCore/EventData"; const defaultMyFaces = StandardInits.defaultMyFaces; const initVirtualElement = StandardInits.initVirtualElement; const STD_XML = StandardInits.STD_XML; @@ -904,6 +913,33 @@ describe('Tests after core when it hits response', function () { }); }); + it("must include a checked checkbox issuing item via appendIssuingItem when not already in form data", () => { + // Use a form where name != id so the checkbox is NOT pre-encoded under the issuing item ID + const waitForResult = initCheckboxForm(); + return waitForResult.then(() => { + document.body.innerHTML += ` +
+ + +
`; + + const send = sinon.spy(XMLHttpRequest.prototype, "send"); + try { + const element = document.getElementById("cb-issuing-item"); + faces.ajax.request(element, null, { + execute: "@none", + render: "@none" + }); + + const body: string = send.args[0][0] as string; + // appendIssuingItem must have added the issuing element keyed by its ID + expect(body).to.include("cb-issuing-item"); + } finally { + send.restore(); + } + }); + }); + /** * https://issues.apache.org/jira/browse/MYFACES-4638 */ @@ -1165,3 +1201,79 @@ describe('XhrRequest error handling and lifecycle', function () { }, 0); }); }); + +describe('XhrRequest GET method', function () { + + beforeEach(async function () { + return defaultMyFaces().then((close) => { + this.xhr = nise.fakeXhr.useFakeXMLHttpRequest(); + this.requests = []; + this.xhr.onCreate = (xhr: any) => this.requests.push(xhr); + (global as any).XMLHttpRequest = this.xhr; + window.XMLHttpRequest = this.xhr; + this.closeIt = () => { + (global as any).XMLHttpRequest = window.XMLHttpRequest = this.xhr.restore(); + close(); + }; + }); + }); + + afterEach(function () { this.closeIt(); }); + + it('must call send(null) for a GET request', function () { + const send = sinon.spy(XMLHttpRequest.prototype, "send"); + try { + const internalCtx = new Config({}); + internalCtx.assign(CTX_PARAM_SRC_FRM_ID).value = "blarg"; + internalCtx.assign(CTX_PARAM_PPS).value = false; + + const reqCtx = new ExtConfig({}); + reqCtx.assign(CTX_PARAM_REQ_PASS_THR, P_EXECUTE).value = IDENT_NONE; + + const request = new XhrRequest(reqCtx, internalCtx, 0, REQ_TYPE_GET); + request.start(); + + expect(this.requests[0].method).to.eq("GET"); + expect(send.calledOnce).to.be.true; + expect(send.firstCall.args[0]).to.be.null; + } finally { + send.restore(); + } + }); +}); + +describe('EventData source resolution fallback', function () { + + beforeEach(async function () { + return defaultMyFaces().then((close) => { + this.closeIt = close; + }); + }); + + afterEach(function () { this.closeIt(); }); + + it('must resolve source from context when _source._element is absent', function () { + const fakeXhr = { status: 200, responseText: "", responseXML: null } as any; + const internalCtx = new Config({}); + + const reqCtx = new ExtConfig({}); + reqCtx.assign(SOURCE).value = "input_2"; + + const event = EventData.createFromRequest(fakeXhr, internalCtx, reqCtx, "begin"); + + expect(event.source).to.not.be.null; + expect(event.source).to.not.be.undefined; + expect((event.source as HTMLElement).id).to.eq("input_2"); + }); + + it('must store resolved source in internalContext for subsequent calls', function () { + const fakeXhr = { status: 200, responseText: "", responseXML: null } as any; + const internalCtx = new Config({}); + const reqCtx = new ExtConfig({}); + reqCtx.assign(SOURCE).value = "input_2"; + + EventData.createFromRequest(fakeXhr, internalCtx, reqCtx, "begin"); + + expect(internalCtx.getIf("_source", "_element").value).to.not.be.null; + }); +}); diff --git a/api/src/client/typescript/faces/test/xhrCore/WebsocketTest.spec.ts b/api/src/client/typescript/faces/test/xhrCore/WebsocketTest.spec.ts index 7c9d844893..392194a5c0 100644 --- a/api/src/client/typescript/faces/test/xhrCore/WebsocketTest.spec.ts +++ b/api/src/client/typescript/faces/test/xhrCore/WebsocketTest.spec.ts @@ -722,4 +722,43 @@ describe('Tests the jsf websocket client side api on high level (generic test wi done(); }); }); + + it("must close the socket when all components are gone after an error-close", function () { + const channelToken = "booga.ws"; + + faces.push.init("clientId_close_test", "booga.ws", "mychannel", + () => {}, + () => {}, + () => {}, + () => {}, + "", + true + ); + + // After push.init with autoConnect=true, Socket.open() has already assigned + // its handler to fakeWebsocket.onopen. Invoking it directly marks the socket + // as hasEverConnected=true, which is required for the subsequent abnormal close + // to take the reconnectable (non-terminal) path. + this.fakeWebsocket.onopen({}); + + // Remove the component from the registry so the channel has no valid + // components. notifyErrorAndPruneMissingComponents will splice the + // clientId out, leaving the array empty, then closeIfChannelHasNoComponents + // detects this and closes the socket. + delete this.pushImpl.components["clientId_close_test"]; + + const closeSpy = sinon.spy(this.fakeWebsocket, "close"); + try { + // Abnormal close (1006) is reconnectable — goes through the + // notifyErrorAndPruneMissingComponents → closeIfChannelHasNoComponents path. + this.fakeWebsocket._close({code: 1006}); + + expect(this.pushImpl.clientIdsByTokens[channelToken].length, + "clientIds array must be empty after stale component pruning").to.eq(0); + expect(closeSpy.called || this.fakeWebsocket.readyState === 3, + "socket must be closed when channel has no remaining components").to.be.true; + } finally { + closeSpy.restore(); + } + }); }); diff --git a/api/src/client/typescript/mona_dish/DomQuery.ts b/api/src/client/typescript/mona_dish/DomQuery.ts index bad1d05081..c2dccae1f4 100644 --- a/api/src/client/typescript/mona_dish/DomQuery.ts +++ b/api/src/client/typescript/mona_dish/DomQuery.ts @@ -160,8 +160,9 @@ function waitUntilDom(root: DomQuery, condition: (element: DomQuery) => boolean, observer!.observe(item, observableOpts) }) } else { // fallback for legacy browsers without mutation observer - - let interval: any = setInterval(() => { + let interval: any; + let timeout: any; + interval = setInterval(() => { let found = findElement(root, condition); if (!!found) { if (timeout) { @@ -172,13 +173,12 @@ function waitUntilDom(root: DomQuery, condition: (element: DomQuery) => boolean, success(new DomQuery(found || root)); } }, options.interval); - let timeout = setTimeout(() => { + timeout = setTimeout(() => { if (interval) { clearInterval(interval); error(MUT_ERROR); } - }, options.timeout) - + }, options.timeout); } }); } @@ -202,7 +202,6 @@ export class ElementAttribute extends ValueEmbedder { for (let cnt = 0; cnt < val.length; cnt++) { val[cnt].setAttribute(this.name, value); } - val[0].setAttribute(this.name, value); } protected getClass(): any { @@ -237,11 +236,11 @@ export class Style extends ValueEmbedder { } protected getClass(): any { - return ElementAttribute; + return Style; } static fromNullable(value?: any, valueKey: string = "value"): ElementAttribute { - return new ElementAttribute(value, valueKey); + return new Style(value, valueKey); } } @@ -434,11 +433,11 @@ export class DomQuery implements IDomQuery, IStreamDataSource, Iterabl get elements(): DomQuery { // a simple querySelectorAll should suffice - return this.querySelectorAll("input, checkbox, select, textarea, fieldset"); + return this.querySelectorAll("input, select, textarea, fieldset"); } get deepElements(): DomQuery { - let elemStr = "input, select, textarea, checkbox, fieldset"; + let elemStr = "input, select, textarea, fieldset"; return this.querySelectorAllDeep(elemStr); } @@ -602,7 +601,6 @@ export class DomQuery implements IDomQuery, IStreamDataSource, Iterabl */ static fromMarkup(markup: string): DomQuery { - // https:// developer.mozilla.org/de/docs/Web/API/DOMParser license creative commons const doc = document.implementation.createHTMLDocument(""); markup = trim(markup); let lowerMarkup = markup.toLowerCase(); @@ -622,16 +620,15 @@ export class DomQuery implements IDomQuery, IStreamDataSource, Iterabl let dummyPlaceHolder = new DomQuery(document.createElement("div")); // table needs special treatment due to the browsers auto creation - if (startsWithTag(lowerMarkup, "thead") || startsWithTag(lowerMarkup, "tbody")) { + if (startsWithTag(lowerMarkup, "thead") + || startsWithTag(lowerMarkup, "tbody") + || startsWithTag(lowerMarkup, "tfoot")) { dummyPlaceHolder.html(`${markup}
`); return dummyPlaceHolder.querySelectorAll("table").get(0).childNodes.detach(); - } else if (startsWithTag(lowerMarkup, "tfoot")) { - dummyPlaceHolder.html(``); - return dummyPlaceHolder.querySelectorAll("table").get(2).childNodes.detach(); } else if (startsWithTag(lowerMarkup, "tr")) { dummyPlaceHolder.html(`
${markup}
`); return dummyPlaceHolder.querySelectorAll("tbody").get(0).childNodes.detach(); - } else if (startsWithTag(lowerMarkup, "td")) { + } else if (startsWithTag(lowerMarkup, "td") || startsWithTag(lowerMarkup, "th")) { dummyPlaceHolder.html(`${markup}
`); return dummyPlaceHolder.querySelectorAll("tr").get(0).childNodes.detach(); } @@ -981,15 +978,15 @@ export class DomQuery implements IDomQuery, IStreamDataSource, Iterabl } firstElem(func: (item: Element, cnt?: number) => any = item => item): DomQuery { - if (this.rootNode.length > 1) { + if (this.rootNode.length > 0) { func(this.rootNode[0], 0); } return this; } lastElem(func: (item: Element, cnt?: number) => any = item => item): DomQuery { - if (this.rootNode.length > 1) { - func(this.rootNode[this.rootNode.length - 1], 0); + if (this.rootNode.length > 0) { + func(this.rootNode[this.rootNode.length - 1], this.rootNode.length - 1); } return this; } @@ -1922,7 +1919,7 @@ export class DomQuery implements IDomQuery, IStreamDataSource, Iterabl ctrl?.focus ? ctrl?.focus() : null; // the selection range is our caret position - ctrl?.setSelectiongRange ? ctrl?.setSelectiongRange(pos, pos) : null; + ctrl?.setSelectionRange ? ctrl?.setSelectionRange(pos, pos) : null; } /** diff --git a/api/src/client/typescript/mona_dish/Lang.ts b/api/src/client/typescript/mona_dish/Lang.ts index bbc9c2bc0b..9329f36ce2 100644 --- a/api/src/client/typescript/mona_dish/Lang.ts +++ b/api/src/client/typescript/mona_dish/Lang.ts @@ -161,8 +161,6 @@ export namespace Lang { return it instanceof Function || typeof it === "function"; } - // code from https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Object/assign - // license https://creativecommons.org/licenses/by-sa/2.5/ export function objAssign(target: any, ...theArgs: any[]) { // .length of function is 2 if (target == null) { // TypeError if undefined or null throw new TypeError('Cannot convert undefined or null to object'); @@ -176,9 +174,11 @@ export namespace Lang { theArgs.filter(item => item != null).forEach(item => { let nextSource = item; - Object.keys(nextSource) - .filter(nextKey => Object.prototype.hasOwnProperty.call(nextSource, nextKey)) - .forEach(nextKey => to[nextKey] = nextSource[nextKey]); + const stringKeys = Object.keys(nextSource); + const symbolKeys = Object.getOwnPropertySymbols(nextSource) + .filter(sym => Object.prototype.propertyIsEnumerable.call(nextSource, sym)); + [...stringKeys, ...symbolKeys] + .forEach(key => to[key] = nextSource[key]); }); return to; } diff --git a/api/src/client/typescript/node_build/copysources.ts b/api/src/client/typescript/node_build/copysources.ts index 31e1da82c4..2e7ef7ac54 100644 --- a/api/src/client/typescript/node_build/copysources.ts +++ b/api/src/client/typescript/node_build/copysources.ts @@ -55,5 +55,6 @@ copyFilesRecursively('./node_modules/jsf.js_next_gen/src/main/typescript/myfaces copyFilesRecursively('./node_modules/jsf.js_next_gen/src/main/typescript/test', './typescript/faces/test'); copyFilesRecursively('./node_modules/jsf.js_next_gen/src/main/typescript/@types', './typescript/faces/@types'); copyFilesRecursively('./node_modules/mona-dish/src/main/typescript', './typescript/mona_dish'); +copyFilesRecursively('./node_modules/jsf.js_next_gen/dist/window/faces.d.ts', './typescript/faces/@types/definitions/faces.d.ts'); diff --git a/api/src/client/webpack.config.ts b/api/src/client/webpack.config.ts index 4b49793285..1592104df9 100644 --- a/api/src/client/webpack.config.ts +++ b/api/src/client/webpack.config.ts @@ -18,6 +18,7 @@ import * as webpack from 'webpack'; import * as path from 'path' let CompressionPlugin = require('compression-webpack-plugin'); +let CopyPlugin = require('copy-webpack-plugin'); /** * we need to define the export in a function @@ -53,6 +54,16 @@ function build(env: {[key:string]: string}, argv: {[key:string]: string}) { "rxjs": "RxJS" }, + plugins: [ + new CopyPlugin({ + patterns: [ + { + from: path.resolve(__dirname, './node_modules/jsf.js_next_gen/dist/window/faces.d.ts'), + to: path.resolve(__dirname, '../../target/classes/META-INF/resources/jakarta.faces/faces.d.ts') + } + ] + }) + ], module: { rules: [ // all files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'