From b8b9584662214a4951f32dc50392177f396a888b Mon Sep 17 00:00:00 2001 From: Christian Westgaard Date: Tue, 12 Nov 2024 10:27:47 +0100 Subject: [PATCH] wip #1500 DataFetcher test --- build.gradle | 1 + jest.config.ts | 141 ++ package-lock.json | 1398 ++++++++++++++++- package.json | 6 + src/jest/server/DataFetcher.test.ts | 365 +++++ src/jest/server/constants.ts | 3 + src/jest/server/data.ts | 256 +++ src/jest/server/global.d.ts | 7 + src/jest/server/mockXP.ts | 125 ++ src/jest/server/replaceMacroComments.test.ts | 53 + src/jest/server/schema.ts | 247 +++ src/jest/server/setupFile.ts | 19 + src/jest/server/tsconfig.json | 30 + .../lib/enonic/react4xp/DataFetcher.ts | 635 ++++++++ .../resources/lib/enonic/react4xp/index.ts | 1 + .../enonic/react4xp/replaceMacroComments.ts | 65 + tsconfig.json | 28 +- 17 files changed, 3332 insertions(+), 48 deletions(-) create mode 100644 jest.config.ts create mode 100644 src/jest/server/DataFetcher.test.ts create mode 100644 src/jest/server/constants.ts create mode 100644 src/jest/server/data.ts create mode 100644 src/jest/server/global.d.ts create mode 100644 src/jest/server/mockXP.ts create mode 100644 src/jest/server/replaceMacroComments.test.ts create mode 100644 src/jest/server/schema.ts create mode 100644 src/jest/server/setupFile.ts create mode 100644 src/jest/server/tsconfig.json create mode 100644 src/main/resources/lib/enonic/react4xp/DataFetcher.ts create mode 100644 src/main/resources/lib/enonic/react4xp/replaceMacroComments.ts diff --git a/build.gradle b/build.gradle index 958d710c..09415b4b 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,7 @@ dependencies { implementation "com.enonic.xp:lib-io:${xpVersion}" implementation "com.enonic.xp:lib-portal:${xpVersion}" + implementation "com.enonic.xp:lib-schema:${xpVersion}" implementation "com.enonic.xp:lib-task:${xpVersion}" implementation 'com.enonic.lib:lib-cache:2.2.1' diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 00000000..02575604 --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,141 @@ +import type { Config } from '@jest/types'; + + +const DIR_SRC = 'src/main/resources'; +const DIR_SRC_JEST = 'src/jest'; +const DIR_SRC_JEST_CLIENT = `${DIR_SRC_JEST}/client`; +const DIR_SRC_JEST_SERVER = `${DIR_SRC_JEST}/server`; +const AND_BELOW = '**'; +const SOURCE_FILES = `*.{ts,tsx}`; +const TEST_EXT = `{spec,test}.{ts,tsx}`; +const TEST_FILES = `*.${TEST_EXT}`; + + +const commonConfig: Config.InitialProjectOptions = { + collectCoverageFrom: [ + `${DIR_SRC}/${AND_BELOW}/${SOURCE_FILES}`, + ], + + // Insert Jest's globals (expect, test, describe, beforeEach etc.) into the + // global environment. If you set this to false, you should import from @jest/globals, e.g. + // injectGlobals: true, // Doesn't seem to work? +}; + +const clientSideConfig: Config.InitialProjectOptions = { + ...commonConfig, + displayName: { + color: 'white', + name: 'CLIENT', + }, + + // A map from regular expressions to module names or to arrays of module + // names that allow to stub out resources, like images or styles with a + // single module. + // Use string token to refer to rootDir value if you want to use + // file paths. + // Additionally, you can substitute captured regex groups using numbered + // backreferences. + moduleNameMapper: { + '/assets/(.*)': `/${DIR_SRC}/assets/$1`, + }, + + // Run clientside tests with DOM globals such as document and window + testEnvironment: 'jsdom', + + // The glob patterns Jest uses to detect test files. By default it looks for + // .js, .jsx, .ts and .tsx files inside of __tests__ folders, as well as any + // files with a suffix of .test or .spec (e.g. Component.test.js or + // Component.spec.js). It will also find files called test.js or spec.js. + // (default: [ + // "**/__tests__/**/*.[jt]s?(x)", + // "**/?(*.)+(spec|test).[jt]s?(x)" + // ]) + testMatch: [ + `/${DIR_SRC_JEST_CLIENT}/${AND_BELOW}/${TEST_FILES}`, + ], + transform: { + "^.+\\.(ts|js)x?$": [ + 'ts-jest', + { + tsconfig: `${DIR_SRC_JEST_CLIENT}/tsconfig.json` + } + ] + } +}; + +const serverSideConfig: Config.InitialProjectOptions = { + ...commonConfig, + displayName: { + color: 'blue', + name: 'SERVER', + }, + + // A set of global variables that need to be available in all test + // environments. + // If you specify a global reference value (like an object or array) here, + // and some code mutates that value in the midst of running a test, that + // mutation will not be persisted across test runs for other test files. + // In addition, the globals object must be json-serializable, so it can't be + // used to specify global functions. For that, you should use setupFiles. + globals: { + app: { + name: 'com.example.myproject', + config: {}, + version: '1.0.0' + }, + }, + + // A map from regular expressions to module names or to arrays of module + // names that allow to stub out resources, like images or styles with a + // single module. + // Use string token to refer to rootDir value if you want to use + // file paths. + // Additionally, you can substitute captured regex groups using numbered + // backreferences. + moduleNameMapper: { + '/lib/enonic/react4xp/(.*)': `/${DIR_SRC}/lib/enonic/react4xp/$1`, + }, + + // A list of paths to modules that run some code to configure or set up the + // testing environment. Each setupFile will be run once per test file. Since + // every test runs in its own environment, these scripts will be executed in + // the testing environment before executing setupFilesAfterEnv and before + // the test code itself. + setupFiles: [ + `/${DIR_SRC_JEST_SERVER}/setupFile.ts`, + `/${DIR_SRC_JEST_SERVER}/mockXP.ts` + ], + + // Run serverside tests without DOM globals such as document and window + testEnvironment: 'node', + // testEnvironment: 'jsdom', // "Graal" + + // The glob patterns Jest uses to detect test files. By default it looks for + // .js, .jsx, .ts and .tsx files inside of __tests__ folders, as well as any + // files with a suffix of .test or .spec (e.g. Component.test.js or + // Component.spec.js). It will also find files called test.js or spec.js. + // (default: [ + // "**/__tests__/**/*.[jt]s?(x)", + // "**/?(*.)+(spec|test).[jt]s?(x)" + // ]) + testMatch: [ + `/${DIR_SRC_JEST_SERVER}/${AND_BELOW}/${TEST_FILES}`, + ], + + transform: { + "^.+\\.(ts|js)x?$": [ + 'ts-jest', + { + tsconfig: `${DIR_SRC_JEST_SERVER}/tsconfig.json` + } + ] + }, + }; + + const customJestConfig: Config.InitialOptions = { + coverageProvider: 'v8', // To get correct line numbers under jsdom + passWithNoTests: true, + projects: [clientSideConfig, serverSideConfig], +}; + +export default customJestConfig; diff --git a/package-lock.json b/package-lock.json index ab575efe..9fda7462 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,8 +23,10 @@ "@enonic-types/lib-io": "^7.14.4", "@enonic-types/lib-node": "^7.14.4", "@enonic-types/lib-portal": "^7.14.4", + "@enonic-types/lib-schema": "^7.14.4", "@enonic-types/lib-static": "^2.0.0", "@enonic/mock-xp": "^1.0.0", + "@enonic/react-components": "file:../npm-react-components", "@enonic/react4xp": "^5.0.4", "@jest/globals": "^29.7.0", "@mrhenry/core-web": "^1.2.4", @@ -36,6 +38,7 @@ "@sinonjs/text-encoding": "^0.7.3", "@swc/core": "^1.8.0", "@swc/helpers": "^0.5.13", + "@testing-library/react": "^16.0.1", "@types/bun": "^1.1.13", "@types/core-js": "^2.5.8", "@types/react": "^18", @@ -44,10 +47,12 @@ "babel-loader": "^9.2.1", "bun": "^1.1.34", "core-js-pure": "^3.39.0", + "diffable-html": "^5.0.0", "esbuild-plugin-manifest": "^1.0.5", "eslint": "^9.14.0", "eslint-formatter-pretty": "^6.0.1", "glob": "^11", + "jest-environment-jsdom": "^29.7.0", "lodash": "^4.17.21", "make-dir-cli": "^4.0.0", "npm-run-all": "^4.1.5", @@ -56,6 +61,7 @@ "rollup-plugin-esbuild": "^6.1.1", "rollup-plugin-output-manifest": "^2.0.0", "rollup-plugin-swc3": "^0.12.1", + "ts-jest": "^29.2.5", "ts-node": "^10.9.2", "tslib": "^2.8.1", "tsup": "^8.3.5", @@ -2134,6 +2140,16 @@ "@enonic-types/core": "7.14.4" } }, + "node_modules/@enonic-types/lib-schema": { + "version": "7.14.4", + "resolved": "https://registry.npmjs.org/@enonic-types/lib-schema/-/lib-schema-7.14.4.tgz", + "integrity": "sha512-znXDWn0C/w4Dq8r1voe7yoMGKlVX+8Fr5+0r9H6M7Bdt7bAEe1fegqT+XW/GaBr0eUyh5ParPto03alLlmulyA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@enonic-types/core": "7.14.4" + } + }, "node_modules/@enonic-types/lib-static": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@enonic-types/lib-static/-/lib-static-2.0.0.tgz", @@ -2171,6 +2187,26 @@ "node": ">=20.11.1" } }, + "node_modules/@enonic/react-components": { + "version": "0.0.0", + "resolved": "file:../npm-react-components", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@enonic/js-utils": "^1.8.0", + "clsx": "^2.1.1", + "domelementtype": "^2.3.0", + "html-react-parser": "^5.1.10", + "uri-js": "^4.4.1" + }, + "peerDependencies": { + "@enonic-types/core": "^7", + "@enonic-types/lib-portal": "^7", + "@enonic-types/lib-schema": "^7", + "prop-types": "*", + "react": "*" + } + }, "node_modules/@enonic/react4xp": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@enonic/react4xp/-/react4xp-5.0.4.tgz", @@ -5188,6 +5224,185 @@ "@swc/counter": "^0.1.3" } }, + "node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@testing-library/dom/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@testing-library/dom/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/dom/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@testing-library/dom/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@testing-library/dom/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/react": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.0.1.tgz", + "integrity": "sha512-dSmwJVtJXmku+iocRhWOUFbrERC76TX2Mnf0ATODz8brzAZrMBbzLwQixlBSanZxR6LddK3eiwpSFZgDET1URg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -5221,6 +5436,14 @@ "@types/readdir-glob": "*" } }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@types/babel__core": { "version": "7.20.1", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", @@ -5326,6 +5549,18 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jsdom": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -5401,6 +5636,13 @@ "integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==", "dev": true }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/uglify-js": { "version": "3.17.2", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.2.tgz", @@ -5919,6 +6161,14 @@ "dev": true, "peer": true }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", @@ -5931,6 +6181,17 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, "node_modules/acorn-import-attributes": { "version": "1.9.5", "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", @@ -5959,6 +6220,19 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -6210,6 +6484,17 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, "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", @@ -6259,6 +6544,13 @@ "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", "dev": true }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -6719,6 +7011,19 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -7062,6 +7367,16 @@ "node": ">=6" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -7109,6 +7424,19 @@ "node": ">=0.1.90" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -7361,6 +7689,33 @@ "node": ">= 8" } }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true, + "license": "MIT" + }, "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", @@ -7380,35 +7735,94 @@ "node": ">= 12" } }, - "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=12" } }, - "node_modules/dedent": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", - "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "node_modules/data-urls/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", "dev": true, - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1" }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dedent": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } } }, "node_modules/deep-freeze": { @@ -7514,6 +7928,27 @@ "node": ">=8" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -7541,6 +7976,100 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/diffable-html": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diffable-html/-/diffable-html-5.0.0.tgz", + "integrity": "sha512-BymfWdoIv53XDp/sINPyngxiyJr7ygmAoUN7nIlPC7E+jiLvPdVIZteG4btCaB5Nyf8CMyMxp8r4Vi2r1FtSsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "htmlparser2": "^3.9.2" + } + }, + "node_modules/diffable-html/node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/diffable-html/node_modules/dom-serializer/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/diffable-html/node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/diffable-html/node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/diffable-html/node_modules/domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/diffable-html/node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/diffable-html/node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/diffable-html/node_modules/htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -7553,12 +8082,119 @@ "node": ">=8" } }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "license": "MIT", + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/domexception/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.32", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.32.tgz", @@ -7615,6 +8251,19 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/envinfo": { "version": "7.10.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz", @@ -8154,6 +8803,28 @@ "node": ">=0.8.0" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, "node_modules/eslint": { "version": "9.14.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.14.0.tgz", @@ -8742,6 +9413,39 @@ "node": ">=16.0.0" } }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/filemanager-webpack-plugin": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/filemanager-webpack-plugin/-/filemanager-webpack-plugin-8.0.0.tgz", @@ -8856,6 +9560,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -9276,12 +9995,107 @@ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, + "node_modules/html-dom-parser": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/html-dom-parser/-/html-dom-parser-5.0.10.tgz", + "integrity": "sha512-GwArYL3V3V8yU/mLKoFF7HlLBv80BZ2Ey1BzfVNRpAci0cEKhFHI/Qh8o8oyt3qlAMLlK250wsxLdYX4viedvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domhandler": "5.0.3", + "htmlparser2": "9.1.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/html-react-parser": { + "version": "5.1.18", + "resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-5.1.18.tgz", + "integrity": "sha512-65BwC0zzrdeW96jB2FRr5f1ovBhRMpLPJNvwkY5kA8Ay5xdL9t/RH2/uUTM7p+cl5iM88i6dDk4LXtfMnRmaJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "domhandler": "5.0.3", + "html-dom-parser": "5.0.10", + "react-property": "2.0.2", + "style-to-js": "1.1.16" + }, + "peerDependencies": { + "@types/react": "0.14 || 15 || 16 || 17 || 18", + "react": "0.14 || 15 || 16 || 17 || 18" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -9474,6 +10288,13 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/internal-slot": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", @@ -9732,6 +10553,13 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-reference": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", @@ -10021,16 +10849,111 @@ "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", "dev": true, "dependencies": { - "@isaacs/cliui": "^8.0.2" + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jake/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jake/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jake/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/jake/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jake/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" }, "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "node": ">=8" } }, "node_modules/jest": { @@ -10631,6 +11554,34 @@ "node": ">=8" } }, + "node_modules/jest-environment-jsdom": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", + "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0", + "jsdom": "^20.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/jest-environment-node": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", @@ -11842,6 +12793,89 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/jsesc": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", @@ -12191,6 +13225,13 @@ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "dev": true }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -12258,6 +13299,17 @@ "yallist": "^3.0.2" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.30.5", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", @@ -12427,7 +13479,6 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, - "peer": true, "engines": { "node": ">= 0.6" } @@ -12437,7 +13488,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, - "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -12736,6 +13786,13 @@ "node": ">=8" } }, + "node_modules/nwsapi": { + "version": "2.2.13", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.13.tgz", + "integrity": "sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==", + "dev": true, + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -12907,6 +13964,19 @@ "node": ">=4" } }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -13247,11 +14317,22 @@ "node": ">=0.10" } }, + "node_modules/psl": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.10.0.tgz", + "integrity": "sha512-KSKHEbjAnpUuAUserOq0FxGXCUrzC3WniuSJhvdbs102rL55266ZcHBqLWOsG30spQMlPdpy7icATiAQehg/iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + } + }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -13286,6 +14367,13 @@ "node": ">=4" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -13364,6 +14452,13 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, + "node_modules/react-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/react-property/-/react-property-2.0.2.tgz", + "integrity": "sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==", + "dev": true, + "license": "MIT" + }, "node_modules/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -13585,6 +14680,13 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.4", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", @@ -13940,6 +15042,19 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -14401,6 +15516,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-to-js": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz", + "integrity": "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.8" + } + }, + "node_modules/style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -14572,6 +15707,13 @@ "webpack": ">=2" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -14788,6 +15930,32 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/tr46": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", @@ -14824,11 +15992,74 @@ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "dev": true }, + "node_modules/ts-jest": { + "version": "29.2.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", + "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.6.3", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, + "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -15325,6 +16556,17 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -15361,6 +16603,19 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -15574,6 +16829,42 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/whatwg-url": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", @@ -15797,6 +17088,45 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/xxhashjs": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", diff --git a/package.json b/package.json index 233c548d..0ae72253 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,10 @@ "@enonic-types/lib-io": "^7.14.4", "@enonic-types/lib-node": "^7.14.4", "@enonic-types/lib-portal": "^7.14.4", + "@enonic-types/lib-schema": "^7.14.4", "@enonic-types/lib-static": "^2.0.0", "@enonic/mock-xp": "^1.0.0", + "@enonic/react-components": "file:../npm-react-components", "@enonic/react4xp": "^5.0.4", "@jest/globals": "^29.7.0", "@mrhenry/core-web": "^1.2.4", @@ -43,6 +45,7 @@ "@sinonjs/text-encoding": "^0.7.3", "@swc/core": "^1.8.0", "@swc/helpers": "^0.5.13", + "@testing-library/react": "^16.0.1", "@types/bun": "^1.1.13", "@types/core-js": "^2.5.8", "@types/react": "^18", @@ -51,10 +54,12 @@ "babel-loader": "^9.2.1", "bun": "^1.1.34", "core-js-pure": "^3.39.0", + "diffable-html": "^5.0.0", "esbuild-plugin-manifest": "^1.0.5", "eslint": "^9.14.0", "eslint-formatter-pretty": "^6.0.1", "glob": "^11", + "jest-environment-jsdom": "^29.7.0", "lodash": "^4.17.21", "make-dir-cli": "^4.0.0", "npm-run-all": "^4.1.5", @@ -63,6 +68,7 @@ "rollup-plugin-esbuild": "^6.1.1", "rollup-plugin-output-manifest": "^2.0.0", "rollup-plugin-swc3": "^0.12.1", + "ts-jest": "^29.2.5", "ts-node": "^10.9.2", "tslib": "^2.8.1", "tsup": "^8.3.5", diff --git a/src/jest/server/DataFetcher.test.ts b/src/jest/server/DataFetcher.test.ts new file mode 100644 index 00000000..bcac08ad --- /dev/null +++ b/src/jest/server/DataFetcher.test.ts @@ -0,0 +1,365 @@ +import type { + Component, + // Part, + // PartComponent, + Request, +} from '@enonic-types/core'; +// import type { +// ListDynamicSchemasParams, +// MixinSchema, +// } from '@enonic-types/lib-schema'; +// import type {RegionsProps} from '../../src/Regions'; +// import type {MacroComponent} from '../../src/types'; +// import type {InfoPanelProps} from './InfoPanel'; +import type { + // DecoratedLayoutComponent, + DecoratedPageComponent, + // DecoratedPartComponent +} from '@enonic/react-components'; + +// import { +// ComponentRegistry, +// XpComponent, +// // XpRegion +// } from '@enonic/react-components'; +import { + // beforeAll, + // afterAll, + describe, + expect, + test as it +} from '@jest/globals'; +// import {render} from '@testing-library/react' +// import toDiffableHtml from 'diffable-html'; +// import {print, stringify} from 'q-i'; +// import * as React from 'react'; + +//────────────────────────────────────────────────────────────────────────────── +// SRC imports +//────────────────────────────────────────────────────────────────────────────── +// import Regions from '../../src/Regions'; +// import Page from '../../src/Page'; +// import {RichText} from '../../src/RichText'; +// import {replaceMacroComments} from '../../src/replaceMacroComments'; +import { + DataFetcher, + // processComponents, +} from '/lib/enonic/react4xp/DataFetcher'; +// import {InfoPanel} from './InfoPanel'; + +//────────────────────────────────────────────────────────────────────────────── +// TEST imports +//────────────────────────────────────────────────────────────────────────────── +import { + DEFAULT_PAGE_DESCRIPTOR, + EXAMPLE_PART_DESCRIPTOR, + LAYOUT_COMPONENT, + LAYOUT_FRAGMENT_CONTENT_ID, + LAYOUT_FRAGMENT_CONTENT, + PAGE_COMPONENT, + PAGE_CONTENT, + PART_COMPONENT, + PART_FRAGMENT_CONTENT_ID, + PART_FRAGMENT_CONTENT, + PROCESSED_HTML, + TEXT_FRAGMENT_CONTENT_ID, + TEXT_FRAGMENT_CONTENT, + TWO_COLUMNS_LAYOUT_DESCRIPTOR, +} from './data'; +import { + LAYOUT_SCHEMA, + MIXIN_SCHEMAS, + PART_SCHEMA, + PAGE_SCHEMA, +} from './schema'; +// import {DefaultPage} from './DefaultPage'; +// import {ExamplePart} from './ExamplePart'; +// import {TwoColumnLayout} from './TwoColumnLayout'; + +const dataFetcher = new DataFetcher( + // { + // getComponentSchema: ({ + // // key, + // type, + // }) => { + // if (type === 'PART') return PART_SCHEMA; + // if (type === 'LAYOUT') return LAYOUT_SCHEMA; + // return PAGE_SCHEMA; + // }, + // // @ts-expect-error + // getContentByKey: ({key}) => { + // if (key === LAYOUT_FRAGMENT_CONTENT_ID) { + // return LAYOUT_FRAGMENT_CONTENT; + // } + // if (key === PART_FRAGMENT_CONTENT_ID) { + // return PART_FRAGMENT_CONTENT; + // } + // if (key === TEXT_FRAGMENT_CONTENT_ID) { + // return TEXT_FRAGMENT_CONTENT; + // } + // console.error("getContentByKey:", key); + // return undefined; + // }, + // listSchemas: ({ + // application: _application, + // type, + // }: ListDynamicSchemasParams) => { + // if (type === 'MIXIN') { + // return MIXIN_SCHEMAS as MixinSchema[]; + // } + // // ContentSchemaType[] + // // XDataSchema[] + // throw new Error(`listSchemas: type: ${type} not mocked.`); + // }, + // processHtml: ({ value }) => { + // // console.info("processHtml:", value); + // return PROCESSED_HTML; + // }, +// } +); +dataFetcher.addLayout(TWO_COLUMNS_LAYOUT_DESCRIPTOR, { + toProps: ({ + component, + content, + processedComponent, + processedConfig, + request, + }) => { + const {regions} = processedComponent; + // console.debug('layout toProps:', stringify({ + // // component, + // // content, + // processedComponent, + // processedConfig, + // // request + // })); + return { + // data: processedConfig.anHtmlArea, + regions + }; + }, +}); +dataFetcher.addPage(DEFAULT_PAGE_DESCRIPTOR, { + toProps: ({ + component, + content, + processedComponent, + processedConfig, + request, + }) => { + // console.debug('page toProps:', { + // // component, + // // content, + // processedComponent, + // processedConfig, + // // request + // }); + const {regions} = processedComponent; + return { + regions + }; + }, +}); +dataFetcher.addPart(EXAMPLE_PART_DESCRIPTOR, { + toProps: ({ + component, + content, + processedConfig, + request, + }) => { + // console.debug("part toProps:", { component, content, processedConfig, request }); + return { + data: processedConfig.anHtmlArea + }; + }, +}); + +// const componentRegistry = new ComponentRegistry; +// componentRegistry.addMacro('info', { +// View: InfoPanel +// }); +// componentRegistry.addPart(EXAMPLE_PART_DESCRIPTOR, { +// View: ExamplePart +// }); +// componentRegistry.addLayout(TWO_COLUMNS_LAYOUT_DESCRIPTOR, { +// View: TwoColumnLayout +// }); +// componentRegistry.addPage(DEFAULT_PAGE_DESCRIPTOR, { +// View: DefaultPage +// }); + +describe('processComponents', () => { + + // it('is able to process a part component', () => { +// const processedComponent = dataFetcher.process({ +// component: PART_COMPONENT as Component, +// content: PAGE_CONTENT, +// request: {} as Request, +// }); +// // print(processedComponent, { maxItems: Infinity }); +// expect(processedComponent.props).toEqual({ +// data: { +// processedHtml: '', +// macros: [ +// { +// config: { +// info: { +// header: 'Header', +// body: 'Text' +// } +// }, +// ref: '1', +// name: 'info', +// descriptor: 'whatever:info' +// } +// ] +// } +// }); +// // const element = render().container; +// // expect(toDiffableHtml(element.outerHTML)).toEqual(toDiffableHtml(` +// //
+// //
+// //
+// //
+// //
+// // +// // +// // +// // Header +// // +// // Text +// //
+// //
+// //
+// //
+// //
+// // `)); +// }); + + // it('is able to process a layout component', () => { + // const processedComponent = dataFetcher.process({ + // component: LAYOUT_COMPONENT as Component, + // content: PAGE_CONTENT, + // request: {} as Request, + // }) as DecoratedLayoutComponent; + // // print(processedComponent, { maxItems: Infinity }); + // // expect(processedComponent.props).toEqual({ + // // data: { + // // processedHtml: '', + // // macros: [ + // // { + // // config: { + // // info: { + // // header: 'Header', + // // body: 'Text' + // // } + // // }, + // // ref: '1', + // // name: 'info', + // // descriptor: 'whatever:info' + // // } + // // ] + // // } + // // }); + // const element = render().container; + // // console.debug(toDiffableHtml(element.outerHTML)); + // expect(toDiffableHtml(element.outerHTML)).toEqual(toDiffableHtml(` + //
+ //
+ //
+ //
+ //
+ //
+ // + // + // + // Header + // + // Text + //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ // `)); + // }); + + it('is able to process a page component', () => { + const decoratedPageComponent = dataFetcher.process({ + component: PAGE_COMPONENT as Component, + content: PAGE_CONTENT, + request: {} as Request, + }) as DecoratedPageComponent; + // print(decoratedPageComponent, { maxItems: Infinity }); + // expect(decoratedPageComponent.props).toEqual({ + // data: { + // processedHtml: '', + // macros: [ + // { + // config: { + // info: { + // header: 'Header', + // body: 'Text' + // } + // }, + // ref: '1', + // name: 'info', + // descriptor: 'whatever:info' + // } + // ] + // } + // }); + // const element = render().container; + // // console.debug(toDiffableHtml(element.outerHTML)); + // expect(toDiffableHtml(element.outerHTML)).toEqual(toDiffableHtml(` + //
+ //
+ //
+ //
+ //
+ //
+ // + // + // + // Header + // + // Text + //
+ //
+ //
+ //
+ //
+ //
+ // `)); + }); +}); diff --git a/src/jest/server/constants.ts b/src/jest/server/constants.ts new file mode 100644 index 00000000..dbe20a43 --- /dev/null +++ b/src/jest/server/constants.ts @@ -0,0 +1,3 @@ +export const HTML_AREA_KEY = 'anHtmlArea'; +export const ITEM_SET_KEY = 'anItemSet'; +export const OPTION_SET_KEY = 'anOptionSet'; diff --git a/src/jest/server/data.ts b/src/jest/server/data.ts new file mode 100644 index 00000000..73029e3f --- /dev/null +++ b/src/jest/server/data.ts @@ -0,0 +1,256 @@ + +import type { + Content, + LayoutComponent, + PageComponent, + PartComponent, + FragmentComponent, + TextComponent, +} from '@enonic-types/core'; +import type {PageContent} from '../../main/resources/lib/enonic/react4xp/DataFetcher'; + +import { + HTML_AREA_KEY, + ITEM_SET_KEY, + OPTION_SET_KEY, +} from './constants'; + +export const UNPROCESSED_HTML = '

