diff --git a/.eslintrc b/.eslintrc index e1ea53f7..1bcb812a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -19,5 +19,9 @@ "varsIgnorePattern": "^_", "caughtErrors": "none" }] - } + }, + "ignorePatterns": [ + "**/__tests__/**/*", + "**/__stories__/**/*" + ] } diff --git a/.github/workflows/pr-visual-tests-report.yml b/.github/workflows/pr-visual-tests-report.yml new file mode 100644 index 00000000..93aabded --- /dev/null +++ b/.github/workflows/pr-visual-tests-report.yml @@ -0,0 +1,43 @@ +name: PR Visual Tests Report + +on: + workflow_run: + workflows: ['PR Visual Tests'] + types: + - completed + +jobs: + comment: + name: Create GitHub Comment + if: github.event.workflow_run.event == 'pull_request' + runs-on: ubuntu-latest + steps: + - name: Download Artifacts + uses: actions/download-artifact@v4 + with: + github-token: ${{ secrets.GRAVITY_UI_BOT_GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + - name: Extract PR Number + id: pr + run: echo "::set-output name=id::$( ./pr-id.txt + shell: bash + - name: Create PR Artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: pr + path: ./pr-id.txt diff --git a/gulpfile.js b/gulpfile.js index e9989f30..d4e652af 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -69,7 +69,12 @@ async function compileTS({module, destPath}) { ]; return new Promise((resolve) => { - gulp.src(['src/**/*.{js,jsx,ts,tsx}', '!src/**/*.test.{js,jsx,ts,tsx}']) + gulp.src([ + 'src/**/*.{js,jsx,ts,tsx}', + '!src/**/*.test.{js,jsx,ts,tsx}', + '!src/**/__tests__/**/*', + '!src/**/__stories__/**/*', + ]) .pipe(sourcemaps.init()) .pipe( tsProject({ diff --git a/jest.config.js b/jest.config.js index 53c53a00..dd50ce86 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,7 +6,7 @@ const {compilerOptions} = require('./tsconfig.json'); module.exports = { preset: 'ts-jest/presets/js-with-ts', testEnvironment: 'jsdom', - testPathIgnorePatterns: ['spec.js', 'spec.ts'], + testPathIgnorePatterns: ['spec.js', 'spec.ts', '__tests__', '__stories__'], setupFilesAfterEnv: ['tests/setup.ts'], moduleNameMapper: { ...pathsToModuleNameMapper(compilerOptions.paths, {prefix: '/'}), diff --git a/package-lock.json b/package-lock.json index 6137fea8..9e6ba8b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,6 +68,8 @@ "@gravity-ui/stylelint-config": "4.0.1", "@gravity-ui/tsconfig": "1.0.0", "@gravity-ui/uikit": "7.1.1", + "@playwright/experimental-ct-react": "^1.49.0", + "@playwright/test": "^1.49.0", "@storybook/addon-docs": "8.4.1", "@storybook/addon-essentials": "^8.4.1", "@storybook/addon-webpack5-compiler-babel": "3.0.3", @@ -88,6 +90,7 @@ "@types/react-dom": "18.0.11", "@types/rimraf": "3.0.2", "@types/sanitize-html": "2.11.0", + "@vitejs/plugin-react": "^4.3.4", "bem-cn-lite": "4.1.0", "dpdm": "3.14.0", "esbuild-sass-plugin": "2.15.0", @@ -1755,6 +1758,36 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-react-pure-annotations": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz", @@ -2817,6 +2850,22 @@ "integrity": "sha512-rGDVyqZyJ4GUjuUIYeMG7w6w5mb1dLF/nkloWEyxqZWy/POO4GiHAG83d4wK6U3gTFGTe+BXabQzdIKZwNVCTw==", "dev": true }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", + "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/android-arm": { "version": "0.19.3", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.3.tgz", @@ -3073,6 +3122,22 @@ "node": ">=12" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", + "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/netbsd-x64": { "version": "0.19.3", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.3.tgz", @@ -3089,6 +3154,22 @@ "node": ">=12" } }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", + "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/openbsd-x64": { "version": "0.19.3", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.3.tgz", @@ -4666,6 +4747,298 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@playwright/experimental-ct-core": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.51.0.tgz", + "integrity": "sha512-rrZkaUEFq5DSAGbkAfQ04kT62HV1oke+AEy1He39BsePAhEfzBMHChZYLgoJcWkjO6Mm1py/Sgn9kcpp6derSg==", + "dev": true, + "dependencies": { + "playwright": "1.51.0", + "playwright-core": "1.51.0", + "vite": "^5.4.14 || ^6.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@playwright/experimental-ct-react": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-react/-/experimental-ct-react-1.51.0.tgz", + "integrity": "sha512-aVaRkCtTo4X3Wu/7h7RzqUTnhkIIH6+IINjTYN4Hl0rMN1Ct8DrxNS7H5NLjTpxUlR1qnVdIQT32Bc5SnaxOCA==", + "dev": true, + "dependencies": { + "@playwright/experimental-ct-core": "1.51.0", + "@vitejs/plugin-react": "^4.2.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@playwright/test": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.51.0.tgz", + "integrity": "sha512-dJ0dMbZeHhI+wb77+ljx/FeC8VBP6j/rj9OAojO08JI80wTZy6vRk9KvHKiDCUh4iMpEiseMgqRBIeW+eKX6RA==", + "dev": true, + "dependencies": { + "playwright": "1.51.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.35.0.tgz", + "integrity": "sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.35.0.tgz", + "integrity": "sha512-FtKddj9XZudurLhdJnBl9fl6BwCJ3ky8riCXjEw3/UIbjmIY58ppWwPEvU3fNu+W7FUsAsB1CdH+7EQE6CXAPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.35.0.tgz", + "integrity": "sha512-Uk+GjOJR6CY844/q6r5DR/6lkPFOw0hjfOIzVx22THJXMxktXG6CbejseJFznU8vHcEBLpiXKY3/6xc+cBm65Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.35.0.tgz", + "integrity": "sha512-3IrHjfAS6Vkp+5bISNQnPogRAW5GAV1n+bNCrDwXmfMHbPl5EhTmWtfmwlJxFRUCBZ+tZ/OxDyU08aF6NI/N5Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.35.0.tgz", + "integrity": "sha512-sxjoD/6F9cDLSELuLNnY0fOrM9WA0KrM0vWm57XhrIMf5FGiN8D0l7fn+bpUeBSU7dCgPV2oX4zHAsAXyHFGcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.35.0.tgz", + "integrity": "sha512-2mpHCeRuD1u/2kruUiHSsnjWtHjqVbzhBkNVQ1aVD63CcexKVcQGwJ2g5VphOd84GvxfSvnnlEyBtQCE5hxVVw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.35.0.tgz", + "integrity": "sha512-mrA0v3QMy6ZSvEuLs0dMxcO2LnaCONs1Z73GUDBHWbY8tFFocM6yl7YyMu7rz4zS81NDSqhrUuolyZXGi8TEqg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.35.0.tgz", + "integrity": "sha512-DnYhhzcvTAKNexIql8pFajr0PiDGrIsBYPRvCKlA5ixSS3uwo/CWNZxB09jhIapEIg945KOzcYEAGGSmTSpk7A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.35.0.tgz", + "integrity": "sha512-uagpnH2M2g2b5iLsCTZ35CL1FgyuzzJQ8L9VtlJ+FckBXroTwNOaD0z0/UF+k5K3aNQjbm8LIVpxykUOQt1m/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.35.0.tgz", + "integrity": "sha512-XQxVOCd6VJeHQA/7YcqyV0/88N6ysSVzRjJ9I9UA/xXpEsjvAgDTgH3wQYz5bmr7SPtVK2TsP2fQ2N9L4ukoUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.35.0.tgz", + "integrity": "sha512-5pMT5PzfgwcXEwOaSrqVsz/LvjDZt+vQ8RT/70yhPU06PTuq8WaHhfT1LW+cdD7mW6i/J5/XIkX/1tCAkh1W6g==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.35.0.tgz", + "integrity": "sha512-c+zkcvbhbXF98f4CtEIP1EBA/lCic5xB0lToneZYvMeKu5Kamq3O8gqrxiYYLzlZH6E3Aq+TSW86E4ay8iD8EA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.35.0.tgz", + "integrity": "sha512-s91fuAHdOwH/Tad2tzTtPX7UZyytHIRR6V4+2IGlV0Cej5rkG0R61SX4l4y9sh0JBibMiploZx3oHKPnQBKe4g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.35.0.tgz", + "integrity": "sha512-hQRkPQPLYJZYGP+Hj4fR9dDBMIM7zrzJDWFEMPdTnTy95Ljnv0/4w/ixFw3pTBMEuuEuoqtBINYND4M7ujcuQw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.35.0.tgz", + "integrity": "sha512-Pim1T8rXOri+0HmV4CdKSGrqcBWX0d1HoPnQ0uw0bdp1aP5SdQVNBy8LjYncvnLgu3fnnCt17xjWGd4cqh8/hA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.35.0.tgz", + "integrity": "sha512-QysqXzYiDvQWfUiTm8XmJNO2zm9yC9P/2Gkrwg2dH9cxotQzunBHYr6jk4SujCTqnfGxduOmQcI7c2ryuW8XVg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.35.0.tgz", + "integrity": "sha512-OUOlGqPkVJCdJETKOCEf1mw848ZyJ5w50/rZ/3IBQVdLfR5jk/6Sr5m3iO2tdPgwo0x7VcncYuOvMhBWZq8ayg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.35.0.tgz", + "integrity": "sha512-2/lsgejMrtwQe44glq7AFFHLfJBPafpsTa6JvP2NGef/ifOa4KBoglVf7AKN7EV9o32evBPRqfg96fEHzWo5kw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.35.0.tgz", + "integrity": "sha512-PIQeY5XDkrOysbQblSW7v3l1MDZzkTEzAfTPkj5VAu3FW8fS4ynyLg2sINp0fp3SjZ8xkRYpLqoKcYqAkhU1dw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -6898,7 +7271,26 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, - "node_modules/@webassemblyjs/ast": { + "node_modules/@vitejs/plugin-react": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", + "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "dev": true, + "dependencies": { + "@babel/core": "^7.26.0", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", @@ -9084,6 +9476,15 @@ "node": ">=10" } }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -16792,9 +17193,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.9.tgz", + "integrity": "sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==", "dev": true, "funding": [ { @@ -17739,6 +18140,36 @@ "dev": true, "license": "MIT" }, + "node_modules/playwright": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.51.0.tgz", + "integrity": "sha512-442pTfGM0xxfCYxuBa/Pu6B2OqxqqaYq39JS8QDMGThUvIOCd6s0ANDog3uwA0cHavVlnTQzGCN7Id2YekDSXA==", + "dev": true, + "dependencies": { + "playwright-core": "1.51.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.51.0.tgz", + "integrity": "sha512-x47yPE3Zwhlil7wlNU/iktF7t2r/URR3VLbH6EknJd/04Qc/PSJ0EY3CMXipmglLG+zyRxW6HNo2EGbKLHPWMg==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/plugin-error": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", @@ -17807,9 +18238,9 @@ } }, "node_modules/postcss": { - "version": "8.4.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", - "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "dev": true, "funding": [ { @@ -17826,9 +18257,9 @@ } ], "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -18529,6 +18960,15 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -19050,6 +19490,44 @@ "dev": true, "license": "Unlicense" }, + "node_modules/rollup": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.35.0.tgz", + "integrity": "sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.35.0", + "@rollup/rollup-android-arm64": "4.35.0", + "@rollup/rollup-darwin-arm64": "4.35.0", + "@rollup/rollup-darwin-x64": "4.35.0", + "@rollup/rollup-freebsd-arm64": "4.35.0", + "@rollup/rollup-freebsd-x64": "4.35.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.35.0", + "@rollup/rollup-linux-arm-musleabihf": "4.35.0", + "@rollup/rollup-linux-arm64-gnu": "4.35.0", + "@rollup/rollup-linux-arm64-musl": "4.35.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.35.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.35.0", + "@rollup/rollup-linux-riscv64-gnu": "4.35.0", + "@rollup/rollup-linux-s390x-gnu": "4.35.0", + "@rollup/rollup-linux-x64-gnu": "4.35.0", + "@rollup/rollup-linux-x64-musl": "4.35.0", + "@rollup/rollup-win32-arm64-msvc": "4.35.0", + "@rollup/rollup-win32-ia32-msvc": "4.35.0", + "@rollup/rollup-win32-x64-msvc": "4.35.0", + "fsevents": "~2.3.2" + } + }, "node_modules/rope-sequence": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.3.tgz", @@ -19712,9 +20190,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -21724,6 +22202,483 @@ "node": ">=0.10.0" } }, + "node_modules/vite": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.1.tgz", + "integrity": "sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q==", + "dev": true, + "dependencies": { + "esbuild": "^0.25.0", + "postcss": "^8.5.3", + "rollup": "^4.30.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", + "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", + "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", + "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", + "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", + "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", + "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", + "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", + "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", + "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", + "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", + "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", + "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", + "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", + "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", + "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", + "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", + "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", + "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", + "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", + "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", + "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", + "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", + "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.0", + "@esbuild/android-arm": "0.25.0", + "@esbuild/android-arm64": "0.25.0", + "@esbuild/android-x64": "0.25.0", + "@esbuild/darwin-arm64": "0.25.0", + "@esbuild/darwin-x64": "0.25.0", + "@esbuild/freebsd-arm64": "0.25.0", + "@esbuild/freebsd-x64": "0.25.0", + "@esbuild/linux-arm": "0.25.0", + "@esbuild/linux-arm64": "0.25.0", + "@esbuild/linux-ia32": "0.25.0", + "@esbuild/linux-loong64": "0.25.0", + "@esbuild/linux-mips64el": "0.25.0", + "@esbuild/linux-ppc64": "0.25.0", + "@esbuild/linux-riscv64": "0.25.0", + "@esbuild/linux-s390x": "0.25.0", + "@esbuild/linux-x64": "0.25.0", + "@esbuild/netbsd-arm64": "0.25.0", + "@esbuild/netbsd-x64": "0.25.0", + "@esbuild/openbsd-arm64": "0.25.0", + "@esbuild/openbsd-x64": "0.25.0", + "@esbuild/sunos-x64": "0.25.0", + "@esbuild/win32-arm64": "0.25.0", + "@esbuild/win32-ia32": "0.25.0", + "@esbuild/win32-x64": "0.25.0" + } + }, + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/vscode-jsonrpc": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", @@ -22222,13 +23177,17 @@ "dev": true }, "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", "dev": true, - "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/yargs": { diff --git a/package.json b/package.json index f3a3f1f1..cd23d2f3 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,14 @@ "test:cov": "jest --coverage", "test:watch": "jest --watchAll", "test:esbuild": "node tests/esbuild-test/esbuild-tester.js", - "prepublishOnly": "npm run lint && npm run build" + "prepublishOnly": "npm run lint && npm run build", + "playwright:install": "playwright install chromium webkit --with-deps", + "playwright": "playwright test --config=playwright/playwright.config.ts", + "playwright:update": "npm run playwright -- -u", + "playwright:clear-cache": "rm -rf ./playwright/.cache", + "playwright:docker": "./scripts/playwright-docker.sh 'npm run playwright'", + "playwright:docker:update": "./scripts/playwright-docker.sh 'npm run playwright:update'", + "playwright:docker:clear-cache": "./scripts/playwright-docker.sh clear-cache" }, "exports": { ".": { @@ -221,6 +228,9 @@ "@gravity-ui/stylelint-config": "4.0.1", "@gravity-ui/tsconfig": "1.0.0", "@gravity-ui/uikit": "7.1.1", + "@playwright/experimental-ct-react": "^1.49.0", + "@playwright/test": "^1.49.0", + "@vitejs/plugin-react": "^4.3.4", "@storybook/addon-docs": "8.4.1", "@storybook/addon-essentials": "^8.4.1", "@storybook/addon-webpack5-compiler-babel": "3.0.3", diff --git a/playwright/README.md b/playwright/README.md new file mode 100644 index 00000000..0c036a21 --- /dev/null +++ b/playwright/README.md @@ -0,0 +1,118 @@ +# Playwright Test Component + +## How to write a test + +1. Select the component you want to write tests for +2. Inside the component folder, create the `__tests__` folder and create a file inside it with the following name `.visual.test.tsx` +3. Writing a test: + + Capture a screenshot, by default in light theme only: + + ```tsx + import {expect} from '@playwright/experimental-ct-react'; + + import {MyComponent} from '../MyComponent'; + + import {test} from '~playwright/core'; + + test('test description', async ({mount}) => { + // mount the component + const component = await mount(); + + // capture the screenshot + await expect(component).toHaveScreenshot(); + }); + ``` + + You can also capture screenshots both in dark and light themes: + + ```tsx + import {MyComponent} from '../MyComponent'; + + import {test} from '~playwright/core'; + + test('test description', async ({mount, expectScreenshot}) => { + // mount the component + await mount(); + + // capture the screenshot + await expectScreenshot(); + }); + ``` + + If you need to do any actions with the component: + + ```tsx + import {MyComponent} from '../MyComponent'; + + import {test} from '~playwright/core'; + + test('test description', async ({mount, expectScreenshot}) => { + // mount the component + const component = await mount(); + + // capture the screenshot + await expectScreenshot({component}); + }); + ``` + + Group of tests. + + ```ts + test.describe('Name group tests', () => { + test('1', ...); + test('2', ...); + ... + test('10', ...) + }); + ``` + +4. Run tests + + ```shell + npm run playwright:install + npm run playwright + ``` + + If you are using system other than Linux, then you need to run tests via docker command: + + ```shell + npm run playwright:docker + ``` + + > `npm run playwright:install` command must be run only once on initial setup + +5. Update screenshots if needed + + ```shell + npm run playwright:update + ``` + + Or + + ```shell + npm run playwright:docker:update + ``` + +6. In the folder `__snapshots__`, which is on the same level as the `__tests__` folder, the folder `.visual.test.tsx-snapshots`, will contain screenshots + +## Description of possible commands: + +1. [playwright-test-components](https://playwright.dev/docs/test-components) +2. [playwright-docs](https://playwright.dev/docs/api/class-test) +3. [playwright-writing-tests](https://playwright.dev/docs/writing-tests) + +## Test examples + +- [Button](../src/components/Button/__tests__/Button.visual.test.tsx) +- [Label](../src/components/Label/__tests__//Label.visual.test.tsx) + +## Npm scripts + +- `npm run playwright:install` - install playwright browsers and dependencies +- `npm run playwright` - run tests +- `npm run playwright:update` - update screenshots +- `npm run playwright:clear-cache` - clear cache vite +- `npm run playwright:docker` - run tests using docker +- `npm run playwright:docker:update` - update screenshots using docker +- `npm run playwright:docker:clear-cache` - clear node_modules cache for docker container and clear cache vite diff --git a/playwright/core/expectScreenshotFixture.ts b/playwright/core/expectScreenshotFixture.ts new file mode 100644 index 00000000..7b5b8e57 --- /dev/null +++ b/playwright/core/expectScreenshotFixture.ts @@ -0,0 +1,66 @@ +import {expect} from '@playwright/experimental-ct-react'; + +import type {CaptureScreenshotParams, ExpectScreenshotFixture, PlaywrightFixture} from './types'; + +const defaultParams: CaptureScreenshotParams = { + themes: ['light', 'dark'], +}; + +export const expectScreenshotFixture: PlaywrightFixture = async ( + {page}, + use, + testInfo, +) => { + const expectScreenshot: ExpectScreenshotFixture = async ({ + component, + nameSuffix, + themes: paramsThemes, + ...pageScreenshotOptions + } = defaultParams) => { + const captureScreenshot = async () => { + return (component || page.locator('.playwright-wrapper-test')).screenshot({ + animations: 'disabled', + ...pageScreenshotOptions, + }); + }; + + const nameScreenshot = + testInfo.titlePath.slice(1).join(' ') + (nameSuffix ? ` ${nameSuffix}` : ''); + + const themes = paramsThemes || defaultParams.themes; + + // Wait for loading of all the images + const locators = await page.locator('//img').all(); + await Promise.all( + locators.map((locator) => + locator.evaluate( + (image: HTMLImageElement) => + image.complete || + new Promise((resolve) => image.addEventListener('load', resolve)), + ), + ), + ); + + // Wait for loading fonts + await page.evaluate(() => document.fonts.ready); + + if (themes?.includes('light')) { + await page.emulateMedia({colorScheme: 'light'}); + + expect(await captureScreenshot()).toMatchSnapshot({ + name: `${nameScreenshot} light.png`, + }); + } + + if (themes?.includes('dark')) { + await page.emulateMedia({colorScheme: 'dark'}); + + expect(await captureScreenshot()).toMatchSnapshot({ + name: `${nameScreenshot} dark.png`, + }); + } + }; + + // eslint-disable-next-line react-hooks/rules-of-hooks + await use(expectScreenshot); +}; diff --git a/playwright/core/index.ts b/playwright/core/index.ts new file mode 100644 index 00000000..3216e61a --- /dev/null +++ b/playwright/core/index.ts @@ -0,0 +1,20 @@ +import {test as base} from '@playwright/experimental-ct-react'; + +import {expectScreenshotFixture} from './expectScreenshotFixture'; +import {mountFixture} from './mountFixture'; +import type {Fixtures} from './types'; + +export const test = base.extend({ + mount: mountFixture, + expectScreenshot: expectScreenshotFixture, +}); + +export {expect} from '@playwright/experimental-ct-react'; + +export const smokeTest = (testSuffix: string, body: Parameters[2]) => { + test.skip(({browserName}) => { + return browserName !== 'chromium'; + }, 'Smoke test is only relevant in Chrome'); + + test(`smoke ${testSuffix}`, {tag: ['@smoke']}, body); +}; diff --git a/playwright/core/mountFixture.tsx b/playwright/core/mountFixture.tsx new file mode 100644 index 00000000..d6c6106c --- /dev/null +++ b/playwright/core/mountFixture.tsx @@ -0,0 +1,27 @@ +import type {MountFixture, PlaywrightFixture} from './types'; + +export const mountFixture: PlaywrightFixture = async ({mount: baseMount}, use) => { + const mount: MountFixture = async (component, options) => { + return await baseMount( +
+ {/* Do not scale buttons while clicking. Floating UI might position its elements differently in every test run. */} + + {component} +
, + options, + ); + }; + + // eslint-disable-next-line react-hooks/rules-of-hooks + await use(mount); +}; diff --git a/playwright/core/types.ts b/playwright/core/types.ts new file mode 100644 index 00000000..f8f6ba58 --- /dev/null +++ b/playwright/core/types.ts @@ -0,0 +1,45 @@ +import type * as React from 'react'; + +import type {MountOptions, MountResult} from '@playwright/experimental-ct-react'; +import type { + Locator, + Page, + PageScreenshotOptions, + PlaywrightTestArgs, + PlaywrightTestOptions, + PlaywrightWorkerArgs, + PlaywrightWorkerOptions, + TestFixture, +} from '@playwright/test'; + +interface ComponentFixtures { + mount( + component: React.JSX.Element, + options?: MountOptions & { + width?: number | string; + rootStyle?: React.CSSProperties; + }, + ): Promise; +} + +type PlaywrightTestFixtures = PlaywrightTestArgs & PlaywrightTestOptions & ComponentFixtures; +type PlaywrightWorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions; +type PlaywrightFixtures = PlaywrightTestFixtures & PlaywrightWorkerFixtures; +export type PlaywrightFixture = TestFixture; + +export type Fixtures = { + mount: MountFixture; + expectScreenshot: ExpectScreenshotFixture; +}; + +export type MountFixture = ComponentFixtures['mount']; + +export interface ExpectScreenshotFixture { + (props?: CaptureScreenshotParams): Promise; +} + +export interface CaptureScreenshotParams extends PageScreenshotOptions { + nameSuffix?: string; + component?: Locator | Page; + themes?: Array<'light' | 'dark'>; +} diff --git a/playwright/playwright.config.ts b/playwright/playwright.config.ts new file mode 100644 index 00000000..6023d714 --- /dev/null +++ b/playwright/playwright.config.ts @@ -0,0 +1,95 @@ +import {resolve} from 'path'; + +import type {PlaywrightTestConfig} from '@playwright/experimental-ct-react'; +import {defineConfig, devices} from '@playwright/experimental-ct-react'; +import react from '@vitejs/plugin-react'; + +function pathFromRoot(p: string) { + return resolve(__dirname, '../', p); +} + +const reporter: PlaywrightTestConfig['reporter'] = []; + +reporter.push( + ['list'], + [ + 'html', + { + open: process.env.CI ? 'never' : 'on-failure', + outputFolder: resolve( + process.cwd(), + process.env.IS_DOCKER ? 'playwright-report-docker' : 'playwright-report', + ), + }, + ], +); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +const config: PlaywrightTestConfig = { + testDir: pathFromRoot('src'), + testMatch: '**/__tests__/*.visual.test.tsx', + updateSnapshots: process.env.UPDATE_REQUEST ? 'all' : 'missing', + snapshotPathTemplate: + '{testDir}/{testFileDir}/../__snapshots__/{testFileName}-snapshots/{arg}{-projectName}-linux{ext}', + /* Maximum time one test can run for. */ + timeout: 10 * 1000, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: Boolean(process.env.CI), + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 8 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter, + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + testIdAttribute: 'data-qa', + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + headless: true, + screenshot: 'only-on-failure', + timezoneId: 'UTC', + ctCacheDir: process.env.IS_DOCKER ? '.cache-docker' : '.cache', + ctViteConfig: { + plugins: [react()], + css: { + preprocessorOptions: { + scss: { + api: 'modern-compiler', + }, + }, + }, + resolve: { + alias: { + '~playwright': resolve(__dirname), + '~@gravity-ui/uikit/styles/mixins': '@gravity-ui/uikit/styles/mixins', + '~@doc-tools/transform/dist/css/yfm.css': + '@doc-tools/transform/dist/css/yfm.css', + }, + }, + }, + }, + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + deviceScaleFactor: 2, + }, + }, + { + name: 'webkit', + use: { + ...devices['Desktop Safari'], + deviceScaleFactor: 2, + }, + }, + ], +}; + +export default defineConfig(config); diff --git a/playwright/playwright/index.html b/playwright/playwright/index.html new file mode 100644 index 00000000..f6933f52 --- /dev/null +++ b/playwright/playwright/index.html @@ -0,0 +1,12 @@ + + + + + + Test MarkdownEditor page + + +
+ + + diff --git a/playwright/playwright/index.scss b/playwright/playwright/index.scss new file mode 100644 index 00000000..a2020a78 --- /dev/null +++ b/playwright/playwright/index.scss @@ -0,0 +1 @@ +@import '@gravity-ui/uikit/styles/styles.scss'; diff --git a/playwright/playwright/index.tsx b/playwright/playwright/index.tsx new file mode 100644 index 00000000..f88ea041 --- /dev/null +++ b/playwright/playwright/index.tsx @@ -0,0 +1,25 @@ +import { + MobileProvider, + ThemeProvider, + Toaster, + ToasterComponent, + ToasterProvider, +} from '@gravity-ui/uikit'; +import {beforeMount} from '@playwright/experimental-ct-react/hooks'; + +import './index.scss'; + +const toaster = new Toaster(); + +beforeMount(async ({App}) => { + return ( + + + + + + + + + ); +}); diff --git a/scripts/playwright-docker.sh b/scripts/playwright-docker.sh new file mode 100755 index 00000000..22b7c84d --- /dev/null +++ b/scripts/playwright-docker.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +set -euo pipefail + +IMAGE_NAME="mcr.microsoft.com/playwright" +IMAGE_TAG="v1.49.0-jammy" # This version have to be synchronized with playwright version from package.json + +NODE_MODULES_CACHE_DIR="$HOME/.cache/markdown-editor-playwright-docker-node-modules" + +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +run_command() { + $CONTAINER_TOOL run --rm --network host -it -w /work \ + -v $(pwd):/work \ + -v "$NODE_MODULES_CACHE_DIR:/work/node_modules" \ + -e IS_DOCKER=1 \ + "$IMAGE_NAME:$IMAGE_TAG" \ + /bin/bash -c "$1" +} + +if command_exists docker; then + CONTAINER_TOOL="docker" +elif command_exists podman; then + CONTAINER_TOOL="podman" +else + echo "Neither Docker nor Podman is installed on the system." + exit 1 +fi + +if [[ "$1" = "clear-cache" ]]; then + rm -rf "$NODE_MODULES_CACHE_DIR" + rm -rf "./playwright/.cache-docker" + exit 0 +fi + +if [[ ! -d "$NODE_MODULES_CACHE_DIR" ]]; then + mkdir -p "$NODE_MODULES_CACHE_DIR" + run_command 'npm ci' +fi + +run_command "$1" diff --git a/src/bundle/__tests__/MarkdownEditorView.visual.test.tsx b/src/bundle/__tests__/MarkdownEditorView.visual.test.tsx new file mode 100644 index 00000000..8774ef60 --- /dev/null +++ b/src/bundle/__tests__/MarkdownEditorView.visual.test.tsx @@ -0,0 +1,12 @@ +import {test} from '~playwright/core'; +import {MarkdownEditorView} from '../MarkdownEditorView'; +import {useMarkdownEditor} from '../useMarkdownEditor'; + +test.describe('MarkdownEditorView', () => { + test('default', async ({mount, expectScreenshot}) => { + const editor = useMarkdownEditor({}); + await mount(); + + await expectScreenshot(); + }); +}); diff --git a/src/stories/tests-factory/create-smoke-scenarios.test.ts b/src/stories/tests-factory/create-smoke-scenarios.test.ts new file mode 100644 index 00000000..b24993b1 --- /dev/null +++ b/src/stories/tests-factory/create-smoke-scenarios.test.ts @@ -0,0 +1,109 @@ +import {createSmokeScenarios} from './create-smoke-scenarios'; + +test('regular', () => { + const smokeScenarios = createSmokeScenarios( + { + theme: 'theme-1', + label: 'label-1', + }, + { + theme: ['theme-2', 'theme-3'], + label: ['label-2', 'label-3'], + }, + ); + + expect(smokeScenarios).toEqual([ + [ + '[default]', + { + label: 'label-1', + theme: 'theme-1', + }, + ], + [ + '[theme: theme-2]', + { + label: 'label-1', + theme: 'theme-2', + }, + ], + [ + '[theme: theme-3]', + { + label: 'label-1', + theme: 'theme-3', + }, + ], + [ + '[label: label-2]', + { + label: 'label-2', + theme: 'theme-1', + }, + ], + [ + '[label: label-3]', + { + label: 'label-3', + theme: 'theme-1', + }, + ], + ]); +}); + +test('with scenario name', () => { + const smokeScenarios = createSmokeScenarios( + { + theme: 'theme-1', + label: 'label-1', + }, + { + theme: [ + ['name-theme-2', 'theme-2'], + ['name-theme-3', 'theme-3'], + ], + label: [ + ['name-label-2', 'label-2'], + ['name-label-3', 'label-3'], + ], + }, + ); + + expect(smokeScenarios).toEqual([ + [ + '[default]', + { + label: 'label-1', + theme: 'theme-1', + }, + ], + [ + '[theme: name-theme-2]', + { + label: 'label-1', + theme: 'theme-2', + }, + ], + [ + '[theme: name-theme-3]', + { + label: 'label-1', + theme: 'theme-3', + }, + ], + [ + '[label: name-label-2]', + { + label: 'label-2', + theme: 'theme-1', + }, + ], + [ + '[label: name-label-3]', + { + label: 'label-3', + theme: 'theme-1', + }, + ], + ]); +}); diff --git a/src/stories/tests-factory/create-smoke-scenarios.ts b/src/stories/tests-factory/create-smoke-scenarios.ts new file mode 100644 index 00000000..a4d5b0d3 --- /dev/null +++ b/src/stories/tests-factory/create-smoke-scenarios.ts @@ -0,0 +1,72 @@ +import type {Cases, CasesWithName, Scenario, ScenarioName} from './models'; + +interface Options { + scenarioName?: string; +} + +function checkIsCasesWithName(cases: CasesWithName | Cases): cases is CasesWithName { + const firstCase = cases[0] || null; + return Array.isArray(firstCase) && firstCase.length === 2; +} + +export const createSmokeScenarios = ( + baseProps: Props, + propsCases: Partial<{ + [K in keyof Props]: CasesWithName | Cases; + }>, + options?: Options, +) => { + const scenarioName: ScenarioName = `${ + options?.scenarioName ? ` ${options?.scenarioName} ` : '' + }`; + + const scenarios: Array> = [ + [ + `${scenarioName}[default]`, + { + ...baseProps, + }, + ], + ]; + + const propNames = Object.keys(propsCases) as Array; + propNames.forEach((propName) => { + const propCases = propsCases[propName]; + if (!propCases) { + return; + } + + if (checkIsCasesWithName(propCases)) { + propCases.forEach((propCase) => { + const [caseName, caseProps] = propCase; + + scenarios.push([ + `${scenarioName}[${propName as string}: ${caseName}]`, + { + ...baseProps, + [propName]: caseProps, + }, + ]); + }); + } else { + propCases.forEach((propCase) => { + const hasStringifyMethod = (propCase as any)?.toString; + if (!hasStringifyMethod) { + throw new Error( + 'The case value does not have a method "toString", use case with name.', + ); + } + + scenarios.push([ + `${scenarioName}[${propName as string}: ${(propCase as any)?.toString()}]`, + { + ...baseProps, + [propName]: propCase, + }, + ]); + }); + } + }); + + return scenarios; +}; diff --git a/src/stories/tests-factory/models.ts b/src/stories/tests-factory/models.ts new file mode 100644 index 00000000..cf87a970 --- /dev/null +++ b/src/stories/tests-factory/models.ts @@ -0,0 +1,6 @@ +export type CaseName = string; +export type Cases = Array; +export type CasesWithName = Array<[CaseName, T]>; + +export type ScenarioName = string; +export type Scenario = [ScenarioName, T]; diff --git a/test-results/.last-run.json b/test-results/.last-run.json new file mode 100644 index 00000000..5fca3f84 --- /dev/null +++ b/test-results/.last-run.json @@ -0,0 +1,4 @@ +{ + "status": "failed", + "failedTests": [] +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index aac0a39a..f6d5781b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,9 +17,10 @@ "#core": ["./src/core"], "#cm/*": ["./src/cm/*"], "#pm/*": ["./src/pm/*"], + "~playwright/*": ["./playwright/*"], "src/*": ["./src/*"] } }, "include": ["src/**/*", "demo/**/*", "tests/**/*"], - "exclude": ["build"] + "exclude": ["build", "**/__stories__", "**/__tests__"] }