diff --git a/.changeset/clean-pandas-move.md b/.changeset/clean-pandas-move.md new file mode 100644 index 0000000000..139bd026b1 --- /dev/null +++ b/.changeset/clean-pandas-move.md @@ -0,0 +1,5 @@ +--- +"create-ima-app": minor +--- + +Use `@ima/testing-library` instead of `enzyme` by default. diff --git a/.changeset/mighty-shrimps-repair.md b/.changeset/mighty-shrimps-repair.md new file mode 100644 index 0000000000..646d1daed0 --- /dev/null +++ b/.changeset/mighty-shrimps-repair.md @@ -0,0 +1,7 @@ +--- +"@ima/storybook-integration": patch +"@ima/server": patch +"@ima/core": patch +--- + +Fix some typing errors. diff --git a/.changeset/soft-frogs-reply.md b/.changeset/soft-frogs-reply.md new file mode 100644 index 0000000000..95e799b83d --- /dev/null +++ b/.changeset/soft-frogs-reply.md @@ -0,0 +1,5 @@ +--- +"@ima/testing-library": minor +--- + +Init new module, see README.md for more info. diff --git a/.gitignore b/.gitignore index b3f16393e1..a397926a66 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ website/.turbo *.tgz .vscode yarn.lock +.swc/ diff --git a/jest.config.base.js b/jest.config.base.js index 5277d3b959..9bcf7ad933 100644 --- a/jest.config.base.js +++ b/jest.config.base.js @@ -1,6 +1,5 @@ module.exports = { rootDir: '.', - testEnvironment: 'node', modulePaths: ['/'], setupFiles: ['/setupJest.js'], testRegex: '(/__tests__/).*Spec\\.jsx?$', @@ -9,6 +8,9 @@ module.exports = { '@swc/jest', { jsc: { + experimental: { + plugins: [['swc_mut_cjs_exports', {}]], + }, target: 'es2022', parser: { syntax: 'ecmascript', @@ -26,6 +28,9 @@ module.exports = { '@swc/jest', { jsc: { + experimental: { + plugins: [['swc_mut_cjs_exports', {}]], + }, target: 'es2022', parser: { syntax: 'typescript', diff --git a/package-lock.json b/package-lock.json index 755e5950f2..48c29c35b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "./packages/devtools", "./packages/error-overlay", "./packages/react-page-renderer", + "./packages/testing-library", "./packages/storybook-integration", "./website" ], @@ -35,6 +36,9 @@ "@rollup/plugin-node-resolve": "^15.0.2", "@size-limit/preset-big-lib": "^8.0.1", "@swc/jest": "^0.2.23", + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.4.8", + "@testing-library/react": "^16.0.0", "@types/jest": "^29.2.2", "@types/node": "^18.7.13", "@typescript-eslint/eslint-plugin": "^5.57.1", @@ -83,6 +87,7 @@ "stylelint-declaration-block-no-ignored-properties": "^2.6.0", "stylelint-order": "^6.0.2", "stylelint-prettier": "^3.0.0", + "swc_mut_cjs_exports": "^0.99.0", "to-mock": "1.6.2", "typescript": "^5.0.3", "verdaccio": "^5.23.2", @@ -100,6 +105,12 @@ "node": ">=0.10.0" } }, + "node_modules/@adobe/css-tools": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.0.tgz", + "integrity": "sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==", + "license": "MIT" + }, "node_modules/@algolia/autocomplete-core": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", @@ -2415,28 +2426,6 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "node_modules/@cfaester/enzyme-adapter-react-18": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@cfaester/enzyme-adapter-react-18/-/enzyme-adapter-react-18-0.7.1.tgz", - "integrity": "sha512-Z3m1qNSlQdrcXdnPSGOAysLdgJFSowu7sbK1cHRcWXuZGS3WOTFOS0kIXbWMa1FnkEbswlIU6KgS+8qKgM6Kqw==", - "dev": true, - "dependencies": { - "enzyme-shallow-equal": "^1.0.0", - "react-is": "^18.2.0", - "react-test-renderer": "^18.2.0" - }, - "peerDependencies": { - "enzyme": "^3.11.0", - "react": "^18.2.0", - "react-dom": "^18.2.0" - } - }, - "node_modules/@cfaester/enzyme-adapter-react-18/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, "node_modules/@changesets/apply-release-plan": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-6.1.4.tgz", @@ -4938,6 +4927,10 @@ "resolved": "packages/storybook-integration", "link": true }, + "node_modules/@ima/testing-library": { + "resolved": "packages/testing-library", + "link": true + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -5047,23 +5040,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/console/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/@jest/core": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", @@ -5111,33 +5087,17 @@ } } }, - "node_modules/@jest/core/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/@jest/create-cache-key-function": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-27.5.1.tgz", - "integrity": "sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", + "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/types": "^27.5.1" + "@jest/types": "^29.6.3" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/environment": { @@ -5155,23 +5115,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/environment/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/@jest/expect": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", @@ -5214,23 +5157,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/fake-timers/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/@jest/globals": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", @@ -5246,23 +5172,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/globals/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/@jest/reporters": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", @@ -5306,23 +5215,6 @@ } } }, - "node_modules/@jest/reporters/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/@jest/reporters/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -5383,23 +5275,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/test-result/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/@jest/test-sequencer": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", @@ -5441,23 +5316,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/transform/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/@jest/transform/node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -5465,28 +5323,20 @@ "dev": true }, "node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "dev": true, + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", "dependencies": { + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", - "@types/yargs": "^16.0.0", + "@types/yargs": "^17.0.8", "chalk": "^4.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/types/node_modules/@types/yargs": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.5.tgz", - "integrity": "sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jridgewell/gen-mapping": { @@ -7539,12 +7389,14 @@ } }, "node_modules/@swc/core": { - "version": "1.3.84", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.84.tgz", - "integrity": "sha512-UPKUiDwG7HOdPfOb1VFeEJ76JDgU2w80JLewzx6tb0fk9TIjhr9yxKBzPbzc/QpjGHDu5iaEuNeZcu27u4j63g==", + "version": "1.7.14", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.14.tgz", + "integrity": "sha512-9aeXeifnyuvc2pcuuhPQgVUwdpGEzZ+9nJu0W8/hNl/aESFsJGR5i9uQJRGu0atoNr01gK092fvmqMmQAPcKow==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@swc/types": "^0.1.4" + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.12" }, "engines": { "node": ">=10" @@ -7554,19 +7406,19 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.3.84", - "@swc/core-darwin-x64": "1.3.84", - "@swc/core-linux-arm-gnueabihf": "1.3.84", - "@swc/core-linux-arm64-gnu": "1.3.84", - "@swc/core-linux-arm64-musl": "1.3.84", - "@swc/core-linux-x64-gnu": "1.3.84", - "@swc/core-linux-x64-musl": "1.3.84", - "@swc/core-win32-arm64-msvc": "1.3.84", - "@swc/core-win32-ia32-msvc": "1.3.84", - "@swc/core-win32-x64-msvc": "1.3.84" + "@swc/core-darwin-arm64": "1.7.14", + "@swc/core-darwin-x64": "1.7.14", + "@swc/core-linux-arm-gnueabihf": "1.7.14", + "@swc/core-linux-arm64-gnu": "1.7.14", + "@swc/core-linux-arm64-musl": "1.7.14", + "@swc/core-linux-x64-gnu": "1.7.14", + "@swc/core-linux-x64-musl": "1.7.14", + "@swc/core-win32-arm64-msvc": "1.7.14", + "@swc/core-win32-ia32-msvc": "1.7.14", + "@swc/core-win32-x64-msvc": "1.7.14" }, "peerDependencies": { - "@swc/helpers": "^0.5.0" + "@swc/helpers": "*" }, "peerDependenciesMeta": { "@swc/helpers": { @@ -7575,12 +7427,13 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.3.84", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.84.tgz", - "integrity": "sha512-mqK0buOo+toF2HoJ/gWj2ApZbvbIiNq3mMwSTHCYJHlQFQfoTWnl9aaD5GSO4wfNFVYfEZ1R259o5uv5NlVtoA==", + "version": "1.7.14", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.7.14.tgz", + "integrity": "sha512-V0OUXjOH+hdGxDYG8NkQzy25mKOpcNKFpqtZEzLe5V/CpLJPnpg1+pMz70m14s9ZFda9OxsjlvPbg1FLUwhgIQ==", "cpu": [ "arm64" ], + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "darwin" @@ -7590,12 +7443,13 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.3.84", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.84.tgz", - "integrity": "sha512-cyuQZz62C43EDZqtnptUTlfDvAjgG3qu139m5zsfIK6ltXA5inKFbDWV3a/M5c18dFzA2Xh21Q46XZezmtQ9Tg==", + "version": "1.7.14", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.7.14.tgz", + "integrity": "sha512-9iFvUnxG6FC3An5ogp5jbBfQuUmTTwy8KMB+ZddUoPB3NR1eV+Y9vOh/tfWcenSJbgOKDLgYC5D/b1mHAprsrQ==", "cpu": [ "x64" ], + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "darwin" @@ -7605,12 +7459,13 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.3.84", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.84.tgz", - "integrity": "sha512-dmt/ECQrp3ZPWnK27p4E4xRIRHOoJhgGvxC5t5YaWzN20KcxE9ykEY2oLGSoeceM/A+4D11aRYGwF/EM7yOkvA==", + "version": "1.7.14", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.7.14.tgz", + "integrity": "sha512-zGJsef9qPivKSH8Vv4F/HiBXBTHZ5Hs3ZjVGo/UIdWPJF8fTL9OVADiRrl34Q7zOZEtGXRwEKLUW1SCQcbDvZA==", "cpu": [ "arm" ], + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -7620,12 +7475,13 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.3.84", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.84.tgz", - "integrity": "sha512-PgVfrI3NVg2z/oeg3GWLb9rFLMqidbdPwVH5nRyHVP2RX/BWP6qfnYfG+gJv4qrKzIldb9TyCGH7y8VWctKLxw==", + "version": "1.7.14", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.7.14.tgz", + "integrity": "sha512-AxV3MPsoI7i4B8FXOew3dx3N8y00YoJYvIPfxelw07RegeCEH3aHp2U2DtgbP/NV1ugZMx0TL2Z2DEvocmA51g==", "cpu": [ "arm64" ], + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -7635,12 +7491,13 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.3.84", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.84.tgz", - "integrity": "sha512-hcuEa8/vin4Ns0P+FpcDHQ4f3jmhgGKQhqw0w+TovPSVTIXr+nrFQ2AGhs9nAxS6tSQ77C53Eb5YRpK8ToFo1A==", + "version": "1.7.14", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.7.14.tgz", + "integrity": "sha512-JDLdNjUj3zPehd4+DrQD8Ltb3B5lD8D05IwePyDWw+uR/YPc7w/TX1FUVci5h3giJnlMCJRvi1IQYV7K1n7KtQ==", "cpu": [ "arm64" ], + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -7650,12 +7507,13 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.3.84", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.84.tgz", - "integrity": "sha512-IvyimSbwGdu21jBBEqR1Up8Jhvl8kIAf1k3e5Oy8oRfgojdUfmW1EIwgGdoUeyQ1VHlfquiWaRGfsnHQUKl35g==", + "version": "1.7.14", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.7.14.tgz", + "integrity": "sha512-Siy5OvPCLLWmMdx4msnEs8HvEVUEigSn0+3pbLjv78iwzXd0qSBNHUPZyC1xeurVaUbpNDxZTpPRIwpqNE2+Og==", "cpu": [ "x64" ], + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -7665,12 +7523,13 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.3.84", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.84.tgz", - "integrity": "sha512-hdgVU/O5ufDCe+p5RtCjU7PRNwd0WM+eWJS+GNY4QWL6O8y2VLM+i4+6YzwSUjeBk0xd+1YElMxbqz7r5tSZhw==", + "version": "1.7.14", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.7.14.tgz", + "integrity": "sha512-FtEGm9mwtRYQNK43WMtUIadxHs/ja2rnDurB99os0ZoFTGG2IHuht2zD97W0wB8JbqEabT1XwSG9Y5wmN+ciEQ==", "cpu": [ "x64" ], + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -7680,12 +7539,13 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.3.84", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.84.tgz", - "integrity": "sha512-rzH6k2BF0BFOFhUTD+bh0oCiUCZjFfDfoZoYNN/CM0qbtjAcFH21hzMh/EH8ZaXq8k/iQmUNNa5MPNPZ4SOMNw==", + "version": "1.7.14", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.7.14.tgz", + "integrity": "sha512-Jp8KDlfq7Ntt2/BXr0y344cYgB1zf0DaLzDZ1ZJR6rYlAzWYSccLYcxHa97VGnsYhhPspMpmCvHid97oe2hl4A==", "cpu": [ "arm64" ], + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -7695,12 +7555,13 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.3.84", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.84.tgz", - "integrity": "sha512-Y+Dk7VLLVwwsAzoDmjkNW/sTmSPl9PGr4Mj1nhc5A2NNxZ+hz4SxFMclacDI03SC5ikK8Qh6WOoE/+nwUDa3uA==", + "version": "1.7.14", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.7.14.tgz", + "integrity": "sha512-I+cFsXF0OU0J9J4zdWiQKKLURO5dvCujH9Jr8N0cErdy54l9d4gfIxdctfTF+7FyXtWKLTCkp+oby9BQhkFGWA==", "cpu": [ "ia32" ], + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -7710,12 +7571,13 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.3.84", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.84.tgz", - "integrity": "sha512-WmpaosqCWMX7DArLdU8AJcj96hy0PKlYh1DaMVikSrrDHbJm2dZ8rd27IK3qUB8DgPkrDYHmLAKNZ+z3gWXgRQ==", + "version": "1.7.14", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.7.14.tgz", + "integrity": "sha512-NNrprQCK6d28mG436jVo2TD+vACHseUECacEBGZ9Ef0qfOIWS1XIt2MisQKG0Oea2VvLFl6tF/V4Lnx/H0Sn3Q==", "cpu": [ "x64" ], + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -7724,13 +7586,21 @@ "node": ">=10" } }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, "node_modules/@swc/jest": { - "version": "0.2.29", - "resolved": "https://registry.npmjs.org/@swc/jest/-/jest-0.2.29.tgz", - "integrity": "sha512-8reh5RvHBsSikDC3WGCd5ZTd2BXKkyOdK7QwynrCH58jk2cQFhhHhFBg/jvnWZehUQe/EoOImLENc9/DwbBFow==", + "version": "0.2.36", + "resolved": "https://registry.npmjs.org/@swc/jest/-/jest-0.2.36.tgz", + "integrity": "sha512-8X80dp81ugxs4a11z1ka43FPhP+/e+mJNXJSxiNYk8gIX/jPBtY4gQTrKu/KIoco8bzKuPI5lUxjfLiGsfvnlw==", "dev": true, + "license": "MIT", "dependencies": { - "@jest/create-cache-key-function": "^27.4.2", + "@jest/create-cache-key-function": "^29.7.0", + "@swc/counter": "^0.1.3", "jsonc-parser": "^3.2.0" }, "engines": { @@ -7741,9 +7611,13 @@ } }, "node_modules/@swc/types": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.4.tgz", - "integrity": "sha512-z/G02d+59gyyUb7KYhKi9jOhicek6QD2oMaotUyG+lUkybpXoV49dY9bj7Ah5Q+y7knK2jU67UTX9FyfGzaxQg==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.12.tgz", + "integrity": "sha512-wBJA+SdtkbFhHjTMYH+dEH1y4VpfGdAc2Kw/LK09i9bXd/K6j6PkDcFCEzb6iVfZMkPRrl/q0e3toqTAJdkIVA==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", @@ -7758,29 +7632,28 @@ } }, "node_modules/@testing-library/dom": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.1.tgz", - "integrity": "sha512-0DGPd9AR3+iDTjGoMpxIkAsUihHZ3Ai6CneU6bRRrffXMgzCdlNk43jTrD2/5LT6CBb3MWTP8v510JzYtahD2w==", - "dev": true, + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", - "aria-query": "5.1.3", + "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": ">=14" + "node": ">=18" } }, "node_modules/@testing-library/dom/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, "engines": { "node": ">=10" }, @@ -7792,7 +7665,6 @@ "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, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -7802,22 +7674,71 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/@testing-library/jest-dom": { + "version": "6.4.8", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.8.tgz", + "integrity": "sha512-JD0G+Zc38f5MBHA4NgxQMR5XtO5Jx9g86jqturNTt2WUfRmLDIY7iKkWHDCCTiDuFMre6nxAD5wHw9W5kI4rGw==", + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "@babel/runtime": "^7.9.2", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.21", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "license": "MIT" + }, "node_modules/@testing-library/react": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.0.0.tgz", - "integrity": "sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg==", - "dev": true, + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.0.0.tgz", + "integrity": "sha512-guuxUKRWQ+FgNX0h0NS0FIq3Q3uLtWVpBzcLOggmfMoUpgBnzBzvLLd4fbm6yS8ydJd94cIfY4yP9qUQjM2KwQ==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.12.5", - "@testing-library/dom": "^9.0.0", - "@types/react-dom": "^18.0.0" + "@babel/runtime": "^7.12.5" }, "engines": { - "node": ">=14" + "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": { @@ -7840,8 +7761,7 @@ "node_modules/@types/aria-query": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz", - "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==", - "dev": true + "integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==" }, "node_modules/@types/babel__core": { "version": "7.20.1", @@ -8293,7 +8213,7 @@ "version": "18.2.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz", "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==", - "dev": true, + "devOptional": true, "dependencies": { "@types/react": "*" } @@ -9780,12 +9700,12 @@ } }, "node_modules/aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", - "dev": true, + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "license": "Apache-2.0", "dependencies": { - "deep-equal": "^2.0.5" + "dequal": "^2.0.3" } }, "node_modules/arr-diff": { @@ -12635,23 +12555,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/create-jest/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/cross-argv": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/cross-argv/-/cross-argv-2.0.0.tgz", @@ -12914,6 +12817,12 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "license": "MIT" + }, "node_modules/cssdb": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.7.2.tgz", @@ -13845,35 +13754,6 @@ } } }, - "node_modules/deep-equal": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.2.tgz", - "integrity": "sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.1", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.0", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -14019,7 +13899,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "engines": { "node": ">=6" } @@ -14182,8 +14061,7 @@ "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 + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==" }, "node_modules/dom-converter": { "version": "0.2.0", @@ -14674,26 +14552,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-iterator-helpers": { "version": "1.0.14", "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.14.tgz", @@ -19126,23 +18984,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-circus/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/jest-circus/node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -19191,23 +19032,6 @@ } } }, - "node_modules/jest-cli/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/jest-config": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", @@ -19253,23 +19077,6 @@ } } }, - "node_modules/jest-config/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/jest-config/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -19333,23 +19140,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-each/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/jest-environment-jsdom": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", @@ -19377,23 +19167,6 @@ } } }, - "node_modules/jest-environment-jsdom/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/jest-environment-jsdom/node_modules/cssstyle": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", @@ -19513,23 +19286,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-environment-node/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/jest-get-type": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", @@ -19564,23 +19320,6 @@ "fsevents": "^2.3.2" } }, - "node_modules/jest-haste-map/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/jest-leak-detector": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", @@ -19629,23 +19368,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-message-util/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/jest-mock": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", @@ -19660,23 +19382,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-mock/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/jest-pnp-resolver": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", @@ -19768,23 +19473,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runner/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/jest-runner/node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -19833,23 +19521,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runtime/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/jest-runtime/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -19901,23 +19572,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-snapshot/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/jest-snapshot/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -19967,22 +19621,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-util/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/jest-validate": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", @@ -20000,23 +19638,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-validate/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -20196,23 +19817,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-watcher/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/jest-worker": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", @@ -20241,23 +19845,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jest/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/jiti": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.20.0.tgz", @@ -21634,7 +21221,6 @@ "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, "bin": { "lz-string": "bin/bin.js" } @@ -22087,7 +21673,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, "engines": { "node": ">=4" } @@ -26154,8 +25739,7 @@ "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 + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, "node_modules/react-json-tree": { "version": "0.18.0", @@ -26345,26 +25929,6 @@ "react": "^16.8.0 || ^17.0.0-0 || ^18.0.0" } }, - "node_modules/react-test-renderer": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.2.0.tgz", - "integrity": "sha512-JWD+aQ0lh2gvh4NM3bBM42Kx+XybOxCpgYK7F8ugAlpaTSnWsX+39Z4XkOykGZAHrjwwTZT3x3KxswVWxHPUqA==", - "dev": true, - "dependencies": { - "react-is": "^18.2.0", - "react-shallow-renderer": "^16.15.0", - "scheduler": "^0.23.0" - }, - "peerDependencies": { - "react": "^18.2.0" - } - }, - "node_modules/react-test-renderer/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, "node_modules/react-textarea-autosize": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz", @@ -26545,7 +26109,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" @@ -29170,18 +28733,6 @@ "graceful-fs": "^4.1.3" } }, - "node_modules/stop-iteration-iterator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", - "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", - "dev": true, - "dependencies": { - "internal-slot": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/stream-shift": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", @@ -29424,7 +28975,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, "dependencies": { "min-indent": "^1.0.0" }, @@ -30128,6 +29678,17 @@ "node": ">= 10" } }, + "node_modules/swc_mut_cjs_exports": { + "version": "0.99.0", + "resolved": "https://registry.npmjs.org/swc_mut_cjs_exports/-/swc_mut_cjs_exports-0.99.0.tgz", + "integrity": "sha512-v3/AiUXrM/6ELCUo7zDHszpYYK0iFmGsnFSAUTsqGFkLH6rFq055L14Wg95agAhwDxO1M7e3nQbxsIUbFv4Khg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@swc/core": "^1.7.0", + "@swc/jest": "^0.2.36" + } + }, "node_modules/swc-loader": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/swc-loader/-/swc-loader-0.2.3.tgz", @@ -34022,8 +33583,7 @@ "memoize-one": "^6.0.0" }, "devDependencies": { - "@cfaester/enzyme-adapter-react-18": "^0.7.0", - "@testing-library/react": "^14.0.0", + "@testing-library/react": "^16.0.0", "@types/react": "^18.0.33", "@types/react-dom": "^18.0.6", "@types/webpack-env": "^1.16.3", @@ -34081,6 +33641,18 @@ "webpack": ">=5.x" } }, + "packages/testing-library": { + "name": "@ima/testing-library", + "version": "19.7.0", + "license": "MIT", + "peerDependencies": { + "@ima/core": ">=19.0.0", + "@ima/react-page-renderer": ">=19.0.0", + "@testing-library/dom": ">=10.0.0", + "@testing-library/jest-dom": ">=6.0.0", + "@testing-library/react": ">=12.0.0" + } + }, "website": { "name": "@ima/docs", "version": "0.0.0", diff --git a/package.json b/package.json index ce9249abbf..0b7e16e7ed 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "./packages/devtools", "./packages/error-overlay", "./packages/react-page-renderer", + "./packages/testing-library", "./packages/storybook-integration", "./website" ], @@ -64,6 +65,9 @@ "@rollup/plugin-node-resolve": "^15.0.2", "@size-limit/preset-big-lib": "^8.0.1", "@swc/jest": "^0.2.23", + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.4.8", + "@testing-library/react": "^16.0.0", "@types/jest": "^29.2.2", "@types/node": "^18.7.13", "@typescript-eslint/eslint-plugin": "^5.57.1", @@ -75,8 +79,8 @@ "copy-webpack-plugin": "^11.0.0", "core-js": "^3.24.0", "css-loader": "^6.7.1", - "es-check": "^7.1.0", "css-minimizer-webpack-plugin": "^5.0.0", + "es-check": "^7.1.0", "eslint": "^8.27.0", "eslint-config-prettier": "^8.5.0", "eslint-import-resolver-typescript": "^3.5.3", @@ -112,6 +116,7 @@ "stylelint-declaration-block-no-ignored-properties": "^2.6.0", "stylelint-order": "^6.0.2", "stylelint-prettier": "^3.0.0", + "swc_mut_cjs_exports": "^0.99.0", "to-mock": "1.6.2", "typescript": "^5.0.3", "verdaccio": "^5.23.2", diff --git a/packages/core/ima-plugin.config.js b/packages/core/ima-plugin.config.js index 72e94089fc..6d390203b0 100644 --- a/packages/core/ima-plugin.config.js +++ b/packages/core/ima-plugin.config.js @@ -5,5 +5,9 @@ const { clientServerConfig } = require('@ima/plugin-cli'); */ module.exports = { ...clientServerConfig, - additionalWatchPaths: ['./transform/**/*', './polyfill/**/*'], + additionalWatchPaths: [ + './transform/**/*', + './polyfill/**/*', + './setupJest.js', + ], }; diff --git a/packages/core/src/boot.ts b/packages/core/src/boot.ts index eefb797d73..772edd1d1c 100644 --- a/packages/core/src/boot.ts +++ b/packages/core/src/boot.ts @@ -55,10 +55,10 @@ export interface Resources { */ export interface Environment { [key: string]: unknown; - $Debug: GlobalImaObject['$Version']; + $Debug: GlobalImaObject['$Debug']; $Language: Record; $Version: GlobalImaObject['$Version']; - $App: GlobalImaObject['$App']; + $App?: GlobalImaObject['$App']; $Resources?: ( response: unknown, manifest: Manifest, diff --git a/packages/create-ima-app/bin/create-ima-app.js b/packages/create-ima-app/bin/create-ima-app.js index 88e13a8a74..ba2f68c921 100755 --- a/packages/create-ima-app/bin/create-ima-app.js +++ b/packages/create-ima-app/bin/create-ima-app.js @@ -9,7 +9,7 @@ import { create } from '../scripts/create.js'; import { error, warn } from '../scripts/utils.js'; const MIN_NODE_VERSION = 16; -const MAX_NODE_VERSION = 18; +const MAX_NODE_VERSION = 22; const __dirname = path.dirname(fileURLToPath(import.meta.url)); if (process.argv.length === 2) { diff --git a/packages/create-ima-app/template/common/.gitignore b/packages/create-ima-app/template/common/.gitignore index ad59241b2f..be0e6986e8 100644 --- a/packages/create-ima-app/template/common/.gitignore +++ b/packages/create-ima-app/template/common/.gitignore @@ -12,3 +12,4 @@ build/ app/assets/bower .npmrc package-lock.json +.swc/ diff --git a/packages/create-ima-app/template/common/app/component/card/__tests__/CardSpec.js b/packages/create-ima-app/template/common/app/component/card/__tests__/CardSpec.js index a8e4ff3ee9..66f70cc5f9 100644 --- a/packages/create-ima-app/template/common/app/component/card/__tests__/CardSpec.js +++ b/packages/create-ima-app/template/common/app/component/card/__tests__/CardSpec.js @@ -1,17 +1,17 @@ -import { shallow } from 'enzyme'; +import { renderWithContext } from '@ima/testing-library'; import { Card } from '../Card'; describe('Card', () => { - it('can render', () => { + it('can render', async () => { const props = { title: 'Test card', href: 'https://www.seznam.cz', children: 'Some content of the card.', }; - const wrapper = shallow(); + const { container } = await renderWithContext(); - expect(wrapper).toMatchSnapshot(); + expect(container.firstChild).toMatchSnapshot(); }); }); diff --git a/packages/create-ima-app/template/common/app/component/card/__tests__/__snapshots__/CardSpec.js.snap b/packages/create-ima-app/template/common/app/component/card/__tests__/__snapshots__/CardSpec.js.snap index f89b08f656..ea0d57ed8f 100644 --- a/packages/create-ima-app/template/common/app/component/card/__tests__/__snapshots__/CardSpec.js.snap +++ b/packages/create-ima-app/template/common/app/component/card/__tests__/__snapshots__/CardSpec.js.snap @@ -2,10 +2,10 @@ exports[`Card can render 1`] = `

+ class="card-content" + > + Some content of the card. +

`; diff --git a/packages/create-ima-app/template/common/app/page/home/__tests__/HomeViewSpec.js b/packages/create-ima-app/template/common/app/page/home/__tests__/HomeViewSpec.js index c916ca0f29..4c2b8496ec 100644 --- a/packages/create-ima-app/template/common/app/page/home/__tests__/HomeViewSpec.js +++ b/packages/create-ima-app/template/common/app/page/home/__tests__/HomeViewSpec.js @@ -1,3 +1,6 @@ +/** + * @jest-environment node + */ import { initImaApp, clearImaApp } from '@ima/plugin-testing-integration'; import cards from '../../../public/cards.json'; diff --git a/packages/create-ima-app/template/common/jest.config.js b/packages/create-ima-app/template/common/jest.config.js index 76bd35f5f2..e1e9b19487 100644 --- a/packages/create-ima-app/template/common/jest.config.js +++ b/packages/create-ima-app/template/common/jest.config.js @@ -1,9 +1,7 @@ module.exports = { bail: true, - testEnvironment: 'node', + preset: '@ima/testing-library', modulePaths: ['/'], - setupFiles: ['@ima/core/setupJest.js', '/setupJest.js'], - snapshotSerializers: ['enzyme-to-json/serializer'], testRegex: '(/__tests__/).*Spec\\.jsx?$', moduleNameMapper: { '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': @@ -15,6 +13,9 @@ module.exports = { '@swc/jest', { jsc: { + experimental: { + plugins: [['swc_mut_cjs_exports', {}]], + }, parser: { syntax: 'ecmascript', jsx: true, @@ -31,6 +32,9 @@ module.exports = { '@swc/jest', { jsc: { + experimental: { + plugins: [['swc_mut_cjs_exports', {}]], + }, parser: { syntax: 'typescript', tsx: true, diff --git a/packages/create-ima-app/template/common/package.json b/packages/create-ima-app/template/common/package.json index 2784ce51d9..3b7898ce3d 100644 --- a/packages/create-ima-app/template/common/package.json +++ b/packages/create-ima-app/template/common/package.json @@ -19,10 +19,11 @@ "@babel/preset-react": "^7.16.7", "@ima/cli": "^19.0.1", "@ima/plugin-testing-integration": "4.1.0", + "@ima/testing-library": "^19.8.0", "@swc/jest": "^0.2.20", - "@cfaester/enzyme-adapter-react-18": "^0.6.0", - "enzyme": "^3.11.0", - "enzyme-to-json": "^3.6.2", + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.4.8", + "@testing-library/react": "^16.0.0", "eslint": "^8.11.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-jest": "^26.1.1", @@ -31,7 +32,9 @@ "error-to-json": "2.0.0", "identity-obj-proxy": "^3.0.0", "jest": "^29.3.1", - "prettier": "^2.6.0" + "jest-environment-jsdom": "^29.7.0", + "prettier": "^2.6.0", + "swc_mut_cjs_exports": "^0.99.0" }, "dependencies": { "@ima/core": "^19.0.3", diff --git a/packages/create-ima-app/template/common/setupJest.js b/packages/create-ima-app/template/common/setupJest.js deleted file mode 100644 index 14fad19f87..0000000000 --- a/packages/create-ima-app/template/common/setupJest.js +++ /dev/null @@ -1,4 +0,0 @@ -import Adapter from '@cfaester/enzyme-adapter-react-18'; -import enzyme from 'enzyme'; - -enzyme.configure({ adapter: new Adapter() }); diff --git a/packages/react-page-renderer/jest.config.js b/packages/react-page-renderer/jest.config.js index be07cab7fb..3922b0cc70 100644 --- a/packages/react-page-renderer/jest.config.js +++ b/packages/react-page-renderer/jest.config.js @@ -2,6 +2,6 @@ const defaultConfig = require('../../jest.config.base.js'); module.exports = { ...defaultConfig, - testEnvironment: 'jsdom', + preset: '@ima/testing-library', testRegex: '(/__tests__/).*Spec\\.[jt]s$', }; diff --git a/packages/react-page-renderer/package.json b/packages/react-page-renderer/package.json index 33b2d19536..47989b3bbd 100644 --- a/packages/react-page-renderer/package.json +++ b/packages/react-page-renderer/package.json @@ -64,8 +64,7 @@ "memoize-one": "^6.0.0" }, "devDependencies": { - "@cfaester/enzyme-adapter-react-18": "^0.7.0", - "@testing-library/react": "^14.0.0", + "@testing-library/react": "^16.0.0", "@types/react": "^18.0.33", "@types/react-dom": "^18.0.6", "@types/webpack-env": "^1.16.3", diff --git a/packages/react-page-renderer/setupJest.js b/packages/react-page-renderer/setupJest.js index cf260d2f2c..e69de29bb2 100644 --- a/packages/react-page-renderer/setupJest.js +++ b/packages/react-page-renderer/setupJest.js @@ -1,13 +0,0 @@ -const util = require('util'); - -var root = typeof window !== 'undefined' && window !== null ? window : global; - -root.TextEncoder = util.TextEncoder; -root.TextDecoder = util.TextDecoder; - -root.$Debug = true; - -const Adapter = require('@cfaester/enzyme-adapter-react-18').default; -const enzyme = require('enzyme'); - -enzyme.configure({ adapter: new Adapter() }); diff --git a/packages/react-page-renderer/src/hooks/__tests__/componentSpec.js b/packages/react-page-renderer/src/hooks/__tests__/componentSpec.js index ee42eabb59..ee0cdb561c 100644 --- a/packages/react-page-renderer/src/hooks/__tests__/componentSpec.js +++ b/packages/react-page-renderer/src/hooks/__tests__/componentSpec.js @@ -1,13 +1,13 @@ -import { shallow } from 'enzyme'; +import { renderWithContext } from '@ima/testing-library'; -import { mountHook } from '../../testUtils'; +import { renderHook } from '../../testUtils'; import { useComponent, useOnce } from '../component'; describe('useComponent', () => { let result; it('should return object of component utility functions', () => { - mountHook(() => { + renderHook(() => { result = useComponent(); }, {}); @@ -29,24 +29,23 @@ describe('useComponent', () => { }); describe('useOnce', () => { - let wrapper; - - it('should call callback only once', () => { + it('should call callback only once', async () => { let count = 0; const TestComponent = () => { useOnce(() => count++); - return null; + return
NotEmpty
; }; - wrapper = shallow(); + const { container, rerender } = await renderWithContext(); - wrapper.setProps({}); - wrapper.setProps({}); - wrapper.setProps({}); - wrapper.setProps({}); + rerender(); + rerender(); + rerender(); + rerender(); + expect(container).not.toBeEmptyDOMElement(); expect(count).toBe(1); }); }); diff --git a/packages/react-page-renderer/src/hooks/__tests__/componentUtilsSpec.js b/packages/react-page-renderer/src/hooks/__tests__/componentUtilsSpec.js index 0d44f2e28d..cecba4084f 100644 --- a/packages/react-page-renderer/src/hooks/__tests__/componentUtilsSpec.js +++ b/packages/react-page-renderer/src/hooks/__tests__/componentUtilsSpec.js @@ -1,4 +1,4 @@ -import { mountHook } from '../../testUtils'; +import { renderHook } from '../../testUtils'; import { useComponentUtils } from '../componentUtils'; describe('useComponentUtils', () => { @@ -6,7 +6,7 @@ describe('useComponentUtils', () => { let contextMock = { $Utils: { CustomContextHelper: {} } }; it('should return componentUtils', () => { - mountHook(() => { + renderHook(() => { result = useComponentUtils(); }, contextMock); diff --git a/packages/react-page-renderer/src/hooks/__tests__/cssClassesSpec.js b/packages/react-page-renderer/src/hooks/__tests__/cssClassesSpec.js index 83a1faeba9..6d0f87eead 100644 --- a/packages/react-page-renderer/src/hooks/__tests__/cssClassesSpec.js +++ b/packages/react-page-renderer/src/hooks/__tests__/cssClassesSpec.js @@ -1,4 +1,4 @@ -import { mountHook } from '../../testUtils'; +import { renderHook } from '../../testUtils'; import { useCssClasses } from '../cssClasses'; describe('useCssClasses', () => { @@ -10,7 +10,7 @@ describe('useCssClasses', () => { }; it('should return shortcut to $CssClasses utility', () => { - mountHook(() => { + renderHook(() => { result = useCssClasses(); }, contextMock); diff --git a/packages/react-page-renderer/src/hooks/__tests__/dispatcherSpec.js b/packages/react-page-renderer/src/hooks/__tests__/dispatcherSpec.js index ab7b695a55..7c4b95febd 100644 --- a/packages/react-page-renderer/src/hooks/__tests__/dispatcherSpec.js +++ b/packages/react-page-renderer/src/hooks/__tests__/dispatcherSpec.js @@ -1,6 +1,6 @@ import React from 'react'; -import { mountHook } from '../../testUtils'; +import { renderHook } from '../../testUtils'; import { useDispatcher } from '../dispatcher'; describe('useDispatcher', () => { @@ -11,7 +11,7 @@ describe('useDispatcher', () => { }); it('should return `fire` callback', () => { - mountHook(() => { + renderHook(() => { result = useDispatcher(); }); diff --git a/packages/react-page-renderer/src/hooks/__tests__/eventBusSpec.js b/packages/react-page-renderer/src/hooks/__tests__/eventBusSpec.js index dc21793c13..787273f306 100644 --- a/packages/react-page-renderer/src/hooks/__tests__/eventBusSpec.js +++ b/packages/react-page-renderer/src/hooks/__tests__/eventBusSpec.js @@ -1,6 +1,6 @@ import React from 'react'; -import { mountHook } from '../../testUtils'; +import { renderHook } from '../../testUtils'; import { useEventBus } from '../eventBus'; describe('useEventBus', () => { @@ -19,7 +19,7 @@ describe('useEventBus', () => { }); it('should return `fire` callback', () => { - mountHook(() => { + renderHook(() => { const ref = React.createRef(null); result = useEventBus(ref, 'event', () => {}); diff --git a/packages/react-page-renderer/src/hooks/__tests__/linkSpec.js b/packages/react-page-renderer/src/hooks/__tests__/linkSpec.js index c0e9c9c981..adaf83304c 100644 --- a/packages/react-page-renderer/src/hooks/__tests__/linkSpec.js +++ b/packages/react-page-renderer/src/hooks/__tests__/linkSpec.js @@ -1,4 +1,4 @@ -import { mountHook } from '../../testUtils'; +import { renderHook } from '../../testUtils'; import { useLink } from '../link'; describe('useLink', () => { @@ -12,7 +12,7 @@ describe('useLink', () => { }; it('should return shortcut to router link', () => { - mountHook(() => { + renderHook(() => { result = useLink(); }, contextMock); diff --git a/packages/react-page-renderer/src/hooks/__tests__/localizeSpec.js b/packages/react-page-renderer/src/hooks/__tests__/localizeSpec.js index 76b72518fc..cca6bf4e4a 100644 --- a/packages/react-page-renderer/src/hooks/__tests__/localizeSpec.js +++ b/packages/react-page-renderer/src/hooks/__tests__/localizeSpec.js @@ -1,4 +1,4 @@ -import { mountHook } from '../../testUtils'; +import { renderHook } from '../../testUtils'; import { useLocalize } from '../localize'; describe('useLocalize', () => { @@ -12,7 +12,7 @@ describe('useLocalize', () => { }; it('should return shortcut to $Dictionary.get function', () => { - mountHook(() => { + renderHook(() => { result = useLocalize(); }, contextMock); diff --git a/packages/react-page-renderer/src/hooks/__tests__/pageContextSpec.js b/packages/react-page-renderer/src/hooks/__tests__/pageContextSpec.js index f00e88808f..83348a6ab9 100644 --- a/packages/react-page-renderer/src/hooks/__tests__/pageContextSpec.js +++ b/packages/react-page-renderer/src/hooks/__tests__/pageContextSpec.js @@ -1,4 +1,4 @@ -import { mountHook } from '../../testUtils'; +import { renderHook } from '../../testUtils'; import { usePageContext } from '../pageContext'; describe('usePageContext', () => { @@ -9,7 +9,7 @@ describe('usePageContext', () => { }; it('should return pageContext', () => { - mountHook(() => { + renderHook(() => { result = usePageContext(); }, contextMock); diff --git a/packages/react-page-renderer/src/hooks/__tests__/settingsSpec.js b/packages/react-page-renderer/src/hooks/__tests__/settingsSpec.js index 7b86774059..25f2f2e961 100644 --- a/packages/react-page-renderer/src/hooks/__tests__/settingsSpec.js +++ b/packages/react-page-renderer/src/hooks/__tests__/settingsSpec.js @@ -1,4 +1,4 @@ -import { mountHook } from '../../testUtils'; +import { renderHook } from '../../testUtils'; import { useSettings } from '../settings'; describe('useSettings', () => { @@ -15,7 +15,7 @@ describe('useSettings', () => { }; it('should return settings object by default', () => { - mountHook(() => { + renderHook(() => { result = useSettings(); }, contextMock); @@ -23,7 +23,7 @@ describe('useSettings', () => { }); it('should return specific sub-settings for given selector', () => { - mountHook(() => { + renderHook(() => { result = useSettings('$Page.scripts'); }, contextMock); @@ -31,7 +31,7 @@ describe('useSettings', () => { }); it('should return empty object for invalid selectors', () => { - mountHook(() => { + renderHook(() => { result = useSettings('invalid.settings.path'); }, contextMock); diff --git a/packages/react-page-renderer/src/hooks/__tests__/windowEventSpec.js b/packages/react-page-renderer/src/hooks/__tests__/windowEventSpec.js index 1dcd4dd3ce..cd8dd2d0d0 100644 --- a/packages/react-page-renderer/src/hooks/__tests__/windowEventSpec.js +++ b/packages/react-page-renderer/src/hooks/__tests__/windowEventSpec.js @@ -1,6 +1,6 @@ import React from 'react'; -import { mountHook } from '../../testUtils'; +import { renderHook } from '../../testUtils'; import { useWindowEvent } from '../windowEvent'; describe('useWindowEvent', () => { @@ -29,7 +29,7 @@ describe('useWindowEvent', () => { }); it('should return window and utility functions', () => { - mountHook(() => { + renderHook(() => { result = useWindowEvent('custom-target', 'custom-event', jest.fn()); expect(result).toMatchInlineSnapshot(` @@ -47,7 +47,7 @@ describe('useWindowEvent', () => { it('should bind events correctly', () => { let cb = jest.fn(); - mountHook(() => { + renderHook(() => { result = useWindowEvent('custom-target', 'custom-event', cb, true); expect(contextMock.$Utils.$Window.bindEventListener).toHaveBeenCalledWith( diff --git a/packages/react-page-renderer/src/testUtils.jsx b/packages/react-page-renderer/src/testUtils.jsx index f3a3cb7704..10de47bb09 100644 --- a/packages/react-page-renderer/src/testUtils.jsx +++ b/packages/react-page-renderer/src/testUtils.jsx @@ -1,4 +1,4 @@ -import { mount } from 'enzyme'; +import { render } from '@ima/testing-library'; import { PageContext } from './PageContext'; @@ -9,17 +9,17 @@ import { PageContext } from './PageContext'; * @param {object} props * @returns {ReactWrapper} */ -function mountHook(callback, context = {}, props = {}) { +function renderHook(callback, context = {}, props = {}) { const TestHookComponent = ({ __callback__ }) => { __callback__(); return null; }; - return mount( + return render( ); } -export { mountHook }; +export { renderHook }; diff --git a/packages/server/types.d.ts b/packages/server/types.d.ts index 8919b4409a..8a92aba009 100644 --- a/packages/server/types.d.ts +++ b/packages/server/types.d.ts @@ -23,6 +23,8 @@ declare module '@ima/server' { } export function createIMAServer(params: { + applicationFolder?: string; + processEnvironment?: (environment: Environment) => Environment; environment?: Environment; logger?: any; emitter?: Emitter; diff --git a/packages/storybook-integration/src/presets/resolvers.ts b/packages/storybook-integration/src/presets/resolvers.ts index 72999dc053..23466d548a 100644 --- a/packages/storybook-integration/src/presets/resolvers.ts +++ b/packages/storybook-integration/src/presets/resolvers.ts @@ -107,16 +107,16 @@ export function resolveRevivalSettings({ process.env.NODE_ENV = oldEnv; const revivalSettings = `(function (root) { - root.$Debug = ${env.$App.$Debug}; + root.$Debug = ${env.$App?.$Debug}; root.$IMA = root.$IMA || {}; $IMA.Test = true; $IMA.SPA = true; - $IMA.$App = ${JSON.stringify(env.$App)}; + $IMA.$App = ${JSON.stringify(env.$App || {})}; $IMA.$PublicPath = ""; $IMA.$RequestID = "storybook-request-id"; $IMA.$Language = "${options.language ?? 'en'}"; $IMA.$Env = "regression"; - $IMA.$Debug = ${env.$App.$Debug}; + $IMA.$Debug = ${env.$App?.$Debug}; $IMA.$Version = "${env.$Version}"; $IMA.$Protocol = "${options.https ? 'https:' : 'http:'}"; $IMA.$Host = "${ diff --git a/packages/testing-library/.npmignore b/packages/testing-library/.npmignore new file mode 100644 index 0000000000..b948e13ae2 --- /dev/null +++ b/packages/testing-library/.npmignore @@ -0,0 +1,3 @@ +* +!dist/**/* +!package.json diff --git a/packages/testing-library/LICENSE b/packages/testing-library/LICENSE new file mode 100644 index 0000000000..a613dddbf1 --- /dev/null +++ b/packages/testing-library/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 Seznam.cz a.s. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/testing-library/README.md b/packages/testing-library/README.md new file mode 100644 index 0000000000..15c5f8aa76 --- /dev/null +++ b/packages/testing-library/README.md @@ -0,0 +1,232 @@ +

+ +

+ +

@ima/testing-library

+

Testing library for IMA.js applications. +

+ +--- + +## IMA Testing Library + +The `@ima/testing-library` contains utilities for testing IMA.js applications. It provides integration with [Jest](https://jestjs.io), [React Testing Library](https://testing-library.com/docs/react-testing-library/intro) (RTL for short) and [Testing Library Jest DOM](https://testing-library.com/docs/ecosystem-jest-dom). + +## Installation + +Install the new dependencies. Note that RTL dependencies are only peer dependencies and you should specify them in your project. + +```bash +npm install -D @ima/testing-library @testing-library/dom @testing-library/jest-dom @testing-library/react jest-environment-jsdom +``` + +Configure jest preset in your jest config file. + +```json +{ + "preset": "@ima/testing-library" +} +``` + +Everything should start working out of the box for a typical IMA.js application. If you are trying to setup this library in a monorepo or an npm package, you might have to do some tweaks with the configuration. + +### Configuration + +There are 2 config functions that you can use to adjust the IMA Testing Library to your specific needs. + +**Server Configuration** + +In this case, you need the jest config file to be in non-json format. + +This configuration should be evaluated in the jest config file. It's config values are used to initialize the JSDOM environment in which the tests are running. + +```javascript +const path = require('node:path'); +const { setImaTestingLibraryServerConfig } = require('@ima/testing-library/server'); + +setImaTestingLibraryServerConfig({ + // your custom config + applicationFolder: path.resolve('./__tests__/') // The default application folder is the root of the project, but you can specify a custom one to add some test specific logic. +}); + +module.exports = { + preset: '@ima/testing-library', + // The preset automatically sets up the moduleNameMapper for the IMA.js application, but you can override it if you need to. + moduleNameMapper: { + '^app/main$': '/app/main.test.js', // You can tell jest to use a different main file for the tests + } +}; +``` + +**Client Configuration** + +This configuration should be evaluated in the setup files, or directly in the test files. It's config values are used to initialize the IMA.js application and provide the context for the tests. + +```javascript +const { setImaTestingLibraryClientConfig } = require('@ima/testing-library/client'); + +setImaTestingLibraryClientConfig({ + // your custom config + imaConfigPath: 'path/to/your/ima.config.js', +}); +``` + +## Usage + +IMA Testing Library is re-exporting everything from `@testing-library/react`. You should always import React Testing Library functions from `@ima/testing-library` as we might add some additional functionality / wrappers in the future. + +IMA Testing Library exports async function `renderWithContext`. It adds default context to the `render` function from RTL. The context is created from the IMA.js application and it contains the same values as the real application context. + +```javascript +import { renderWithContext } from '@ima/testing-library'; + +test('renders learn react link', async () => { + const { getByText } = await renderWithContext(My Text); + const textElement = getByText(/My Text/i); + + expect(textElement).toBeInTheDocument(); +}); +``` + +You might need to specify custom additions to the context, or mock some parts of the IMA application. You can do this by providing a custom context wrapper and using the `@ima/testing-library` specific utilities. + +```javascript +import { renderWithContext, getContextValue, initImaApp } from '@ima/testing-library'; + +test('renders learn react link with custom app configuration', async () => { + const app = await initImaApp(); + + app.oc.get('$Utils').$Foo = jest.fn(() => 'bar'); + + const { getByText } = await renderWithContext(My Text, { app }); + const textElement = getByText(/My Text/i); + + expect(textElement).toBeInTheDocument(); +}); + +test('renders learn react link with custom context value', async () => { + const app = await initImaApp(); + const contextValue = await getContextValue(app); + + contextValue.$Utils.$Foo = jest.fn(() => 'bar'); + + const { getByText } = await renderWithContext(My Text, { contextValue }); + const textElement = getByText(/My Text/i); + + expect(textElement).toBeInTheDocument(); +}); +``` + +### Extending IMA boot config methods + +You can extend IMA boot config by using [IMA `pluginLoader.register`](https://imajs.io/api/classes/ima_core.PluginLoader/#register) method. Use the same approach as in IMA plugins. + +You can either register a plugin loader for all tests by setting it up in a setup file. + +```javascript +// jestSetup.js +import { pluginLoader } from '@ima/core'; + +// If you don't care, if this plugin loader is registered first, or last +pluginLoader.register('jestSetup.js', () => { + return { + initSettings: () => { + return { + prod: { + customSetting: 'customValue' + } + } + } + }; +}); + +// If you need to register the plugin loader as the last one +beforeAll(() => { + pluginLoader.register('jestSetup.js', () => { + return { + initSettings: () => { + return { + prod: { + customSetting: 'customValue' + } + } + } + }; + }); +}); + +// jest.config.js +module.exports = { + // Add this line to your jest config + setupFilesAfterEnv: ['./jestSetup.js'] +}; +``` + +Or you can register a plugin loader for a specific test file. + +```javascript +// mySpec.js +import { pluginLoader } from '@ima/core'; + +beforeAll(() => { + pluginLoader.register('mySpec', () => { + return { + initSettings: () => { + return { + prod: { + customSetting: 'customValue' + } + } + } + }; + }); +}); + +test('renders learn react link with custom app configuration', async () => { + const { getByText } = await renderWithContext(My Text); + const textElement = getByText(/My Text/i); + + expect(textElement).toBeInTheDocument(); +}); +``` + +Or you can register a plugin loader for a test file, but make the boot config methods dynamic so you can change them for each test. + +```javascript +// mySpec.js +import { pluginLoader } from '@ima/core'; + +// We create a placeholder for the plugin loader, so we can change it later +let initSettings = () => {}; + +beforeAll(() => { + pluginLoader.register('mySpec', (...args) => { + return { + initSettings: (...args) => { + return initSettings(...args); // Here we call our overridable function + } + }; + }); +}); + +afterEach(() => { + initSettings = () => {}; // Reset the plugin loader so it is not called for other tests +}); + +test('renders learn react link with custom app configuration', async () => { + initSettings = () => { + return { + prod: { + customSetting: 'customValue' + } + } + }; + + const { getByText } = await renderWithContext(My Text); + const textElement = getByText(/My Text/i); + + expect(textElement).toBeInTheDocument(); +}); +``` + +*Note, that the plugin loader register method evaluates the second argument right away, but the specific boot config methods are evaluated during `renderWithContext` (or `initImaApp` if you are using it directly).* diff --git a/packages/testing-library/jest.config.js b/packages/testing-library/jest.config.js new file mode 100644 index 0000000000..9f891301ff --- /dev/null +++ b/packages/testing-library/jest.config.js @@ -0,0 +1,6 @@ +const defaultConfig = require('../../jest.config.base.js'); + +module.exports = { + ...defaultConfig, + testRegex: '(/__tests__/).*Spec\\.[jt]s$', +}; diff --git a/packages/testing-library/package.json b/packages/testing-library/package.json new file mode 100644 index 0000000000..27144f1c02 --- /dev/null +++ b/packages/testing-library/package.json @@ -0,0 +1,83 @@ +{ + "name": "@ima/testing-library", + "version": "19.7.0", + "description": "Testing library for IMA.js applications.", + "keywords": [ + "IMA.js", + "react", + "testing", + "library" + ], + "bugs": { + "url": "https://github.com/seznam/ima/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/seznam/ima.git", + "directory": "packages/testing-library" + }, + "license": "MIT", + "author": "Filip Satek ", + "scripts": { + "dev": "node ../plugin-cli/dist/bin/ima-plugin.js dev", + "build": "node ../plugin-cli/dist/bin/ima-plugin.js build", + "link": "node ../plugin-cli/dist/bin/ima-plugin.js link", + "lint": "eslint './**/*.{js,jsx,ts,tsx}'", + "test": "jest -c jest.config.js" + }, + "exports": { + ".": { + "types": "./dist/esm/index.d.ts", + "import": "./dist/esm/index.js", + "default": "./dist/cjs/index.js" + }, + "./client": { + "types": "./dist/esm/client/index.d.ts", + "import": "./dist/esm/client/index.js", + "default": "./dist/cjs/client/index.js" + }, + "./server": { + "types": "./dist/esm/server/index.d.ts", + "import": "./dist/esm/server/index.js", + "default": "./dist/cjs/server/index.js" + }, + "./fallback/app/main": { + "types": "./dist/esm/client/app/main.d.ts", + "import": "./dist/esm/client/app/main.js", + "default": "./dist/cjs/client/app/main.js" + }, + "./fallback/server/*": { + "types": "./dist/esm/server/*.d.ts", + "import": "./dist/esm/server/*.js", + "default": "./dist/cjs/server/*.js" + }, + "./jest-preset": { + "types": "./dist/esm/jest-preset.d.ts", + "import": "./dist/esm/jest-preset.js", + "default": "./dist/cjs/jest-preset.js" + }, + "./jestSetupFileAfterEnv": { + "types": "./dist/esm/jestSetupFileAfterEnv.d.ts", + "import": "./dist/esm/jestSetupFileAfterEnv.js", + "default": "./dist/cjs/jestSetupFileAfterEnv.js" + } + }, + "dependencies": { + "@messageformat/core": "^3.0.1", + "globby": "^11.1.0" + }, + "peerDependencies": { + "@ima/cli": ">=19.0.0", + "@ima/core": ">=19.0.0", + "@ima/server": ">=19.0.0", + "@ima/react-page-renderer": ">=19.0.0", + "@testing-library/dom": ">=10.0.0", + "@testing-library/jest-dom": ">=6.0.0", + "@testing-library/react": ">=12.0.0", + "express": ">=4.0.0" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + } +} diff --git a/packages/testing-library/setupJest.js b/packages/testing-library/setupJest.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/testing-library/src/client/app/config/bind.ts b/packages/testing-library/src/client/app/config/bind.ts new file mode 100644 index 0000000000..03f2166b66 --- /dev/null +++ b/packages/testing-library/src/client/app/config/bind.ts @@ -0,0 +1,53 @@ +import { + ComponentUtils, + InitBindFunction, + PageRenderer, + Window, +} from '@ima/core'; +import { + defaultCssClasses as cssClassNameProcessor, + PageRendererFactory, + ServerPageRenderer, +} from '@ima/react-page-renderer'; +import { ClientPageRenderer } from '@ima/react-page-renderer/renderer/ClientPageRenderer'; + +declare module '@ima/core' { + interface OCAliasMap { + $CssClasses: () => typeof cssClassNameProcessor; + $PageRendererFactory: PageRendererFactory; + } +} + +export const initBindApp: InitBindFunction = (ns, oc) => { + // UI components + oc.bind('$CssClasses', function () { + return cssClassNameProcessor; + }); + + // You can set own Component utils here + oc.get(ComponentUtils).register({ + $CssClasses: '$CssClasses', + }); + + oc.inject(PageRendererFactory, [ComponentUtils]); + oc.bind('$PageRendererFactory', PageRendererFactory); + + if (oc.get(Window).isClient()) { + oc.provide(PageRenderer, ClientPageRenderer, [ + PageRendererFactory, + '$Helper', + '$Dispatcher', + '$Settings', + Window, + ]); + } else { + oc.provide(PageRenderer, ServerPageRenderer, [ + PageRendererFactory, + '$Helper', + '$Dispatcher', + '$Settings', + ]); + } + + oc.bind('$PageRenderer', PageRenderer); +}; diff --git a/packages/testing-library/src/client/app/config/settings.ts b/packages/testing-library/src/client/app/config/settings.ts new file mode 100644 index 0000000000..03e6b7e572 --- /dev/null +++ b/packages/testing-library/src/client/app/config/settings.ts @@ -0,0 +1,45 @@ +import { InitSettingsFunction } from '@ima/core'; + +export const initSettings: InitSettingsFunction = (ns, oc, config) => { + return { + prod: { + $Version: config.$Version, + $Http: { + defaultRequestOptions: { + timeout: 7000, // Request timeout + repeatRequest: 0, // Count of automatic repeated request after failing request. + ttl: 60000, // Default time to live for cached request in ms. + fetchOptions: { + mode: 'cors', + headers: { + // Set default request headers + Accept: 'application/json', + 'Accept-Language': config.$Language, + }, + }, + cache: false, // if value exists in cache then returned it else make request to remote server. + }, + cacheOptions: { + prefix: 'http.', // Cache key prefix for response bodies (already parsed as JSON) of completed HTTP requests. + }, + }, + $Router: { + /** + * Middleware execution timeout, see https://imajs.io/basic-features/routing/middlewares#execution-timeout + * for more information. + */ + middlewareTimeout: 30000, + }, + $Cache: { + enabled: false, //Turn on/off cache for all application. + ttl: 60000, // Default time to live for cached value in ms. + }, + $Page: { + $Render: { + documentView: () => {}, // eslint-disable-line @typescript-eslint/no-empty-function + masterElementId: 'page', + }, + }, + }, + }; +}; diff --git a/packages/testing-library/src/client/app/main.ts b/packages/testing-library/src/client/app/main.ts new file mode 100644 index 0000000000..f3387a4c9a --- /dev/null +++ b/packages/testing-library/src/client/app/main.ts @@ -0,0 +1,15 @@ +import * as ima from '@ima/core'; + +import { initBindApp } from './config/bind'; +import { initSettings } from './config/settings'; + +const getInitialAppConfigFunctions = () => { + return { + initBindApp, + initRoutes: () => {}, // eslint-disable-line @typescript-eslint/no-empty-function + initServicesApp: () => {}, // eslint-disable-line @typescript-eslint/no-empty-function + initSettings, + }; +}; + +export { getInitialAppConfigFunctions, ima }; diff --git a/packages/testing-library/src/client/configuration.ts b/packages/testing-library/src/client/configuration.ts new file mode 100644 index 0000000000..0e4e7904b3 --- /dev/null +++ b/packages/testing-library/src/client/configuration.ts @@ -0,0 +1,45 @@ +import type { ContextValue, ImaApp } from '../types'; + +export interface ClientConfiguration { + /** + * If true, the fake dictionary will be used. The real dictionary degrades the performance of the tests so disable this with caution. + */ + useFakeDictionary: boolean; + /** + * The path to the IMA configuration file. This can be only configured once before first `initImaApp` call and cannot be reconfigured later. + */ + rootDir: string; + /** + * The function that will be called after the IMA application is initialized. + */ + afterInitImaApp: (app: ImaApp) => void; + /** + * The function that will be called after the context value is created. + */ + getContextValue: (app: ImaApp) => ContextValue; +} + +const clientConfiguration: ClientConfiguration = { + useFakeDictionary: true, + rootDir: process.cwd(), + afterInitImaApp: () => {}, // eslint-disable-line @typescript-eslint/no-empty-function + getContextValue: app => ({ + $Utils: app.oc.get('$ComponentUtils').getUtils(), + }), +}; + +/** + * Get the current clientConfiguration. + */ +export function getImaTestingLibraryClientConfig() { + return clientConfiguration; +} + +/** + * Modify the current clientConfiguration. + */ +export function setImaTestingLibraryClientConfig( + config: Partial +) { + Object.assign(clientConfiguration, config); +} diff --git a/packages/testing-library/src/client/index.ts b/packages/testing-library/src/client/index.ts new file mode 100644 index 0000000000..21eb56d839 --- /dev/null +++ b/packages/testing-library/src/client/index.ts @@ -0,0 +1,11 @@ +/** + * Exposed as @ima/testing-library/client + * + * The separation from @ima/testing-library is necessary to avoid importing `app/main` when + * users just want to adjust the configuration in a jest setup file. The `app/main` file can + * import a lot of unnecessary dependencies and slow down the test suite. Also, it can break some + * `jest.mock` calls as any dependency imported from jest setup file is evaluated before `jest.mock` calls + * in the test file. + */ + +export * from './configuration'; diff --git a/packages/testing-library/src/helpers.ts b/packages/testing-library/src/helpers.ts new file mode 100644 index 0000000000..c904221221 --- /dev/null +++ b/packages/testing-library/src/helpers.ts @@ -0,0 +1,11 @@ +import path from 'node:path'; + +/** + * Requires specified file from projectPath + * + * @param {string} projectPath relative project path to a file + * @returns {*} File exports + */ +export function importFromProject(projectPath: string) { + return import(path.resolve(projectPath)); +} diff --git a/packages/testing-library/src/index.ts b/packages/testing-library/src/index.ts new file mode 100644 index 0000000000..9b302c9f43 --- /dev/null +++ b/packages/testing-library/src/index.ts @@ -0,0 +1,2 @@ +export * from './rtl'; +export * from './types'; diff --git a/packages/testing-library/src/jest-preset.ts b/packages/testing-library/src/jest-preset.ts new file mode 100644 index 0000000000..87a42b004d --- /dev/null +++ b/packages/testing-library/src/jest-preset.ts @@ -0,0 +1,48 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +import type { Config } from 'jest'; + +import { + getIMAResponseContent, + getImaTestingLibraryServerConfig, +} from './server'; + +/** + * Jest configuration for IMA testing library. + * We are entering undocumented territory here, jestConfig is a promise, but documentation does not mention, if it is allowed. + * It would be nice if there was a synchronous and more straightforward way of generating IMA SPA content. + */ +const jestConfig: Promise = (async () => { + const serverConfig = getImaTestingLibraryServerConfig(); + + let html; + + try { + html = await getIMAResponseContent(); + } catch (error: any) { + // Some async errors are swallowed by jest, so we need to log them manually and throw a safe error + console.error(error.stack ?? error); + + throw new Error( + 'Failed to get IMA response content. Check the error above.' + ); + } + + return { + setupFiles: ['@ima/core/setupJest.js'], + setupFilesAfterEnv: ['@ima/testing-library/jestSetupFileAfterEnv'], + moduleNameMapper: { + '^app/main$': fs.existsSync(path.resolve('./app/main.js')) + ? '/app/main' + : '@ima/testing-library/fallback/app/main', + }, + testEnvironment: 'jsdom', + testEnvironmentOptions: { + html, + url: `${serverConfig.protocol}//${serverConfig.host}/`, + }, + }; +})(); + +export default jestConfig; diff --git a/packages/testing-library/src/jestSetupFileAfterEnv.ts b/packages/testing-library/src/jestSetupFileAfterEnv.ts new file mode 100644 index 0000000000..7b0828bfa8 --- /dev/null +++ b/packages/testing-library/src/jestSetupFileAfterEnv.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; diff --git a/packages/testing-library/src/localization.ts b/packages/testing-library/src/localization.ts new file mode 100644 index 0000000000..a8041e4f5d --- /dev/null +++ b/packages/testing-library/src/localization.ts @@ -0,0 +1,134 @@ +/** + * @TODO The localization logic should be mostly taken from https://github.com/seznam/ima/blob/master/packages/cli/src/webpack/languages.ts + * This solution is similar, but not the same and there can be some inconsistencies. + */ +import path from 'node:path'; + +import { resolveImaConfig } from '@ima/cli'; +import type { ImaCliArgs } from '@ima/cli'; +import type { DictionaryConfig, DictionaryData } from '@ima/core'; +import { assignRecursively } from '@ima/helpers'; +import MessageFormat from '@messageformat/core'; +import globby from 'globby'; + +import { getImaTestingLibraryClientConfig } from './client/configuration'; +import { importFromProject } from './helpers'; + +// Some operations take way too long to be executed with each render call, +// so we need to cache these values +const dictionary: Record = {}; + +/** + * Generates dictionary object, either fake or real depending on the configuration. + */ +export async function generateDictionary() { + const config = getImaTestingLibraryClientConfig(); + + if (config.useFakeDictionary) { + $IMA.i18n = generateFakeDictionary(); + } else { + if (!dictionary[$IMA.$Language]) { + if (!$IMA.$Language) { + throw new Error( + 'Variable $IMA.$Language is not defined. The variable should be defined in the jsdom html template, but it is missing. Maybe your SPA template is not setting this variable?' + ); + } + + dictionary[$IMA.$Language] = await generateRealDictionary($IMA.$Language); + } + + $IMA.i18n = dictionary[$IMA.$Language]; + } +} + +/** + * Generates infinite object as a fake dictionary, returning string `localize(key)` for every key. + * This function does not validate, if the key would exist in the real dictionary. + * Example: + * ``` + * const dictionary = generateFakeDictionary(); + * dictionary['key1']() === 'localize(key1)'; + * dictionary['key1']['key2']['key3']() === 'localize(key1.key2.key3)'; + * ``` + */ +export function generateFakeDictionary(path = ''): DictionaryData { + return new Proxy(() => `localize(${path})`, { + get: (target, prop: string) => { + if (typeof prop === 'string') { + const newPath = path ? `${path}.${prop}` : prop; + return generateFakeDictionary(newPath); + } + return target[prop]; + }, + }) as unknown as DictionaryData; +} + +/** + * Generates IMA formatted dictionary + * + * @param {string} locale + * @returns {object} + */ +export async function generateRealDictionary(locale: string) { + const { rootDir } = getImaTestingLibraryClientConfig(); + const { languages } = await resolveImaConfig({ rootDir } as ImaCliArgs); + const mf = new MessageFormat(locale); + const dictionaries: Record = {}; + const langFileGlobs = languages[locale]; + + await Promise.all( + ( + await globby(langFileGlobs) + ).map(async file => { + try { + const filename = path + .basename(file) + .replace(locale.toUpperCase() + path.extname(file), ''); + + const dictJson = await importFromProject(file); + + dictionaries[filename] = assignRecursively( + dictionaries[filename] ?? {}, + _deepMapValues(dictJson, mf.compile.bind(mf)) + ); + } catch (e) { + console.error( + `Tried to load dictionary JSON at path "${file}", but recieved following error.` + ); + console.error(e); + } + }) + ); + + return dictionaries; +} + +/** + * Apply function through full object or array values + * + * @param {object | Array} obj object to be manipulated + * @param {Function} fn function to run on values + * @returns {object | Array} + */ +function _deepMapValues( + obj: object | Array, + fn: typeof MessageFormat.prototype.compile +): + | ReturnType + | Record + | string { + if (Array.isArray(obj)) { + return obj.map(val => _deepMapValues(val, fn)); + } else if (typeof obj === 'function') { + // Skip already mapped values + return obj; + } else if (typeof obj === 'object' && obj !== null) { + return Object.keys(obj).reduce((acc, current) => { + // @ts-expect-error I don't know how to type `obj[current]`, help me! + acc[current] = _deepMapValues(obj[current], fn); + return acc; + }, {} as Record); + } else { + return fn(obj); + } +} diff --git a/packages/testing-library/src/rtl.tsx b/packages/testing-library/src/rtl.tsx new file mode 100644 index 0000000000..77ebb153db --- /dev/null +++ b/packages/testing-library/src/rtl.tsx @@ -0,0 +1,104 @@ +import { PageContext } from '@ima/react-page-renderer'; +import { render } from '@testing-library/react'; +import type { RenderOptions } from '@testing-library/react'; +import type { ReactElement } from 'react'; + +// import of app/main is resolved by the jest moduleNameMapper +import { ima, getInitialAppConfigFunctions } from 'app/main'; + +import { getImaTestingLibraryClientConfig } from './client/configuration'; +import { generateDictionary } from './localization'; +import type { + ContextValue, + ImaApp, + ImaContextWrapper, + ImaRenderResult, +} from './types'; + +/** + * Initialize the IMA application for testing. + */ +export async function initImaApp(): Promise { + if (!document || !window) { + throw new Error( + 'Missing document, or window. Are you running the test in the jsdom environment?' + ); + } + + const config = getImaTestingLibraryClientConfig(); + + // Init language files + // This must be initialized before oc.get('$Dictionary').init() is called (usualy part of initServices) + await generateDictionary(); + + const app = await ima.createImaApp(); + const bootConfig = await ima.getClientBootConfig( + await getInitialAppConfigFunctions() + ); + + // Init app + await ima.bootClientApp(app, bootConfig); + + await config.afterInitImaApp(app); + + return app; +} + +/** + * Get the context value for the IMA application. + */ +export async function getContextValue(app?: ImaApp): Promise { + const config = getImaTestingLibraryClientConfig(); + + if (!app) { + app = await initImaApp(); + } + + return config.getContextValue(app); +} + +/** + * Creates a context wrapper component from the provided context value. + */ +async function getContextWrapper( + contextValue: ContextValue +): Promise { + return function IMATestingLibraryContextWrapper({ children }) { + return ( + + {children} + + ); + }; +} + +/** + * Renders the provided React element with the IMA context. + */ +async function renderWithContext( + ui: ReactElement, + options?: RenderOptions & { contextValue?: ContextValue; app?: ImaApp } +): Promise { + let { app = null, contextValue, ...rest } = options ?? {}; // eslint-disable-line prefer-const + + if (!contextValue) { + if (!app) { + app = await initImaApp(); + } + + contextValue = await getContextValue(app); + } + + const wrapper = await getContextWrapper(contextValue); + + const result = render(ui, { ...rest, wrapper }); + + return { + ...result, + app, + contextValue, + }; +} + +export * from '@testing-library/react'; +export { renderWithContext }; diff --git a/packages/testing-library/src/server/config/environment.ts b/packages/testing-library/src/server/config/environment.ts new file mode 100644 index 0000000000..1d072ad516 --- /dev/null +++ b/packages/testing-library/src/server/config/environment.ts @@ -0,0 +1,137 @@ +import type { AppEnvironment } from '@ima/core'; +import type { Request } from 'express'; + +const environment: AppEnvironment = { + /** + * The production environment is used as a base template for other + * environment configurations. Meaning that all `dev` or `test` env + * definitions are deeply merged into `prod` base config. + * + * So you can only define other-env specific overrides without the need + * to re-define whole custom configuration. + */ + prod: { + /** + * Enable/disable debug mode. When enabled you can see additional + * error messages while this also enable some additional validation + * that can produce additional errors. + */ + $Debug: false, + $Version: '1.0.0', + /* + * Key-value pairs used for configuring the languages used with + * specific hosts or starting paths. + * + * - Key: Has to start with '//' instead of a protocol, and you can + * define the root path. Optional parameter ":language" + * could be defined at the end to display language in the + * URL. + * + * - Value: Language to use when the key is matched by the current + * URL. If the ":language" parameter is used, the language + * specified in this value is used as the default language + * when the path part specifying the language is not + * present in the current URL. + */ + $Language: { + '//*:*': 'en', + }, + $Server: { + /** + * When defined it overrides any other protocol and + * host settings in the urlParser hook. + */ + // protocol: 'https', + // host: 'localhost', + /** + * The port at which the server listens for incoming HTTP connections + */ + port: 3001, + /** + * Base path, which serves static files form the build folder, + * see https://imajs.io/cli/ima-config-js/#publicpath for more info. + * Used in staticPath middleware definition in app.js. + */ + staticPath: '/static', + /** + * The number of application instances (not threads) used to handle + * concurrent connections within a single thread. + */ + concurrency: 100, + /** + * When the number of concurrent connection exceeds the `staticConcurrency`, + * the server response with static files for 4xx and 5xx. + */ + staticConcurrency: 100, + /** + * When the number of concurrent connection exceeds the `overloadConcurrency`, + * the server response with 503 status code. + */ + overloadConcurrency: 100, + /** + * Define the number of server processes you want to start. + * Use `null` for the current number of available CPU cores. + */ + clusters: null, + /** + * SPA mode means, that the server-side-render is completely disabled + * and clients receive base template generated from spa.ejs file + * with app root html and static files, which initialize the app + * only on client-side. This negates some performance impacts of SSR + * on the app server. + */ + serveSPA: { + /** + * When enabled, and the number of concurrent connection exceeds the concurrency, + * the server will serve the application in SPA mode (without server-side rendering) + */ + allow: true, + /** + * These user agents will always be served a server-rendered page. + */ + blackList: (userAgent: string) => + new RegExp('Googlebot|SeznamBot').test(userAgent), + }, + cache: { + // boolean, or function(Express.Request): boolean + enabled: false, + // null or function(Express.Request): string + cacheKeyGenerator: undefined, + // the maximum time a cache entry is kept + entryTtl: 60 * 60 * 1000, // milliseconds + // the time after which the unused entries are discarded + unusedEntryTtl: 15 * 60 * 1000, // milliseconds + // the maximum entries in cache + maxEntries: 500, + }, + logger: { + /** + * Use "simple", "JSON" or "dev". "dev" option produces colorful output + * with source-mapping of error stacks. This is usefull in development. + */ + formatting: 'simple', + }, + }, + /** + * Options for 'express-http-proxy' defined in app.js file. It's purpose + * is mainly to create proxy to REST API server. This should be used only + * in development due to performance and possible security concerns. + */ + $Proxy: { + // Server route/path at which the proxy will be listening for + path: '/api', + // Proxy sever URL + server: 'example.com', + // Options to pass to the express-http-proxy + options: { + https: true, + timeout: 10000, + proxyReqPathResolver: (request: Request) => `/api/v1${request.url}`, + }, + }, + }, +}; + +const { prod } = environment; + +export { prod }; diff --git a/packages/testing-library/src/server/configuration.ts b/packages/testing-library/src/server/configuration.ts new file mode 100644 index 0000000000..91f1932a2d --- /dev/null +++ b/packages/testing-library/src/server/configuration.ts @@ -0,0 +1,58 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +import type { Environment } from '@ima/core'; + +export interface ServerConfiguration { + /** + * The protocol of the application. + */ + protocol: string; + /** + * The host of the application. + */ + host: string; + /** + * The process environment configuration. This allows you to change the environment configuration that will be available in jsdom. + */ + processEnvironment: (env: Environment) => Environment; + /** + * The path to the application folder. + */ + applicationFolder: string | undefined; +} + +export const FALLBACK_APPLICATION_FOLDER = path.resolve(__dirname, '..'); + +const serverConfiguration: ServerConfiguration = resolveDefaultConfiguration(); + +/** + * Get the current serverConfiguration. + */ +export function getImaTestingLibraryServerConfig() { + return serverConfiguration; +} + +/** + * Modify the current serverConfiguration. + */ +export function setImaTestingLibraryServerConfig( + config: Partial +) { + Object.assign(serverConfiguration, config); +} + +function resolveDefaultConfiguration() { + const serverConfiguration: ServerConfiguration = { + protocol: 'https:', + host: 'imajs.io', + processEnvironment: env => env, + applicationFolder: undefined, + }; + + if (!fs.existsSync(path.resolve('.', 'server/config/environment.js'))) { + serverConfiguration.applicationFolder = FALLBACK_APPLICATION_FOLDER; + } + + return serverConfiguration; +} diff --git a/packages/testing-library/src/server/content.ts b/packages/testing-library/src/server/content.ts new file mode 100644 index 0000000000..96bb866e80 --- /dev/null +++ b/packages/testing-library/src/server/content.ts @@ -0,0 +1,51 @@ +import { createIMAServer } from '@ima/server'; + +import { getImaTestingLibraryServerConfig } from './configuration'; + +const serverConfig = getImaTestingLibraryServerConfig(); + +/** + * Get response content from @ima/server. + */ +export async function getIMAResponseContent(): Promise { + // Mock devUtils to override manifest loading + const devUtils = { + manifestRequire: () => ({}), + }; + + // Prepare serverApp with environment override + const { serverApp } = await createIMAServer({ + devUtils, + applicationFolder: serverConfig.applicationFolder, + processEnvironment: currentEnvironment => + serverConfig.processEnvironment({ + ...currentEnvironment, + $Server: { + ...currentEnvironment.$Server, + concurrency: 0, + serveSPA: { + allow: true, + }, + }, + $Debug: true, + }), + }); + + // Generate request response + const response = await serverApp.requestHandler( + { + get: () => '', + headers: () => '', + originalUrl: serverConfig.host, + protocol: serverConfig.protocol.replace(':', ''), + }, + { + status: () => 200, + send: () => {}, // eslint-disable-line @typescript-eslint/no-empty-function + set: () => {}, // eslint-disable-line @typescript-eslint/no-empty-function + locals: {}, + } + ); + + return response.content; +} diff --git a/packages/testing-library/src/server/index.ts b/packages/testing-library/src/server/index.ts new file mode 100644 index 0000000000..bd7d3007d3 --- /dev/null +++ b/packages/testing-library/src/server/index.ts @@ -0,0 +1,2 @@ +export * from './configuration'; +export * from './content'; diff --git a/packages/testing-library/src/server/template/400.ejs b/packages/testing-library/src/server/template/400.ejs new file mode 100644 index 0000000000..aec7e4bc18 --- /dev/null +++ b/packages/testing-library/src/server/template/400.ejs @@ -0,0 +1,14 @@ + + + + + + + IMA.js - 400 + + + +

400 Page

+ + + diff --git a/packages/testing-library/src/server/template/500.ejs b/packages/testing-library/src/server/template/500.ejs new file mode 100644 index 0000000000..9ad3eaf696 --- /dev/null +++ b/packages/testing-library/src/server/template/500.ejs @@ -0,0 +1,14 @@ + + + + + + + IMA.js - 500 + + + +

500 Page

+ + + diff --git a/packages/testing-library/src/server/template/spa.ejs b/packages/testing-library/src/server/template/spa.ejs new file mode 100644 index 0000000000..d7b81a6ce6 --- /dev/null +++ b/packages/testing-library/src/server/template/spa.ejs @@ -0,0 +1,18 @@ + + + + + + + IMA.js - SPA + #{styles} + + + +
+ #{revivalCache} + #{revivalSettings} + #{runner} + + + diff --git a/packages/testing-library/src/types.ts b/packages/testing-library/src/types.ts new file mode 100644 index 0000000000..23c3930437 --- /dev/null +++ b/packages/testing-library/src/types.ts @@ -0,0 +1,13 @@ +import type { Utils, createImaApp } from '@ima/core'; +import { render } from '@testing-library/react'; + +export interface ContextValue { + $Utils: Utils; +} + +export type ImaApp = ReturnType; +export type ImaContextWrapper = React.FC<{ children: React.ReactNode }>; +export type ImaRenderResult = ReturnType & { + app: ImaApp | null; + contextValue: ContextValue; +}; diff --git a/packages/testing-library/tsconfig.json b/packages/testing-library/tsconfig.json new file mode 100644 index 0000000000..28bfe9b14c --- /dev/null +++ b/packages/testing-library/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "target": "ES2018", + "module": "Node16", + "lib": [ + "ES2022", + "DOM", + "DOM.Iterable", + ], + "outDir": "dist", + "paths": { + "app/main": ["./src/client/app/main"], + }, + "types": ["webpack", "core", "cli", "react-page-renderer", "less-plugin-glob"] + }, + "include": [ + "src" + ] +} diff --git a/utils/tests/createImaAppTests.sh b/utils/tests/createImaAppTests.sh index abe1ed7b91..6a3ed0538e 100755 --- a/utils/tests/createImaAppTests.sh +++ b/utils/tests/createImaAppTests.sh @@ -1,5 +1,8 @@ #!/bin/bash +TARGET_WEB_URL="http://localhost:3001/" +PARALLEL_TEST_CONNECTIONS=300 + # Add customized environment configuration mv server/config/environment.js server/config/environment.orig.js cp "$ROOT_DIR_IMA/utils/tests/app/environment.js" server/config/environment.js diff --git a/utils/tests/testRunner.sh b/utils/tests/testRunner.sh index 81596ab7c2..ddc21b828a 100755 --- a/utils/tests/testRunner.sh +++ b/utils/tests/testRunner.sh @@ -2,9 +2,6 @@ set -e -TARGET_WEB_URL="http://localhost:3001/" -PARALLEL_TEST_CONNECTIONS=300 -SKELETON_URL="https://github.com/seznam/IMA.js-skeleton.git" NPM_LOCAL_REGISTRY_URL_NO_PROTOCOL="localhost:4873" NPM_LOCAL_REGISTRY_URL="http://${NPM_LOCAL_REGISTRY_URL_NO_PROTOCOL}/" ROOT_DIR_IMA=`pwd` @@ -17,7 +14,7 @@ cd "$ROOT_DIR_IMA" CREATE_IMA_APP_DIR="$ROOT_DIR_IMA/packages/create-ima-app" PACKAGE_VERSION="0.0.0-next" -PACKAGES="cli core create-ima-app dev-utils error-overlay helpers hmr-client server react-page-renderer" +PACKAGES="cli core create-ima-app dev-utils error-overlay helpers hmr-client server react-page-renderer testing-library" # Setup local registry node_modules/.bin/verdaccio -l "$NPM_LOCAL_REGISTRY_URL_NO_PROTOCOL" -c utils/tests/verdaccio_config.yml >/dev/null &