[info header="Header"]Text[/info]

'; +export const PROCESSED_HTML = `

` + +export const CONFIG = { + [HTML_AREA_KEY]: UNPROCESSED_HTML, + [ITEM_SET_KEY]: { + [HTML_AREA_KEY]: UNPROCESSED_HTML, + }, + [OPTION_SET_KEY]: [ + { + hr: {}, + _selected: "hr", + }, + { + text: { + [HTML_AREA_KEY]: UNPROCESSED_HTML, + }, + _selected: "text", + }, + ], +}; + +export const TEXT_COMPONENT: TextComponent = { + path: "/main/0", + type: "text", + text: UNPROCESSED_HTML, +}; + +export const EXAMPLE_PART_DESCRIPTOR = "com.enonic.app.react4xp:example"; + +export const PART_COMPONENT: PartComponent = { + path: "/main/0/left/0", + type: "part", + descriptor: EXAMPLE_PART_DESCRIPTOR, + config: CONFIG, +}; + +export const LAYOUT_FRAGMENT_CONTENT_ID = '8c926279-39bd-4bff-a502-ffe921b95ada'; +export const PART_FRAGMENT_CONTENT_ID = '6a71fd9e-f9fc-4395-8954-1d67a5e35bf3'; +export const TEXT_FRAGMENT_CONTENT_ID = 'a641b21d-1af3-4559-a936-9e1ab71a19c4'; + +export const PART_FRAGMENT_COMPONENT: FragmentComponent = { + path: "/main/0", + type: "fragment", + fragment: PART_FRAGMENT_CONTENT_ID, +}; + +export const TEXT_FRAGMENT_COMPONENT: FragmentComponent = { + path: "/main/1", + type: "fragment", + fragment: TEXT_FRAGMENT_CONTENT_ID, +}; + +export const LAYOUT_FRAGMENT_COMPONENT: FragmentComponent = { + path: "/main/0", + type: "fragment", + fragment: LAYOUT_FRAGMENT_CONTENT_ID, +}; + +export const TWO_COLUMNS_LAYOUT_DESCRIPTOR = "com.enonic.app.react4xp:twoColumns"; + +export const LAYOUT_FRAGMENT_CONTENT = { + _id: LAYOUT_FRAGMENT_CONTENT_ID, + _name: "fragment-two-columns", + _path: "/mysite/page-with-layout-fragment-with-text-and-part-fragment/fragment-two-columns", + creator: "user:system:su", + modifier: "user:system:su", + createdTime: "2024-11-05T09:38:31.175061Z", + modifiedTime: "2024-11-05T09:38:31.280283Z", + owner: "user:system:su", + type: "portal:fragment", + displayName: "Two columns", + hasChildren: false, + valid: true, + childOrder: "modifiedtime DESC", + data: {}, + x: {}, + fragment: { + type: "layout", + descriptor: TWO_COLUMNS_LAYOUT_DESCRIPTOR, + config: { + myhtmlarea: '

[info header="Header"]Text[/info]

\n', + }, + regions: { + left: { + components: [ + { + path: "/left/0", + type: "fragment", + fragment: TEXT_FRAGMENT_CONTENT_ID, + }, + { + path: "/left/1", + type: "fragment", + fragment: PART_FRAGMENT_CONTENT_ID, + }, + ], + name: "left", + }, + }, + }, + attachments: {}, + publish: {}, +}; + +export const TEXT_FRAGMENT_CONTENT = { + _id: TEXT_FRAGMENT_CONTENT_ID, + _name: "fragment-info-header-header-text-info", + _path: "/mysite/fragment-info-header-header-text-info", + creator: "user:system:su", + modifier: "user:system:su", + createdTime: "2024-11-05T09:13:48.488533Z", + modifiedTime: "2024-11-05T09:13:48.568588Z", + owner: "user:system:su", + type: "portal:fragment", + displayName: '[info header="Header"]Text[/info]', + hasChildren: false, + valid: true, + childOrder: "modifiedtime DESC", + data: {}, + x: {}, + fragment: { + type: 'text', + text: UNPROCESSED_HTML, + }, + attachments: {}, + publish: {}, +}; + +export const PART_FRAGMENT_CONTENT = { + _id: PART_FRAGMENT_CONTENT_ID, + _name: "fragment-example", + _path: "/mysite/with-fragment/fragment-example", + creator: "user:system:su", + modifier: "user:system:su", + createdTime: "2024-10-30T08:19:49.053500Z", + modifiedTime: "2024-10-30T08:19:49.122382Z", + owner: "user:system:su", + type: "portal:fragment", + displayName: "example", + hasChildren: false, + valid: true, + childOrder: "modifiedtime DESC", + data: {}, + x: {}, + fragment: { + type: "part", + descriptor: EXAMPLE_PART_DESCRIPTOR, + config: CONFIG, + }, + attachments: {}, + publish: {}, +}; + +export const LAYOUT_COMPONENT: LayoutComponent = { + path: '/main/0', + type: 'layout', + descriptor: TWO_COLUMNS_LAYOUT_DESCRIPTOR, + config: CONFIG, + regions: { + left: { + components: [ + // TEXT_COMPONENT, + // TEXT_FRAGMENT_COMPONENT, + {...PART_COMPONENT, path: '/main/0/left/0'}, + // PART_FRAGMENT_COMPONENT, + ], + name: "left", + }, + right: { + components: [ + // { + // ...PART_FRAGMENT_COMPONENT, + // path: "/main/0/right/0", + // } + ], + name: "right", + }, + }, +}; + +export const DEFAULT_PAGE_DESCRIPTOR = "com.enonic.app.react4xp:default"; + +export const PAGE_COMPONENT: PageComponent = { + type: "page", + path: "/", + descriptor: DEFAULT_PAGE_DESCRIPTOR, + config: CONFIG, + regions: { + main: { + components: [ + // TEXT_COMPONENT, + // TEXT_FRAGMENT_COMPONENT, + // LAYOUT_COMPONENT, + {...PART_COMPONENT, path: "/main/0"}, + // LAYOUT_FRAGMENT_COMPONENT, + // { + // ...PART_FRAGMENT_COMPONENT, + // path: "/main/1", + // } + ], + name: "main", + }, + }, +}; + +export const PAGE_CONTENT: PageContent = { + _id: "3e36f69a-fa2f-4943-a812-5a2d06f22e56", + _name: "mysite", + _path: "/mysite", + creator: "user:system:su", + modifier: "user:system:su", + createdTime: "2024-10-30T08:14:10.575402Z", + modifiedTime: "2024-11-05T09:36:11.782402Z", + owner: "user:system:su", + type: "portal:site", + displayName: "mysite", + hasChildren: true, + valid: true, + childOrder: "modifiedtime DESC", + data: { + siteConfig: [ + { + applicationKey: "com.enonic.app.react4xp", + config: {}, + }, + { + applicationKey: "com.enonic.app.panelmacros", + config: { + custom: false, + }, + }, + ], + }, + x: {}, + page: PAGE_COMPONENT, + attachments: {}, + publish: {}, +}; diff --git a/src/jest/server/global.d.ts b/src/jest/server/global.d.ts new file mode 100644 index 00000000..e40df8fb --- /dev/null +++ b/src/jest/server/global.d.ts @@ -0,0 +1,7 @@ +/// + +export declare type App = typeof app; +export declare type Log = typeof log; +export declare type DoubleUnderscore = typeof __; +export declare type Require = typeof require; +export declare type Resolve = typeof resolve; diff --git a/src/jest/server/mockXP.ts b/src/jest/server/mockXP.ts new file mode 100644 index 00000000..d619157c --- /dev/null +++ b/src/jest/server/mockXP.ts @@ -0,0 +1,125 @@ +import type { + get as getContentByKeyType, +} from '@enonic-types/lib-content'; +import type { + SiteConfig, + assetUrl, + getContent as getCurrentContentType, + getSiteConfig, + imageUrl as imageUrlType, + processHtml, +} from '@enonic-types/lib-portal'; +import type { + MixinSchema, + getComponent as getComponentSchema, + listSchemas +} from '@enonic-types/lib-schema'; + +type AssetUrl = typeof assetUrl; +type ProcessHtml = typeof processHtml; +type GetSiteConfig = typeof getSiteConfig; + +import { + App, + LibContent, + LibPortal, + Request, + Server +} from '@enonic/mock-xp'; +import { + CONTENT_TYPE_PORTAL_SITE, +} from '@enonic/mock-xp/dist/constants'; +import {jest} from '@jest/globals'; + +import { + // DEFAULT_PAGE_DESCRIPTOR, + // EXAMPLE_PART_DESCRIPTOR, + // LAYOUT_COMPONENT, + // LAYOUT_FRAGMENT_CONTENT_ID, + // LAYOUT_FRAGMENT_CONTENT, + // PAGE_COMPONENT, + // PAGE_CONTENT, + // PART_COMPONENT, + // PART_FRAGMENT_CONTENT_ID, + // PART_FRAGMENT_CONTENT, + PROCESSED_HTML, + // TEXT_FRAGMENT_CONTENT_ID, + // TEXT_FRAGMENT_CONTENT, + // TWO_COLUMNS_LAYOUT_DESCRIPTOR, +} from './data'; +import { + LAYOUT_SCHEMA, + MIXIN_SCHEMAS, + PART_SCHEMA, + PAGE_SCHEMA, +} from './schema'; + +const APP_KEY = 'com.example.myproject'; +const PROJECT_NAME = 'myproject'; +const SITE_NAME = 'mysite'; + + +export const server = new Server({ + loglevel: 'debug' +}).createProject({ + projectName: PROJECT_NAME +}).setContext({ + projectName: PROJECT_NAME +}); + +const app = new App({ + key: APP_KEY +}); + +export const libContent = new LibContent({ + server +}); + +const myAppSiteConfig: SiteConfig<{ + foo: string +}> = { + applicationKey: APP_KEY, + config: { + foo: 'bar' + } +} + +const siteContent = libContent.create({ + contentType: CONTENT_TYPE_PORTAL_SITE, + data: { + siteConfig: myAppSiteConfig, + }, + name: SITE_NAME, + parentPath: '/', +}); + +export const libPortal = new LibPortal({ + app, + server +}); + +libPortal.request = new Request({ + repositoryId: server.context.repository, + path: `/admin/site/preview/${PROJECT_NAME}/draft/${SITE_NAME}` +}); + +jest.mock('/lib/xp/content', () => ({ + get: jest.fn((params) => libContent.get(params)), +}), { virtual: true }); + +jest.mock('/lib/xp/portal', () => ({ + assetUrl: jest.fn((params) => libPortal.assetUrl(params)), + getContent: jest.fn(() => libPortal.getContent()), + getSiteConfig: jest.fn(() => libPortal.getSiteConfig()), + imageUrl: jest.fn((params) => libPortal.imageUrl(params)), + processHtml: jest.fn((params) => PROCESSED_HTML), +}), { virtual: true }); + +jest.mock('/lib/xp/schema', () => ({ + getComponent: jest.fn(({type}) => { + if (type === 'PART') return PART_SCHEMA; + if (type === 'LAYOUT') return LAYOUT_SCHEMA; + return PAGE_SCHEMA; + }), + listSchemas: jest.fn(() => MIXIN_SCHEMAS as MixinSchema[]), +}), { virtual: true }); diff --git a/src/jest/server/replaceMacroComments.test.ts b/src/jest/server/replaceMacroComments.test.ts new file mode 100644 index 00000000..9a61038b --- /dev/null +++ b/src/jest/server/replaceMacroComments.test.ts @@ -0,0 +1,53 @@ +import { + // beforeAll, + // afterAll, + describe, + expect, + test as it +} from '@jest/globals'; +import toDiffableHtml from 'diffable-html'; + +import {replaceMacroComments} from '/lib/enonic/react4xp/replaceMacroComments'; + +describe('replaceMacroComments', () => { + it('should replace macro comments', () => { + const replaced = replaceMacroComments(`

+
`); + replaced.processedHtml = toDiffableHtml(replaced.processedHtml); + expect(replaced) + .toEqual({ + processedHtml: toDiffableHtml(` + + + `), + macros: [ + { + "ref": "1", + "name": "info", + "descriptor": "whatever:info", + "config": { + "info": { + "body": "Text1
\nWith
\nNewlines", + "header": "Header1" + } + } + }, + { + "ref": "2", + "name": "info", + "descriptor": "whatever:info", + "config": { + "info": { + "body": "Text2
\nWith
\nNewlines", + "header": "Header2" + } + } + } + ] + }); + }); // it +}); // describe diff --git a/src/jest/server/schema.ts b/src/jest/server/schema.ts new file mode 100644 index 00000000..5a57ead6 --- /dev/null +++ b/src/jest/server/schema.ts @@ -0,0 +1,247 @@ +import type { + // ContentSchemaType, + // FormItem, + FormItemInput, + FormItemOptionSet, + FormItemSet, + // ListDynamicSchemasParams, + MixinSchema, + // XDataSchema, +} from '@enonic-types/lib-schema'; +import type { + GetComponentReturnType, + NestedPartial +} from '../../main/resources/lib/enonic/react4xp/DataFetcher'; + +import { + HTML_AREA_KEY, + ITEM_SET_KEY, + OPTION_SET_KEY, +} from './constants'; + + +export const FORM_ITEM_INPUT: Partial = { + formItemType: "Input", + name: HTML_AREA_KEY, + label: "My HtmlArea", + maximize: true, + inputType: "HtmlArea", + occurrences: { + maximum: 1, + minimum: 0, + }, + config: {}, +}; + +export const FORM_ITEM_SET: Partial = { + formItemType: "ItemSet", + name: ITEM_SET_KEY, + label: "Contact Info", + occurrences: { + maximum: 0, + minimum: 0, + }, + items: [ + FORM_ITEM_INPUT as FormItemInput, + ], +}; + +export const FORM_ITEM_OPTION_SET: FormItemOptionSet = { + formItemType: "OptionSet", + name: OPTION_SET_KEY, + label: "Content blocks", + expanded: false, + helpText: "Create content with optional blocks", + occurrences: { + maximum: 0, + minimum: 0, + }, + selection: { + maximum: 1, + minimum: 1, + }, + options: [ + { + name: "hr", + label: "Horisontal line", + helpText: "Adds a separator between blocks", + default: false, + items: [], + }, + { + name: "text", + label: "Text", + default: false, + items: [ + // @ts-expect-error + FORM_ITEM_INPUT + ], + }, + ], +}; + +const FORM = [ + FORM_ITEM_INPUT, + FORM_ITEM_SET, + FORM_ITEM_OPTION_SET, +]; + +export const MIXIN_SCHEMAS: NestedPartial = [ + { + name: "com.enonic.app.react4xp:mymixin", + displayName: "My mixin", + displayNameI18nKey: "", + modifiedTime: "2024-11-05T07:23:42Z", + resource:` + My mixin +
+ + + + + + + + + + + + + + + + Create content with optional blocks + + + + + +
+
+`, + type: "MIXIN", + form: FORM, + }, +]; + +export const PART_SCHEMA: GetComponentReturnType = { + key: "com.enonic.app.react4xp:example", + displayName: "Example Part", + displayNameI18nKey: "", + componentPath: "com.enonic.app.react4xp:/site/parts/example", + modifiedTime: "2024-11-04T07:55:16Z", + resource: ` + Example Part +
+ + + + +
+
+`, + type: "PART", + // form: FORM, + form: [ + // { + // formItemType: "InlineMixin", + // name: "com.enonic.app.react4xp:mymixin", + // }, + { + formItemType: "Layout", + name: "fieldSet30", + label: "FieldSet", + items: FORM, + }, + ], + config: {}, +}; + +export const LAYOUT_SCHEMA: GetComponentReturnType = { + key: "com.enonic.app.react4xp:twoColumns", + displayName: "Two columns", + displayNameI18nKey: "", + description: "Two columns react-rendered layout controller", + descriptionI18nKey: "", + componentPath: "com.enonic.app.react4xp:/site/layouts/twoColumns", + modifiedTime: "2024-11-04T10:09:04Z", + resource: + ` + Two columns + Two columns react-rendered layout controller +
+ + + + + + + + + + + + +
+ + + + +
+ `, + type: "LAYOUT", + form: FORM, + config: {}, + regions: ["left", "right"], +}; + +export const PAGE_SCHEMA: GetComponentReturnType = { + key: "com.enonic.app.react4xp:default", + displayName: "Default page", + displayNameI18nKey: "", + description: "Default react-rendered page controller", + descriptionI18nKey: "", + componentPath: "com.enonic.app.react4xp:/site/pages/default", + modifiedTime: "2024-11-04T10:38:34Z", + resource: ` + Default page + Default react-rendered page controller +
+ + + + + + + + + + + + +
+ + + +
+`, + type: "PAGE", + form: FORM, + config: {}, + regions: ["main"], +}; diff --git a/src/jest/server/setupFile.ts b/src/jest/server/setupFile.ts new file mode 100644 index 00000000..3bffb0b0 --- /dev/null +++ b/src/jest/server/setupFile.ts @@ -0,0 +1,19 @@ +import type {App, Log} from './global.d'; + + +// Avoid type errors +declare module globalThis { + var app: App + var log: Log +} + + +// In order for console to exist in the global scope when running tests in +// testEnvironment: 'node' the @types/node package must be installed and +// potentially listed under types in tsconfig.json. +globalThis.log = { + debug: console.debug, + info: console.info, + error: console.error, + warning: console.warn +} diff --git a/src/jest/server/tsconfig.json b/src/jest/server/tsconfig.json new file mode 100644 index 00000000..3eb0a765 --- /dev/null +++ b/src/jest/server/tsconfig.json @@ -0,0 +1,30 @@ +{ + // Specifies an array of filenames or patterns to include in the program. + // These filenames are resolved relative to the directory containing the + // tsconfig.json file. + "include": [ + "./**/*.spec.ts", + "./**/*.spec.tsx", + "./**/*.test.ts", + "./**/*.test.tsx", + ], + + "compilerOptions": { + // Import CommonJS modules in compliance with es6 modules spec + "esModuleInterop": true, + + // A series of entries which re-map imports to lookup locations relative + // to the baseUrl if set, or to the tsconfig file itself otherwise. + "paths": { + "@enonic-types/core": ["../../../../xp-comlock-9742/modules/lib/core/index.d.ts"], + "/lib/xp/*": ["../../../node_modules/@enonic-types/lib-*"], + "/*": ["../../main/resources/*"], + }, + "sourceMap": true, // Important to get correct line numbers when running coverage tests + "types": [ + "@enonic-types/global", + // "jest", // Doesn't even work for test files in this folder? + "node", // console + ], + } +} diff --git a/src/main/resources/lib/enonic/react4xp/DataFetcher.ts b/src/main/resources/lib/enonic/react4xp/DataFetcher.ts new file mode 100644 index 00000000..35a51fea --- /dev/null +++ b/src/main/resources/lib/enonic/react4xp/DataFetcher.ts @@ -0,0 +1,635 @@ +import type { + Component, + Content, + FragmentComponent, + Layout, + LayoutComponent, + LiteralUnion, + Page, + PageComponent, + Part, + PartComponent, + Request, + RequestMode, + TextComponent, +} from '@enonic-types/core'; +import type { + // GetDynamicComponentParams, + FormItem, + FormItemOptionSet, + FormItemSet, + MixinSchema, +} from '@enonic-types/lib-schema'; +import type { + DecoratedComponent, + DecoratedLayoutComponent, + DecoratedPageComponent, + DecoratedPartComponent, + DecoratedTextComponent, +} from '@enonic/react-components'; + + +import {getIn} from '@enonic/js-utils/object/getIn'; +import {setIn} from '@enonic/js-utils/object/setIn'; +// import {stringify} from 'q-i'; + +import {get as getContentByKey} from '/lib/xp/content'; +import { + getComponent as getComponentSchema, + listSchemas +} from '/lib/xp/schema'; +import { + getContent as getCurrentContent, + getSiteConfig as getCurrentSiteConfig, + processHtml +} from '/lib/xp/portal'; + +import {replaceMacroComments} from './replaceMacroComments'; + + +export type FragmentContent< + Component extends LayoutComponent | PartComponent = Layout | Part +> = Content; + +export type NestedPartial = { + [K in keyof T]?: T[K] extends object ? NestedPartial : T[K]; +}; +export interface GetComponentReturnType { + componentPath: string; + config: Record; + description?: string; + descriptionI18nKey?: string; + displayName: string; + displayNameI18nKey: string; + form: NestedPartial[]; + key: string; + modifiedTime: string; + regions?: string[]; + resource: string; + type: 'PART' | 'LAYOUT' | 'PAGE'; +}; +// type GetComponent = (params: GetDynamicComponentParams) => GetComponentReturnType; + +interface LayoutComponentToPropsParams { + component: LayoutComponent; + content?: PageContent; + processedComponent: DecoratedLayoutComponent; + processedConfig: Record; + siteConfig?: Record | null; + request: Request; +} + +interface PageComponentToPropsParams { + component: PageComponent; + content?: PageContent; + processedComponent: DecoratedPageComponent; + processedConfig: Record; + siteConfig?: Record | null; + request: Request; +} + +export type PageContent< + Data = Record, + Type extends string = string, + Component extends PageComponent = Page +> = Content< + Data, + Type, + // @ts-expect-error Does not satisfy the type constraint + Component +> + +interface PartComponentToPropsParams { + component: PartComponent; + content?: PageContent; + processedConfig: Record; + siteConfig?: Record | null; + request: Request; +} + +type LayoutComponentToPropsFunction = (params: LayoutComponentToPropsParams) => Record; +type PageComponentToPropsFunction = (params: PageComponentToPropsParams) => Record; +type PartComponentToPropsFunction = (params: PartComponentToPropsParams) => Record; + +export class DataFetcher { + private layouts: Record = {}; + private pages: Record = {}; + private parts: Record = {}; + private mixinSchemas: Record = {} + + private static getPath(name: string, ancestor?: string) { + return ancestor ? `${ancestor}.${name}` : name; + } + + constructor() {} + + private findHtmlAreasInFormItemArray({ + ancestor, + form, + htmlAreas, // get modified + }: { + ancestor?: string; + form: NestedPartial[]; + htmlAreas: string[]; + }) { + for (let i = 0; i < form.length; i++) { + const formItem = form[i]; + const { + formItemType, + name + } = formItem; + if (formItemType === 'Input') { + const {inputType} = formItem; + if (inputType === 'HtmlArea') { + htmlAreas.push(DataFetcher.getPath(name as string, ancestor)); + } + } else if (formItemType === 'ItemSet') { + const {items} = formItem as FormItemSet; + this.findHtmlAreasInFormItemArray({ // recurse + ancestor: DataFetcher.getPath(name as string, ancestor), + form: items as NestedPartial[], + htmlAreas, // get modified + }); + } else if (formItemType === 'OptionSet') { + const {options} = formItem as FormItemOptionSet; + for (let j = 0; j < options.length; j++) { + const option = options[j]; + const { + name: optionName, + items + } = option; + this.findHtmlAreasInFormItemArray({ // recurse + ancestor: DataFetcher.getPath(`${name}.${j}.${optionName}`, ancestor), + form: items as NestedPartial[], + htmlAreas, // get modified + }); + // log.info('findHtmlAreasInFormItemArray OptionSet htmlAreas', htmlAreas); + } + } else if (formItemType === 'InlineMixin') { + if (!name) { + throw new Error(`findHtmlAreasInFormItemArray: InlineMixin name not found!`); + } + let mixin = this.mixinSchemas[name]; + + if (!mixin) { + const [application] = name.split(':'); + const mixinsList = listSchemas({ + application, + type: 'MIXIN' + }) as MixinSchema[]; + // log.debug('findHtmlAreasInFormItemArray mixinsList', mixinsList); + + for (let j = 0; j < mixinsList.length; j++) { + const mixin = mixinsList[j]; + const {name: mixinName} = mixin; + this.mixinSchemas[mixinName] = mixin; + } + // log.debug('findHtmlAreasInFormItemArray multiAppMixinsObj', multiAppMixinsObj); + mixin = this.mixinSchemas[name]; + if (!mixin) { + throw new Error(`findHtmlAreasInFormItemArray: InlineMixin mixin not found for name: ${name}!`); + } + // log.debug('findHtmlAreasInFormItemArray mixin', mixin); + } + + const {form} = mixin; + if (!form) { + throw new Error(`findHtmlAreasInFormItemArray: InlineMixin mixin form not found for name: ${name}!`); + } + // log.debug('findHtmlAreasInFormItemArray form', form); + + this.findHtmlAreasInFormItemArray({ // recurse + ancestor, + form: form, + htmlAreas, // get modified + }); + } else if (formItemType === 'Layout') { + // log.debug('findHtmlAreasInFormItemArray Layout formItem', formItem); + const {items} = formItem; + if (items) { // Avoid empty fieldsets + this.findHtmlAreasInFormItemArray({ // recurse + ancestor, + form: items as NestedPartial[], + htmlAreas, // get modified + }); + } + } + } + // log.info('findHtmlAreasInFormItemArray htmlAreas', htmlAreas); + } + + private getHtmlAreas({ + ancestor, + form, + }: { + ancestor?: string; + form: NestedPartial[]; + }): string[] { + const htmlAreas: string[] = []; + this.findHtmlAreasInFormItemArray({ + ancestor, + form, + htmlAreas, + }); + // log.info('getHtmlAreas htmlAreas', htmlAreas); + return htmlAreas; + } + + private processFragment({ + component, + content, + request, + siteConfig, + }: { + component: FragmentComponent; + content?: PageContent; + request: Request; + siteConfig?: Record | null; + }) { + const { + fragment: key, + path + } = component; + // log.info('processFragment fragment key:', key); + + // @ts-expect-error Too complex/strict type generics. + const fragmentContent = getContentByKey({key}); + if (!fragmentContent) { + throw new Error(`processFragment: content not found for key: ${key}!`); + } + // log.info('processFragment content:', content); + + const {fragment} = fragmentContent; + if (!fragment) { + throw new Error(`processFragment: fragment not found in content with key: ${key}!`); + } + + if (!fragment.path) { + fragment.path = path; + } + // log.info('processFragment fragment:', fragment); + + const {type} = fragment; + // log.info('processFragment fragment:', fragment); + + if(type === 'part') { + return this.processPart({ + component: fragment, + content, + request, + siteConfig, + }); + } + if(type === 'layout') { + return this.processLayout({ + component: fragment, + content, + request, + siteConfig, + }); + } + if(type === 'text') { + return this.processTextComponent({ + component: fragment as TextComponent, + mode: request.mode, + }); + } + throw new Error(`processFragment: fragment type not supported: ${type}!`); + } + + private processLayout({ + component, + content, + request, + siteConfig, + }: { + component: LayoutComponent; + content?: PageContent; + request: Request; + siteConfig?: Record | null; + }): DecoratedLayoutComponent { + const decoratedComponent: DecoratedLayoutComponent = JSON.parse(JSON.stringify(component)); + const {descriptor} = component; + const toProps = this.layouts[descriptor]; + if (!toProps) { + log.warning(`processLayout: toProps not found for descriptor: ${descriptor}!`); + return decoratedComponent; + } + + const {form} = getComponentSchema({ + key: descriptor, + type: 'LAYOUT', + }) as GetComponentReturnType; + + const processedComponent = this.processWithRegions({ + component, + content, + form, + request, + }) as DecoratedLayoutComponent; + + decoratedComponent.props = toProps({ + component, + content, + processedComponent: processedComponent, + processedConfig: processedComponent.config, + siteConfig, // : this.getCurrentSiteConfig && this.getCurrentSiteConfig() as Record, + request, + }); + // decoratedComponent.processedConfig = processedComponent.config; + return decoratedComponent; + } + + private processPage({ + component, + content, + request, + siteConfig, + }: { + component: PageComponent; + content?: PageContent; + request: Request; + siteConfig?: Record | null; + }): DecoratedPageComponent { + // log.debug('processPage component:', component); + const decoratedComponent: DecoratedPageComponent = JSON.parse(JSON.stringify(component)); + const {descriptor} = component; + const toProps = this.pages[descriptor]; + if (!toProps) { + log.warning(`processPage: toProps not found for descriptor: ${descriptor}!`); + return decoratedComponent; + } + + const {form} = getComponentSchema({ + key: descriptor, + type: 'PAGE', + }) as GetComponentReturnType; + + const processedComponent = this.processWithRegions({ + component, + content, + form, + request, + }) as DecoratedPageComponent; + + decoratedComponent.props = toProps({ + component, + content, + processedComponent: processedComponent, + processedConfig: processedComponent.config, + siteConfig, // : this.getCurrentSiteConfig && this.getCurrentSiteConfig() as Record, + request, + }); + // decoratedComponent.processedConfig = processedComponent.config; + return decoratedComponent; + } + + private processPart({ + component, + content, + request, + siteConfig + }: { + component: PartComponent; + content?: PageContent; + request: Request; + siteConfig?: Record | null; + }): DecoratedPartComponent { + const {descriptor} = component; + + const decoratedComponent: DecoratedPartComponent = JSON.parse(JSON.stringify(component)); + const toProps = this.parts[descriptor]; + if (!toProps) { + log.warning(`processPart: toProps not found for descriptor: ${descriptor}!`); + return decoratedComponent; + } + + const {form} = getComponentSchema({ + key: descriptor, + type: 'PART', + }) as GetComponentReturnType; + + const htmlAreas = this.getHtmlAreas({ + ancestor: 'config', + form, + }); + // log.info('processPart htmlAreas:', htmlAreas); + + const processedComponent = JSON.parse(JSON.stringify(component)); + for (let i = 0; i < htmlAreas.length; i++) { + // log.info('component:', component); + + const path = htmlAreas[i]; + // log.info('path:', path); + + const html = getIn(component, path) as string; + // log.info('html:', html); + + if (html) { + const processedHtml = processHtml({ + value: html + }); + const data = replaceMacroComments(processedHtml); + setIn(processedComponent, path, data); + } + } // for + + decoratedComponent.props = toProps({ + component, + content, + processedConfig: processedComponent.config, + siteConfig,//: this.getCurrentSiteConfig && this.getCurrentSiteConfig() as Record, + request, + }); + // decoratedComponent.processedConfig = processedComponent.config; + return decoratedComponent; + } + + private processTextComponent({ + component, + mode + }: { + component: TextComponent + mode: LiteralUnion + }): DecoratedTextComponent { + const {text} = component; + const processedHtml = processHtml({ + value: text + }); + const decoratedTextComponent: DecoratedTextComponent = JSON.parse(JSON.stringify(component)); + decoratedTextComponent.props = { + data: replaceMacroComments(processedHtml), + mode + }; + return decoratedTextComponent; + } + + private processWithRegions({ + component: layoutOrPageComponent, + content, + form, + request, + }: { + component: LayoutComponent | PageComponent; + content?: PageContent; + form: NestedPartial[]; + request: Request; + }): DecoratedLayoutComponent | DecoratedPageComponent { + const htmlAreas = this.getHtmlAreas({ + ancestor: 'config', + form, + }); + // log.info('processWithRegions htmlAreas:', htmlAreas); + + const decoratedLayoutOrPageComponent: DecoratedLayoutComponent | DecoratedPageComponent = JSON.parse(JSON.stringify(layoutOrPageComponent)); + + //────────────────────────────────────────────────────────────────────── + // This modifies layoutOrPage.config: + //────────────────────────────────────────────────────────────────────── + for (let i = 0; i < htmlAreas.length; i++) { + // log.info('component:', component); + + const path = htmlAreas[i]; + // log.debug('processWithRegions path:', path); + + const html = getIn(layoutOrPageComponent, path) as string; + // log.info('html:', html); + + if (html) { + const processedHtml = processHtml({ + value: html + }); + const data = replaceMacroComments(processedHtml); + setIn(decoratedLayoutOrPageComponent, path, data); + } + } // for + // log.debug('processWithRegions config:', decoratedLayoutOrPageComponent.config); + + //────────────────────────────────────────────────────────────────────── + // This modifies layoutOrPage.regions: + //────────────────────────────────────────────────────────────────────── + const {regions} = layoutOrPageComponent; + // log.debug('processWithRegions regions:', stringify(regions, {maxItems: Infinity})); + const regionNames = Object.keys(regions); + for (let i = 0; i < regionNames.length; i++) { + const regionName = regionNames[i]; + const region = regions[regionName]; + const components = region.components; + for (let j = 0; j < components.length; j++) { + const component = components[j]; + // @ts-expect-error Too complex/strict type generics. + decoratedLayoutOrPageComponent.regions[regionName].components[j] = this.process({ + component, + content, + request, + }); + } + } + // log.debug('processWithRegions regions:', stringify(decoratedLayoutOrPageComponent.regions, {maxItems: Infinity})); + return decoratedLayoutOrPageComponent; + } // processWithRegions + + public addLayout(descriptor: string, { + toProps + }: { + toProps: (params: LayoutComponentToPropsParams) => Record; + }) { + // log.debug('addLayout:', descriptor); + this.layouts[descriptor] = toProps; + } + + public addPage(descriptor: string, { + toProps + }: { + toProps: (params: PageComponentToPropsParams) => Record; + }) { + // log.debug('addPage:', descriptor); + this.pages[descriptor] = toProps; + } + + public addPart(descriptor: string, { + toProps + }: { + toProps: (params: PartComponentToPropsParams) => Record; + }) { + // log.debug('addPart:', descriptor); + this.parts[descriptor] = toProps; + } + + public process({ + component, + content, + request, + }: { + component?: Component; + content?: PageContent; + request: Request; + }): DecoratedComponent { + // content = this.getCurrentContent && this.getCurrentContent() as PageContent + if (!content) { + content = getCurrentContent!() as PageContent; + if (!content) { + throw new Error(`process: getCurrentContent returned null!`); + } + } + if (!component) { + component = (content.page || content.fragment) as Component; + if (!component) { + throw new Error(`process: component not passed and content.page and content.fragment not found!`); + } + } + const siteConfig = getCurrentSiteConfig(); + if (!siteConfig) { + log.warning(`process: getCurrentSiteConfig returned null!`); + } + const {type} = component; + switch (type) { + case 'part': return this.processPart({ + component, + content, + request, + siteConfig, + }); + case 'layout': return this.processLayout({ + component: component as LayoutComponent, + content, + request, + siteConfig, + }); + case 'page': return this.processPage({ + component: component as PageComponent, + content, + request, + siteConfig, + }); + case 'text': return this.processTextComponent({ + component: component as TextComponent, + mode: request.mode, + }); + case 'fragment': return this.processFragment({ + component: component as FragmentComponent, + content, + request, + siteConfig, + }); + default: throw new Error(`processComponents: component type not supported: ${type}!`); + } + } +} // class DataFetcher + +export function fetchData({ + component, + content, + request, +}: { + component?: Component; + content?: PageContent; + request: Request; +}): DecoratedComponent { + const processor = new DataFetcher(); + return processor.process({ + component, + content, + request + }); +} diff --git a/src/main/resources/lib/enonic/react4xp/index.ts b/src/main/resources/lib/enonic/react4xp/index.ts index bde480ab..c9e11113 100644 --- a/src/main/resources/lib/enonic/react4xp/index.ts +++ b/src/main/resources/lib/enonic/react4xp/index.ts @@ -5,3 +5,4 @@ export { getExecutorUrl, render, } from './React4xp'; +export {DataFetcher} from './DataFetcher'; diff --git a/src/main/resources/lib/enonic/react4xp/replaceMacroComments.ts b/src/main/resources/lib/enonic/react4xp/replaceMacroComments.ts new file mode 100644 index 00000000..cea78da4 --- /dev/null +++ b/src/main/resources/lib/enonic/react4xp/replaceMacroComments.ts @@ -0,0 +1,65 @@ +import type { + MacroData, + RichTextData +} from '@enonic/react-components'; + +export function replaceMacroComments(processedHtml: string): RichTextData { + const rv: RichTextData = { + processedHtml, + macros: [] + }; + let index = 0; + rv.processedHtml = processedHtml + .replace( + /

()<\/p>/gm, + '$1' + ) + .replace( + /

()<\/pre>/gm,
+			'$1'
+		)
+		.replace(
+			//gm,
+			(_origHtmlMacroComment, attributesString) => {
+				// Replacer is executed once per match (macro comment)
+				index++;
+				const ref = index.toString();
+				let name: string = '';
+				const macro: Partial = {
+					config: {},
+					ref,
+				};
+				const replacedAttributes = attributesString.replace(
+					/([^=]+)="([^"]*)"\s*/g,
+					(_kv, key, value) => {
+						// Replacer is executed once per match (attribute key/value)
+						if (key === '_name') {
+							name = value;
+							macro.name = name;
+							macro.descriptor = `whatever:${name}`;
+							return `data-macro-name="${value}" data-macro-ref="${ref}"`;
+						}
+						if (key === '_document') {
+							return '';
+						}
+						if (key === '_body') {
+							key = 'body';
+						}
+						if (macro.config && name) {
+							if (!macro.config[name]) {
+								macro.config[name] = {};
+							}
+							macro.config[name][key] = value;
+						}
+						return '';
+					}
+				)
+				const replacedMacro = ``;
+				if (rv.macros) {
+					rv.macros.push(macro as MacroData);
+				}
+				return replacedMacro;
+			} // single macro replacer
+		);
+	return rv;
+}
diff --git a/tsconfig.json b/tsconfig.json
index 25be7113..eaf7948c 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -2,6 +2,16 @@
 	// This file is mainly for letting your code editor use typings.
 	// It's also used when doing a simple yarn tsc, to check all files.
 	// Rollup overrides some of these options when doing the actual build.
+	"include": [
+		"./src/main/resources/**/*.ts", // This includes *.d.ts files.
+	// 	"node_modules/@enonic/react4xp/**/*.ts",
+	// 	"node_modules/@enonic/js-utils/**/*.ts"
+	],
+	"exclude": [
+		// This will exclude src/main/resources/index.d.ts which we might want!
+		// "**/*.d.ts",
+		"./src/main/resources/assets/**/*.*",
+	],
 	"compilerOptions": {
 		//──────────────────────────────────────────────────────────────────────
 		// Type Checking
@@ -33,14 +43,13 @@
 		// available at runtime, but you can’t access it with an import.
 		"allowUmdGlobalAccess": true,
 
-		"baseUrl": "./",
-
 		"module": "esnext", // What rollup wants
 		// "module": "CommonJS", // When no longer using rollup, just tsup?
 
 		"moduleResolution": "node",
 		"paths": {
 			// Development (should be commented out when commiting)
+			"@enonic-types/core": ["../xp-comlock-9742/modules/lib/core/index.d.ts"],
 			// "@enonic-types/core": ["../../enonic/xp/modules/core/index.ts"],
 			// "@enonic-types/lib-content": ["../../enonic/xp/modules/lib/lib-content/src/main/resources/lib/xp/content.ts"],
 			// "@enonic-types/lib-io": ["../../enonic/xp/modules/lib/lib-io/src/main/resources/lib/xp/io.ts"],
@@ -52,15 +61,16 @@
 			// "/lib/xp/node": ["../../enonic/xp/modules/lib/lib-node/src/main/resources/lib/xp/node.ts"],
 			// "/lib/xp/portal": ["../../enonic/xp/modules/lib/lib-portal/src/main/resources/lib/xp/portal.ts"],
 
-			// A series of entries which re-map imports to lookup locations relative to the baseUrl.
+			// A series of entries which re-map imports to lookup locations relative to this file.
 			// In order to get all of the correct typing in your favourite editor.
 			// yarn tsc doesn't change the import/require paths based on this
+			"@enonic-types/lib-*": ["./node_modules/@enonic-types/lib-*"],
 			"@enonic/react4xp": ["./node_modules/@enonic/react4xp/src"],
 			"@enonic/react4xp/*": ["./node_modules/@enonic/react4xp/src/*"],
 
 			"/lib/enonic/react4xp/*": ["./src/main/resources/lib/enonic/react4xp/*"],
 			"/lib/enonic/static": ["./node_modules/@enonic-types/lib-static"],
-			"/lib/xp/*": ["node_modules/@enonic-types/lib-*"],
+			"/lib/xp/*": ["./node_modules/@enonic-types/lib-*"],
 			"/*": ["./src/main/resources/*"],
 		},
 		"resolveJsonModule": true,
@@ -132,14 +142,4 @@
 		// refer to in your application's source code.
 		"skipLibCheck": true, // Disable type checking for node_modules
 	},
-	"exclude": [
-		// This will exclude src/main/resources/index.d.ts which we might want!
-		// "**/*.d.ts",
-		"src/main/resources/assets/**/*.*",
-	],
-	"include": [
-		"src/main/resources/**/*.ts", // This includes *.d.ts files.
-	// 	"node_modules/@enonic/react4xp/**/*.ts",
-	// 	"node_modules/@enonic/js-utils/**/*.ts"
-	]
 }