diff --git a/.husky/pre-commit b/.husky/pre-commit index 263cce30..d37daa07 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -npx nano-staged +npx --no-install lint-staged diff --git a/package-lock.json b/package-lock.json index 8fe460f1..ff229ca2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,6 +59,7 @@ "jest": "^29.7.0", "jest-environment-jsdom": "^29.5.0", "jest-transform-css": "^6.0.1", + "lint-staged": "15.5.2", "monaco-editor": "^0.30.1", "monaco-editor-webpack-plugin": "^6.0.0", "npm-run-all": "^4.1.5", @@ -8317,12 +8318,13 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -8753,6 +8755,136 @@ "node": ">=0.10.0" } }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-truncate/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -9607,12 +9739,13 @@ "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -10186,6 +10319,19 @@ "node": ">=6" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -10649,29 +10795,6 @@ } } }, - "node_modules/eslint-import-resolver-typescript/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/eslint-import-resolver-typescript/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, "node_modules/eslint-module-utils": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", @@ -11404,6 +11527,13 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -11862,10 +11992,11 @@ "optional": true }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -12456,6 +12587,19 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -14455,6 +14599,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -17174,6 +17319,410 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/lint-staged": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.2.tgz", + "integrity": "sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.4.1", + "commander": "^13.1.0", + "debug": "^4.4.0", + "execa": "^8.0.1", + "lilconfig": "^3.1.3", + "listr2": "^8.2.5", + "micromatch": "^4.0.8", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.7.0" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/lint-staged/node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/lint-staged/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/lint-staged/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/lilconfig": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.4.tgz", + "integrity": "sha512-zJZyBmQhArDRefuWTypZxwDpFDIYmrj8ypSBvwGD5P13Y95s6DjzKgu4DVdGbeQa5PDEsAOCAPNk4X+QOR006w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lint-staged/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lint-staged/node_modules/pidtree": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.1.tgz", + "integrity": "sha512-e0F9AOF1JMrCfBsyJOwU9lNvQ0WtXTq0j/4jk0BQ5JSI9VAybPXmDpPRw/2FQ3e5d3ZFN1mLh7jW99m/jjaptw==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/lint-staged/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lint-staged/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lint-staged/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/lint-staged/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/lint-staged/node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/listr2": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", + "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -17343,6 +17892,160 @@ "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", "dev": true }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -17929,12 +18632,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -17971,6 +18675,19 @@ "node": ">=6" } }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -18087,10 +18804,11 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" }, "node_modules/mute-stdout": { "version": "1.0.1", @@ -20417,6 +21135,52 @@ "node": ">=10" } }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", @@ -20436,6 +21200,13 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, "node_modules/rimraf": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", @@ -21545,6 +22316,16 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, "node_modules/string-hash": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", @@ -22530,6 +23311,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -29951,12 +30733,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browser-assert": { @@ -30239,6 +31021,81 @@ } } }, + "cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "requires": { + "restore-cursor": "^5.0.0" + } + }, + "cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "requires": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true + }, + "ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true + }, + "emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true + }, + "slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + } + }, + "string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "requires": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + } + }, + "strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "requires": { + "ansi-regex": "^6.2.2" + } + } + } + }, "clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -30903,12 +31760,12 @@ "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" }, "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "requires": { - "ms": "2.1.2" + "ms": "^2.1.3" } }, "decamelize": { @@ -31343,6 +32200,12 @@ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true }, + "environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -31968,23 +32831,6 @@ "is-bun-module": "^1.0.2", "stable-hash": "^0.0.4", "tinyglobby": "^0.2.12" - }, - "dependencies": { - "debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, - "requires": { - "ms": "^2.1.3" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - } } }, "eslint-module-utils": { @@ -32244,6 +33090,12 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true + }, "events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -32609,9 +33461,9 @@ "optional": true }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -33063,6 +33915,12 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "dev": true + }, "get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -36559,6 +37417,241 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "lint-staged": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.2.tgz", + "integrity": "sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==", + "dev": true, + "requires": { + "chalk": "^5.4.1", + "commander": "^13.1.0", + "debug": "^4.4.0", + "execa": "^8.0.1", + "lilconfig": "^3.1.3", + "listr2": "^8.2.5", + "micromatch": "^4.0.8", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.7.0" + }, + "dependencies": { + "chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true + }, + "commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + } + }, + "get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true + }, + "human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true + }, + "is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true + }, + "lilconfig": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.4.tgz", + "integrity": "sha512-zJZyBmQhArDRefuWTypZxwDpFDIYmrj8ypSBvwGD5P13Y95s6DjzKgu4DVdGbeQa5PDEsAOCAPNk4X+QOR006w==", + "dev": true + }, + "mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true + }, + "npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "requires": { + "path-key": "^4.0.0" + }, + "dependencies": { + "path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true + } + } + }, + "onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "requires": { + "mimic-fn": "^4.0.0" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "pidtree": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.1.tgz", + "integrity": "sha512-e0F9AOF1JMrCfBsyJOwU9lNvQ0WtXTq0j/4jk0BQ5JSI9VAybPXmDpPRw/2FQ3e5d3ZFN1mLh7jW99m/jjaptw==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, + "strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "dev": true + } + } + }, + "listr2": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", + "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==", + "dev": true, + "requires": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true + }, + "ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true + }, + "emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true + }, + "string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "requires": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + } + }, + "strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "requires": { + "ansi-regex": "^6.2.2" + } + }, + "wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "requires": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + } + } + } + }, "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -36707,6 +37800,98 @@ "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", "dev": true }, + "log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "requires": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "dependencies": { + "ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "requires": { + "environment": "^1.0.0" + } + }, + "ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true + }, + "ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true + }, + "emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "requires": { + "get-east-asian-width": "^1.3.1" + } + }, + "slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "requires": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + } + }, + "string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "requires": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + } + }, + "strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "requires": { + "ansi-regex": "^6.2.2" + } + }, + "wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "requires": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + } + } + } + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -37153,12 +38338,12 @@ "dev": true }, "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "requires": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" } }, @@ -37183,6 +38368,12 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, + "mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true + }, "min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -37272,9 +38463,9 @@ } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "mute-stdout": { @@ -38969,6 +40160,33 @@ "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true }, + "restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "requires": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "dependencies": { + "onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "requires": { + "mimic-function": "^5.0.0" + } + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + } + } + }, "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", @@ -38981,6 +40199,12 @@ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true }, + "rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true + }, "rimraf": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", @@ -39795,6 +41019,12 @@ "safe-buffer": "~5.2.0" } }, + "string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true + }, "string-hash": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", diff --git a/package.json b/package.json index 274d4c62..755371ec 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,9 @@ "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" + "playwright:docker:clear-cache": "./scripts/playwright-docker.sh clear-cache", + "playwright:schema": "playwright test --config=playwright/playwright.config.ts src/lib/unstable/", + "playwright:legacy": "playwright test --config=playwright/playwright.config.ts src/lib/kit/" }, "dependencies": { "@bem-react/classname": "^1.8.0", @@ -94,6 +96,7 @@ "jest": "^29.7.0", "jest-environment-jsdom": "^29.5.0", "jest-transform-css": "^6.0.1", + "lint-staged": "15.5.2", "monaco-editor": "^0.30.1", "monaco-editor-webpack-plugin": "^6.0.0", "npm-run-all": "^4.1.5", @@ -123,8 +126,8 @@ "react-final-form": "^6.5.3", "react-is": "^17.0.0 || ^18.0.0 || ^19.0.0" }, - "nano-staged": { - "*.{scss}": [ + "lint-staged": { + "*.scss": [ "stylelint --fix --quiet" ], "*.{js,jsx,ts,tsx}": [ diff --git a/playwright/README.md b/playwright/README.md index d0d50c87..15662eea 100644 --- a/playwright/README.md +++ b/playwright/README.md @@ -1,110 +1,204 @@ -# Playwright Test Component +# Playwright component tests -## How to write a test +`npm run playwright` runs both the existing DynamicField / DynamicView suite and +new SchemaRenderer tests. SchemaRenderer is imported from `src/unstable.ts`; this +branch uses the dependencies of main, including UIKit 7. -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: - - ```ts - import React from 'react'; - - import {TEST_SPEC} from './helpers'; - - import {test} from '~playwright/core'; - import {DynamicForm} from '~playwright/core/DynamicForm'; - - test('Name test', async ({mount, expectScreenshot}) => { - //mounting a component - await mount(); - - //screenshot - await expectScreenshot(); - }); - ``` - - or if you need to do any actions on the component - - ```ts - import React from 'react'; - - import {TEST_SPEC} from './helpers'; - - import {test} from '~playwright/core'; - import {DynamicForm} from '~playwright/core/DynamicForm'; - - test('Name test', async ({mount, expectScreenshot}) => { - //mounting a component - const component = await mount(); - - await component.getByRole('button').click(); - - //screenshot - await expectScreenshot(); - }); - ``` - - 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 +```sh +npm run playwright:schema # SchemaRenderer only +npm run playwright:legacy # Existing DynamicField / DynamicView only +npm run playwright # Both suites, as in CI ``` -> `npm run playwright:install` command must be run only once on initial setup - -5. Update screenshots if needed +## Where a test lives + +Tests and fixtures belong to the component they exercise. For example: + +```text +src/lib/unstable/kit/form-entities/StringInput/ + StringInput.tsx + __tests__/ + StringInput.visual.test.tsx + helpers.ts + __snapshots__/ + StringInput.visual.test.tsx-snapshots/ + ...-light-chromium-linux.png + ...-dark-chromium-linux.png + ...-light-webkit-linux.png + ...-dark-webkit-linux.png +``` - ```shell - npm run playwright:update - ``` +Overview components have their own suites in `overview-entities`. Components +shared by Form and Overview are tested in both modes in `entities`. Layout tests +are next to the corresponding layout. + +## How a test runs + +1. Playwright collects `**/__tests__/*.visual.test.tsx` and runs each scenario in + Chromium and WebKit. Vite builds the React fixtures for the browser. +2. `playwright/playwright/index.tsx` provides UIKit theme/mobile/toaster context. +3. `mount` from `~playwright/core` adds the screenshot wrapper. The test passes + its schema, value and explicit settings to `SchemaForm`. +4. `SchemaForm` creates a real Final Form and renders the real SchemaRenderer + with the kit config, error messages and field name `input`. It does not set + fallback values for size, view, mode, value, width or renderer settings. +5. The scenario checks a visible control/value and, for interactions, the form + state. Hidden `form-value` and `form-value-type` outputs expose the actual Final Form + value and distinguish Date, DateTime, strings and other values. They are not + part of the screenshot. +6. `expectScreenshot` switches to light and dark themes, waits for each theme, + captures the component and compares it with that component's Linux reference. + Popups stay in their real portals, but fit within the grid's screenshot bounds. + +`SchemaFormProps` makes the renderer settings required at compile time. A test +must explicitly provide `size`, `view`, `mode`, `coerceInitialValues`, +`jsonDefaultValues`, `validateOnBlur`, `userContext`, `value`, `width` and +`showSubmit`. `validateOnBlur` is passed to both Final Form and SchemaRenderer. +A value of `undefined` is allowed, but must be written explicitly. + +Monaco has a local browser fixture that supplies the real MonacoEditor and its +workers through userContext; the editor is not mocked. Its wrapper uses UIKit's +resolved theme (`useThemeType`) to select Monaco's `vs` or `vs-dark` theme, including +when the test switches the system color scheme between screenshots. + +Data-only helpers import runtime enums directly from `core/constants`. Importing +them through the full `core` barrel also loads browser components in the Node.js +test collector. Type-only imports can use the public exports because TypeScript +removes them from the runtime code. + +## Coverage matrix + +Each entity suite renders all 64 labelled variants from +`playwright/core/schemaRendererVariants.ts` in one vertical column: + +- Seven registered layouts: accordeon, card, cell, column, row, section, + transparent; plus explicit `layout: undefined` (no layout). +- Every layout option is tested with all four sizes: s, m, l, xl. +- Each layout × size pair is rendered with both fit and stretch. The order is + layout → size → view: no-layout/s/fit, no-layout/s/stretch, no-layout/m/fit, + and so on, followed by the next layout. +- Shared entities repeat the matrix in both Form and Overview. Form-only and + Overview-only components use their respective mode. +- Dedicated layout suites check every size × view combination in each supported + mode. Cell and transparent get distinct empty-value checks. + +`SchemaFormGrid` arranges independent forms and labels their settings. It does not +supply default renderer props. Each test still passes all +props explicitly and checks every cell before taking the screenshot. Composite +values must fit inside their own cell, not just somewhere inside the grid. + +The same grid also contains labelled cells for specific props, empty/disabled +states, validation errors and results of interactions. Each cell has its own +explicit props. The test performs the relevant actions and assertions inside +that cell before capturing the whole grid. When a scenario has several visual +checkpoints, separate cells reproduce those checkpoints independently. + +One grid produces four reference images: light/dark × Chromium/WebKit, instead +of four images per variant or state. Form and Overview have separate grids. +NumberInput, for example, has four images containing its 64 variants plus four +states: disabled, edited, empty and validation error. Tests and references remain +next to their components. + +SelectInput and MultiSelectInput render their matrix with `entityProps.open: true`. +All menus are visible together, including a separate filtered example. Selection, +clearing and filtering still run on independent, uncontrolled cells: these checks +use the popup referenced by the control's `aria-controls`, rather than a global +option locator. + +Every suite explicitly sets `caseHeight` in CSS pixels. This fixes the whole frame, +including its label, border and padding; all sizes and views have the same height. +The height is chosen from browser measurements of the component's tallest variant: +for example, NumberInput uses 220 px, SelectInput uses 360 px and Monaco uses +420 px. Form/Overview can have different frame heights. Ordinary inputs do not +reserve as much space as arrays, editors or open menus. A case can supply +`height` to reserve extra space for an open calendar, color palette or a longer +validation state. These constants do not adapt to the rendered component: if it +grows, the following frames stay at the same coordinates. + +Content is not clipped. Changes in its dimensions and visible overflow are caught +by comparing screenshots; there is no separate frame-size assertion. +The calendar and color palette also have reserved cells in the common column. Helpers in `popupGrid.ts` +fit the viewport to the grid and check that each popup stays inside its own cell +and does not cover headings, labels, descriptions or controls. This prevents overlap with neighbouring +forms and menus. These components have four screenshots per supported mode; +Form and Overview remain separate. + +Description tooltips keep four separate screenshots: their default placement +covers the field below the label, so putting them into a grid would hide content. +The expanded Monaco dialog also keeps four separate screenshots: it is a modal +over the page and cannot coexist with the other cells without covering them. +Functional checks can also remain separate, without additional screenshots. + +A grid captures simultaneous states, rather than each intermediate focus/hover +appearance: browser focus can belong to only one cell at a time. The actions and +value assertions still run for every cell. This targets the components' own +behavior; it does not enumerate every inherited UIKit prop. + +SchemaRenderer suites import `schemaTest` from `playwright/core/index.ts`. +It inherits the same mount/screenshot fixtures and sets `deviceScaleFactor: 1`. +Together with `scale: 'css'`, this gives one image pixel per CSS pixel. Tall +columns otherwise allocate Retina-sized painting surfaces even when the final +PNG is scaled down. This changes the pixel density, not the components' CSS sizes +or props. Existing DynamicField / DynamicView suites still use `test` with their +original browser settings. + +The test timeout includes mounting, interactions and screenshots in both themes; +it is not a per-click allowance. Local runs and CI use two workers to bound memory +consumption when the matrix includes Monaco. Monaco scenarios run sequentially within each +browser project: its grids mount 64 editor variants plus state examples, so Form +and Overview must not compete for resources. + +## Generate and verify screenshots + +Use the same **Linux amd64** image as CI: +`mcr.microsoft.com/playwright:v1.40.0-jammy`. Do not generate macOS screenshots +under filenames ending in `linux`. + +With Docker or Podman running, allocate about 6 GiB to its Linux VM for a +two-worker run. The component bundle also needs memory before browser workers +start, so reducing `--workers` alone does not prevent build-time heap errors. + +The commands below use the same two-worker parallelism as CI: + +```sh +# Generate only the new suite's references. Existing screenshots stay unchanged. +./scripts/playwright-docker.sh 'npm run playwright:schema -- --workers=2 --update-snapshots' + +# Verify without changing any references. +./scripts/playwright-docker.sh 'npm run playwright:schema -- --workers=2' +./scripts/playwright-docker.sh 'npm run playwright -- --workers=2' +``` - Or +The script caches Linux node_modules separately. It runs `npm ci` when Playwright +is missing, the previous installation did not finish, or package-lock.json changes. +Use `npm run playwright:docker:clear-cache` to explicitly reset the cache. - ```shell - npm run playwright:docker:update - ``` +On a Linux amd64 machine with Playwright browsers installed, the equivalent is: -6. In the folder `__snapshots__`, which is on the same level as the `__tests__` folder, the folder `.visual.test.tsx-snapshots`, will contain screenshots +```sh +npm ci +npm run playwright:schema -- --update-snapshots +npm run playwright:schema +``` -## Description of possible commands: +Normal runs fail on missing or changed references. Only an explicit update +command writes references. Review generated images before committing them. +The HTML report lives in `playwright-report` (`playwright-report-docker` when +`IS_DOCKER=1`); CI uploads its report as an artifact. -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) +To run one component, append its path, e.g.: -## Test examples +```sh +npm run playwright -- src/lib/unstable/kit/form-entities/StringInput/ --project=chromium +``` -- [ArrayBase](../src/lib/kit/components/Inputs/ArrayBase/__tests__/ArrayBase.visual.test.tsx) -- [Text](../src/lib/kit/components/Inputs/Text/__tests__/Text.visual.test.tsx) -- [TextContent](../src/lib/kit/components/Inputs/TextContent/__tests__/TextContent.visual.test.tsx) -- [TextArea](../src/lib/kit/components/Inputs/TextArea/__tests__/TextArea.visual.test.tsx) +## Date and scale scenarios -## Npm scripts +DateInput covers the default ISO-string mode without an explicit `outputFormat`, +custom date patterns, Date, DateTime and timestamp outputs. The explicit +`outputFormat: 'string'` scenario is not included. -- `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 +NumberWithScaleInput keeps its unit selector enabled when the numeric input is +disabled. The test switches units, verifies that the stored base value stays the +same and captures the disabled input before and after the switch. There are no +expected-failure annotations in these suites. diff --git a/playwright/core/SchemaForm.tsx b/playwright/core/SchemaForm.tsx new file mode 100644 index 00000000..260ed387 --- /dev/null +++ b/playwright/core/SchemaForm.tsx @@ -0,0 +1,49 @@ +import React from 'react'; + +import {isDateTime} from '@gravity-ui/date-utils'; +import {Button} from '@gravity-ui/uikit'; +import noop from 'lodash/noop'; +import {Form} from 'react-final-form'; + +import {SchemaRenderer, config, errorMessages} from '../../src/unstable'; +import type {FieldValue, SchemaRendererProps} from '../../src/unstable'; + +export interface SchemaFormProps + extends Required> { + value: FieldValue; + showSubmit: boolean; + width: number; +} + +// Only infrastructure lives here. Each test supplies its renderer settings explicitly. +export const SchemaForm = ({value, showSubmit, width, ...rendererProps}: SchemaFormProps) => ( +
+
+ {({handleSubmit, values}) => ( + + + {showSubmit && } + + + + )} + +
+); diff --git a/playwright/core/SchemaFormGrid.tsx b/playwright/core/SchemaFormGrid.tsx new file mode 100644 index 00000000..c8015f3d --- /dev/null +++ b/playwright/core/SchemaFormGrid.tsx @@ -0,0 +1,47 @@ +import React from 'react'; + +import {SchemaForm, type SchemaFormProps} from './SchemaForm'; + +export interface SchemaFormGridProps { + /** Fixed outer height in CSS pixels, shared by all sizes and views. */ + caseHeight: number; + cases: Array<{ + label: string; + props: SchemaFormProps; + /** Override for a state that needs extra room, such as an open calendar. */ + height?: number; + }>; +} + +// Fixed frames keep later cases in place if a component changes its dimensions. +// Overflow stays visible in the screenshot instead of being clipped away. +export const SchemaFormGrid = ({caseHeight, cases}: SchemaFormGridProps) => ( +
+ {cases.map(({label, props, height}) => ( +
+
+ {label} +
+ +
+ ))} +
+); diff --git a/playwright/core/expectInsideScreenshot.ts b/playwright/core/expectInsideScreenshot.ts new file mode 100644 index 00000000..0bc135a3 --- /dev/null +++ b/playwright/core/expectInsideScreenshot.ts @@ -0,0 +1,28 @@ +import {expect} from '@playwright/experimental-ct-react'; +import type {Locator} from '@playwright/test'; + +export const expectInsideScreenshot = async (target: Locator, frame: Locator) => { + await expect(target).toBeVisible(); + await expect + .poll( + async () => { + const [targetBox, frameBox] = await Promise.all([ + target.boundingBox(), + frame.boundingBox(), + ]); + if (!targetBox || !frameBox) { + return false; + } + return ( + targetBox.width > 0 && + targetBox.height > 0 && + targetBox.x >= frameBox.x && + targetBox.y >= frameBox.y && + targetBox.x + targetBox.width <= frameBox.x + frameBox.width && + targetBox.y + targetBox.height <= frameBox.y + frameBox.height + ); + }, + {message: 'The entity value must fit inside the screenshot frame'}, + ) + .toBe(true); +}; diff --git a/playwright/core/expectScreenshotFixture.ts b/playwright/core/expectScreenshotFixture.ts index 73fab3ae..e1d0e63d 100644 --- a/playwright/core/expectScreenshotFixture.ts +++ b/playwright/core/expectScreenshotFixture.ts @@ -4,15 +4,23 @@ import type {ExpectScreenshotFixture, PlaywrightFixture} from './types'; export const expectScreenshotFixture: PlaywrightFixture = async ( {page}, - use, + run, testInfo, ) => { const expectScreenshot: ExpectScreenshotFixture = async ({ component, screenshotName, + fullPage, ...pageScreenshotOptions } = {}) => { const captureScreenshot = async () => { + if (fullPage) { + return page.screenshot({ + animations: 'disabled', + ...pageScreenshotOptions, + fullPage, + }); + } return (component || page.locator('.playwright-wrapper-test')).screenshot({ animations: 'disabled', ...pageScreenshotOptions, @@ -21,16 +29,20 @@ export const expectScreenshotFixture: PlaywrightFixture const nameScreenshot = testInfo.titlePath.slice(1).join(' '); + await page.emulateMedia({colorScheme: 'light'}); + await expect(page.locator('body')).toHaveClass(/g-root_theme_light/); + expect(await captureScreenshot()).toMatchSnapshot({ name: `${screenshotName || nameScreenshot} light.png`, }); await page.emulateMedia({colorScheme: 'dark'}); + await expect(page.locator('body')).toHaveClass(/g-root_theme_dark/); expect(await captureScreenshot()).toMatchSnapshot({ name: `${screenshotName || nameScreenshot} dark.png`, }); }; - await use(expectScreenshot); + await run(expectScreenshot); }; diff --git a/playwright/core/index.ts b/playwright/core/index.ts index 62cc1fe8..e00f0b0b 100644 --- a/playwright/core/index.ts +++ b/playwright/core/index.ts @@ -8,3 +8,7 @@ export const test = base.extend({ mount: mountFixture, expectScreenshot: expectScreenshotFixture, }); + +// The full SchemaRenderer column is captured at CSS resolution. Avoid allocating +// Retina-sized browser surfaces for tens of thousands of pixels in height. +export const schemaTest = test.extend({deviceScaleFactor: 1}); diff --git a/playwright/core/popupGrid.ts b/playwright/core/popupGrid.ts new file mode 100644 index 00000000..24496aa3 --- /dev/null +++ b/playwright/core/popupGrid.ts @@ -0,0 +1,99 @@ +import {expect} from '@playwright/experimental-ct-react'; +import type {Locator, Page} from '@playwright/test'; + +// Floating UI positions portals against the viewport. Fit the whole grid before +// checking their positions, so off-screen rows cannot flip into neighbouring cells. +export const fitGridInViewport = async (page: Page, grid: Locator) => { + const box = await grid.locator('.playwright-wrapper-test').boundingBox(); + if (!box) { + throw new Error('The screenshot grid must be mounted before fitting the viewport'); + } + await page.setViewportSize({ + width: Math.ceil(box.width + 40), + height: Math.ceil(box.height + 40), + }); + await page.evaluate(() => window.scrollTo(0, 0)); +}; + +export const getSelectPopup = async (page: Page, control: Locator) => { + await expect(control).toHaveAttribute('aria-expanded', 'true'); + const id = await control.getAttribute('aria-controls'); + expect(id).toBeTruthy(); + return page.locator('.g-select-popup').filter({has: page.locator(`[id="${id}"]`)}); +}; + +export const expectPopupInCell = async (popup: Locator, cell: Locator, control: Locator) => { + await expect(popup).toBeVisible(); + await expect + .poll( + async () => { + const [popupBox, controlBox] = await Promise.all([ + popup.boundingBox(), + control.boundingBox(), + ]); + if (!popupBox || !controlBox) { + return {insideCell: false, covered: ['Popup or control is not visible']}; + } + // Measure visible text and inputs, not background containers such + // as Card. One browser evaluation checks the entire cell. + return cell.evaluate( + (element, {box, trigger}) => { + const intersects = (rect: { + x: number; + y: number; + width: number; + height: number; + }) => + rect.width > 0 && + rect.height > 0 && + rect.x < box.x + box.width && + rect.x + rect.width > box.x && + rect.y < box.y + box.height && + rect.y + rect.height > box.y; + const frame = element.getBoundingClientRect(); + // Containment also rules out overlap with neighbouring cells + // and their portals, since the grid cells do not intersect. + const insideCell = + box.width > 0 && + box.height > 0 && + box.x >= frame.left && + box.y >= frame.top && + box.x + box.width <= frame.right && + box.y + box.height <= frame.bottom; + const covered = intersects(trigger) ? ['Popup trigger'] : []; + const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT); + while (walker.nextNode()) { + const node = walker.currentNode; + const text = node.textContent?.trim(); + if ( + !text || + !node.parentElement || + getComputedStyle(node.parentElement).visibility === 'hidden' + ) { + continue; + } + const range = document.createRange(); + range.selectNodeContents(node); + if (Array.from(range.getClientRects()).some(intersects)) { + covered.push(text); + } + } + for (const input of element.querySelectorAll( + 'input, textarea, button, [role="combobox"]', + )) { + if ( + getComputedStyle(input).visibility !== 'hidden' && + intersects(input.getBoundingClientRect()) + ) { + covered.push(input.getAttribute('aria-label') || input.tagName); + } + } + return {insideCell, covered}; + }, + {box: popupBox, trigger: controlBox}, + ); + }, + {message: 'The popup must fit its cell without covering text or controls'}, + ) + .toEqual({insideCell: true, covered: []}); +}; diff --git a/playwright/core/schemaRendererVariants.ts b/playwright/core/schemaRendererVariants.ts new file mode 100644 index 00000000..a112a752 --- /dev/null +++ b/playwright/core/schemaRendererVariants.ts @@ -0,0 +1,31 @@ +import type {SchemaRendererProps} from '../../src/lib/unstable/core/SchemaRenderer'; + +export const rendererSizes = ['s', 'm', 'l', 'xl'] as const satisfies ReadonlyArray< + SchemaRendererProps['size'] +>; +export const rendererViews = ['fit', 'stretch'] as const satisfies ReadonlyArray< + SchemaRendererProps['view'] +>; +export const rendererLayouts = [ + undefined, + 'accordeon', + 'card', + 'cell', + 'column', + 'row', + 'section', + 'transparent', +] as const; + +// Full layout × size × view product, ordered for a single vertical column. +// These are named test cases, not fallback values supplied by the mount fixture. +export const rendererVariants = rendererLayouts.flatMap((layout) => + rendererSizes.flatMap((size) => + rendererViews.map((view) => ({ + layout, + layoutName: layout ?? 'no-layout', + size, + view, + })), + ), +); diff --git a/playwright/playwright.config.ts b/playwright/playwright.config.ts index eb30356f..f33737f7 100644 --- a/playwright/playwright.config.ts +++ b/playwright/playwright.config.ts @@ -30,20 +30,20 @@ reporter.push( const config: PlaywrightTestConfig = { testDir: pathFromRoot('src'), testMatch: '**/__tests__/*.visual.test.tsx', - updateSnapshots: process.env.UPDATE_REQUEST ? 'all' : 'missing', + updateSnapshots: process.env.UPDATE_REQUEST ? 'all' : 'none', snapshotPathTemplate: '{testDir}/{testFileDir}/../__snapshots__/{testFileName}-snapshots/{arg}{-projectName}-linux{ext}', /* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */ /* Maximum time one test can run for. */ - timeout: 10 * 1000, + timeout: 30 * 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, + /* Full-height matrices, especially 64 Monaco editors, need bounded concurrency. */ + workers: 2, /* 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. */ diff --git a/scripts/playwright-docker.sh b/scripts/playwright-docker.sh index d8c56a44..2c029a71 100755 --- a/scripts/playwright-docker.sh +++ b/scripts/playwright-docker.sh @@ -5,15 +5,16 @@ set -euo pipefail IMAGE_NAME="mcr.microsoft.com/playwright" IMAGE_TAG="v1.40.0-jammy" # This version have to be synchronized with playwright version from package.json -NODE_MODULES_CACHE_DIR="$HOME/.cache/dynamic-forms-playwright-docker-node-modules" +NODE_MODULES_CACHE_DIR="$HOME/.cache/dynamic-forms-playwright-docker-node-modules-linux-amd64" +CACHE_LOCK_FILE="$NODE_MODULES_CACHE_DIR/.dynamic-forms-package-lock.json" command_exists() { command -v "$1" >/dev/null 2>&1 } run_command() { - $CONTAINER_TOOL run --rm --network host -it -w /work \ - -v $(pwd):/work \ + $CONTAINER_TOOL run --rm --platform linux/amd64 --network host -it -w /work \ + -v "$(pwd):/work" \ -v "$NODE_MODULES_CACHE_DIR:/work/node_modules" \ -e IS_DOCKER=1 \ "$IMAGE_NAME:$IMAGE_TAG" \ @@ -35,9 +36,16 @@ if [[ "$1" = "clear-cache" ]]; then exit 0 fi -if [[ ! -d "$NODE_MODULES_CACHE_DIR" ]]; then +if ! "$CONTAINER_TOOL" info >/dev/null 2>&1; then + echo "$CONTAINER_TOOL is not running. Start Docker Desktop or run 'podman machine start'." >&2 + exit 1 +fi + +if [[ ! -x "$NODE_MODULES_CACHE_DIR/.bin/playwright" ]] || ! cmp -s package-lock.json "$CACHE_LOCK_FILE"; then mkdir -p "$NODE_MODULES_CACHE_DIR" + rm -f "$CACHE_LOCK_FILE" run_command 'npm ci' + cp package-lock.json "$CACHE_LOCK_FILE" fi run_command "$1" diff --git a/src/lib/kit/components/Card/__tests__/helpers.ts b/src/lib/kit/components/Card/__tests__/helpers.ts index beeb7c4a..37371334 100644 --- a/src/lib/kit/components/Card/__tests__/helpers.ts +++ b/src/lib/kit/components/Card/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {ArraySpec, FormValue, ObjectSpec} from '../../../../core'; -import {SpecTypes} from '../../../../core'; +import {SpecTypes} from '../../../../core/constants'; export const ACCORDEON_CARD: Record = { arraySpec: { diff --git a/src/lib/kit/components/Inputs/ArrayBase/__tests__/helpers.tsx b/src/lib/kit/components/Inputs/ArrayBase/__tests__/helpers.tsx index a8599c53..8868b90f 100644 --- a/src/lib/kit/components/Inputs/ArrayBase/__tests__/helpers.tsx +++ b/src/lib/kit/components/Inputs/ArrayBase/__tests__/helpers.tsx @@ -3,7 +3,7 @@ import React from 'react'; import {DynamicForm as BaseDynamicForm} from '~playwright/core/DynamicForm'; import type {ArraySpec, FormValue, Spec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const ARRAY_BASE: Record = { default: { diff --git a/src/lib/kit/components/Inputs/CardOneOf/__tests__/helpers.ts b/src/lib/kit/components/Inputs/CardOneOf/__tests__/helpers.ts index 31184d2e..51981d4c 100644 --- a/src/lib/kit/components/Inputs/CardOneOf/__tests__/helpers.ts +++ b/src/lib/kit/components/Inputs/CardOneOf/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {FormValue, ObjectSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const CARD_ONEOF: Record = { default: { diff --git a/src/lib/kit/components/Inputs/Checkbox/__tests__/helpers.ts b/src/lib/kit/components/Inputs/Checkbox/__tests__/helpers.ts index 76c126f4..7359cf7a 100644 --- a/src/lib/kit/components/Inputs/Checkbox/__tests__/helpers.ts +++ b/src/lib/kit/components/Inputs/Checkbox/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {BooleanSpec, FormValue} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const CHECKBOX: Record = { default: { diff --git a/src/lib/kit/components/Inputs/CheckboxGroup/__tests__/helpers.ts b/src/lib/kit/components/Inputs/CheckboxGroup/__tests__/helpers.ts index e9d72b2f..4af3440a 100644 --- a/src/lib/kit/components/Inputs/CheckboxGroup/__tests__/helpers.ts +++ b/src/lib/kit/components/Inputs/CheckboxGroup/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {ArraySpec, FormValue} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const CHECKBOX_GROUP: Record = { default: { diff --git a/src/lib/kit/components/Inputs/ColorPicker/__tests__/helpers.ts b/src/lib/kit/components/Inputs/ColorPicker/__tests__/helpers.ts index 189e3da6..8137421c 100644 --- a/src/lib/kit/components/Inputs/ColorPicker/__tests__/helpers.ts +++ b/src/lib/kit/components/Inputs/ColorPicker/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {FormValue, StringSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const COLOR_PICKER: Record = { default: { diff --git a/src/lib/kit/components/Inputs/DateInput/__tests__/helpers.ts b/src/lib/kit/components/Inputs/DateInput/__tests__/helpers.ts index 1db6e09b..4dd45fcc 100644 --- a/src/lib/kit/components/Inputs/DateInput/__tests__/helpers.ts +++ b/src/lib/kit/components/Inputs/DateInput/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {FormValue, StringSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const DATE_INPUT: Record = { default: { diff --git a/src/lib/kit/components/Inputs/FileInput/__tests__/helpers.ts b/src/lib/kit/components/Inputs/FileInput/__tests__/helpers.ts index 28417952..b0df9a76 100644 --- a/src/lib/kit/components/Inputs/FileInput/__tests__/helpers.ts +++ b/src/lib/kit/components/Inputs/FileInput/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {FormValue, StringSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const FILE_INPUT: Record = { default: { diff --git a/src/lib/kit/components/Inputs/MultiOneOf/__tests__/helpers.ts b/src/lib/kit/components/Inputs/MultiOneOf/__tests__/helpers.ts index 1ea23f0e..bd6c0146 100644 --- a/src/lib/kit/components/Inputs/MultiOneOf/__tests__/helpers.ts +++ b/src/lib/kit/components/Inputs/MultiOneOf/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {FormValue, ObjectSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const MULTI_ONEOF: Record = { default: { diff --git a/src/lib/kit/components/Inputs/MultiSelect/__tests__/helpers.ts b/src/lib/kit/components/Inputs/MultiSelect/__tests__/helpers.ts index 56dfa0a1..ffe5e4d4 100644 --- a/src/lib/kit/components/Inputs/MultiSelect/__tests__/helpers.ts +++ b/src/lib/kit/components/Inputs/MultiSelect/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {ArraySpec, FormValue} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const MULTI_SELECT: Record = { default: { diff --git a/src/lib/kit/components/Inputs/Number/__tests__/helpers.ts b/src/lib/kit/components/Inputs/Number/__tests__/helpers.ts index 19575bc8..dcb91765 100644 --- a/src/lib/kit/components/Inputs/Number/__tests__/helpers.ts +++ b/src/lib/kit/components/Inputs/Number/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {NumberSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const NUMBER_SPEC: Record = { default: { diff --git a/src/lib/kit/components/Inputs/NumberWithScale/__tests__/helpers.ts b/src/lib/kit/components/Inputs/NumberWithScale/__tests__/helpers.ts index d806ea1b..a9219a96 100644 --- a/src/lib/kit/components/Inputs/NumberWithScale/__tests__/helpers.ts +++ b/src/lib/kit/components/Inputs/NumberWithScale/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {FormValue, StringSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const NUMBER_WITH_SCALE: Record = { default: { diff --git a/src/lib/kit/components/Inputs/ObjectBase/__tests__/helpers.ts b/src/lib/kit/components/Inputs/ObjectBase/__tests__/helpers.ts index 90cea19e..57b935e6 100644 --- a/src/lib/kit/components/Inputs/ObjectBase/__tests__/helpers.ts +++ b/src/lib/kit/components/Inputs/ObjectBase/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {FormValue, ObjectSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const OBJECT_BASE: Record = { default: { diff --git a/src/lib/kit/components/Inputs/ObjectValueInput/__tests__/helpers.ts b/src/lib/kit/components/Inputs/ObjectValueInput/__tests__/helpers.ts index 88cbbb0e..f0d51f45 100644 --- a/src/lib/kit/components/Inputs/ObjectValueInput/__tests__/helpers.ts +++ b/src/lib/kit/components/Inputs/ObjectValueInput/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {FormValue, ObjectSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const OBJECT_VALUE_INPUT: Record = { default: { diff --git a/src/lib/kit/components/Inputs/OneOf/__tests__/helpers.ts b/src/lib/kit/components/Inputs/OneOf/__tests__/helpers.ts index cd26bdf6..157e83dc 100644 --- a/src/lib/kit/components/Inputs/OneOf/__tests__/helpers.ts +++ b/src/lib/kit/components/Inputs/OneOf/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {FormValue, ObjectSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const ONEOF: Record = { default: { diff --git a/src/lib/kit/components/Inputs/RadioGroup/__tests__/helpers.ts b/src/lib/kit/components/Inputs/RadioGroup/__tests__/helpers.ts index 935f0820..569a83fe 100644 --- a/src/lib/kit/components/Inputs/RadioGroup/__tests__/helpers.ts +++ b/src/lib/kit/components/Inputs/RadioGroup/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {StringSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const RADIO_GROUP: Record = { default: { diff --git a/src/lib/kit/components/Inputs/RangeInputPicker/__tests__/helpers.ts b/src/lib/kit/components/Inputs/RangeInputPicker/__tests__/helpers.ts index 4a25cdbd..1ba29552 100644 --- a/src/lib/kit/components/Inputs/RangeInputPicker/__tests__/helpers.ts +++ b/src/lib/kit/components/Inputs/RangeInputPicker/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {FormValue, NumberSpec, ObjectSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; const fromTo = ( extra: Partial = {}, diff --git a/src/lib/kit/components/Inputs/Secret/__tests__/helpers.ts b/src/lib/kit/components/Inputs/Secret/__tests__/helpers.ts index 33bc2844..a1720c95 100644 --- a/src/lib/kit/components/Inputs/Secret/__tests__/helpers.ts +++ b/src/lib/kit/components/Inputs/Secret/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {ObjectSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const SECRET: Record = { default: { diff --git a/src/lib/kit/components/Inputs/Select/__tests__/helpers.ts b/src/lib/kit/components/Inputs/Select/__tests__/helpers.ts index 5ca729cf..8547ac01 100644 --- a/src/lib/kit/components/Inputs/Select/__tests__/helpers.ts +++ b/src/lib/kit/components/Inputs/Select/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {StringSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const SELECT: Record = { default: { diff --git a/src/lib/kit/components/Inputs/Switch/__tests__/helpers.ts b/src/lib/kit/components/Inputs/Switch/__tests__/helpers.ts index c3d11585..c670019c 100644 --- a/src/lib/kit/components/Inputs/Switch/__tests__/helpers.ts +++ b/src/lib/kit/components/Inputs/Switch/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {BooleanSpec, FormValue} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const SWITCH: Record = { default: { diff --git a/src/lib/kit/components/Inputs/TableArrayInput/__tests__/helpers.tsx b/src/lib/kit/components/Inputs/TableArrayInput/__tests__/helpers.tsx index 8960fe09..c06da747 100644 --- a/src/lib/kit/components/Inputs/TableArrayInput/__tests__/helpers.tsx +++ b/src/lib/kit/components/Inputs/TableArrayInput/__tests__/helpers.tsx @@ -3,7 +3,7 @@ import React from 'react'; import {DynamicForm as BaseDynamicForm} from '~playwright/core/DynamicForm'; import type {ArraySpec, FormValue, Spec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const TABLE_ARRAY_INPUT: Record = { default: { diff --git a/src/lib/kit/components/Inputs/Text/__tests__/helpers.ts b/src/lib/kit/components/Inputs/Text/__tests__/helpers.ts index 4202c270..bfa45a50 100644 --- a/src/lib/kit/components/Inputs/Text/__tests__/helpers.ts +++ b/src/lib/kit/components/Inputs/Text/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {StringSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const PASSWORD_SPEC: Record = { default: { diff --git a/src/lib/kit/components/Inputs/TextArea/__tests__/helpers.ts b/src/lib/kit/components/Inputs/TextArea/__tests__/helpers.ts index b521ffc7..43eb15ca 100644 --- a/src/lib/kit/components/Inputs/TextArea/__tests__/helpers.ts +++ b/src/lib/kit/components/Inputs/TextArea/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {StringSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const TEXT_AREA_SPEC: Record = { default: { diff --git a/src/lib/kit/components/Inputs/TextContent/__tests__/helpers.ts b/src/lib/kit/components/Inputs/TextContent/__tests__/helpers.ts index 27f21ba5..12be9a0a 100644 --- a/src/lib/kit/components/Inputs/TextContent/__tests__/helpers.ts +++ b/src/lib/kit/components/Inputs/TextContent/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {StringSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const TEXT_CONTENT_SPEC: Record = { defaultText: { diff --git a/src/lib/kit/components/Inputs/TimeRangeSelector/__tests__/helpers.ts b/src/lib/kit/components/Inputs/TimeRangeSelector/__tests__/helpers.ts index 96504bd6..985cf825 100644 --- a/src/lib/kit/components/Inputs/TimeRangeSelector/__tests__/helpers.ts +++ b/src/lib/kit/components/Inputs/TimeRangeSelector/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {FormValue, ObjectSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; const ENUM_START = [ '00:00', diff --git a/src/lib/kit/components/Layouts/Accordeon/__tests__/helpers.ts b/src/lib/kit/components/Layouts/Accordeon/__tests__/helpers.ts index 91909951..95f3e440 100644 --- a/src/lib/kit/components/Layouts/Accordeon/__tests__/helpers.ts +++ b/src/lib/kit/components/Layouts/Accordeon/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {ArraySpec, FormValue, ObjectSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const ACCORDEON: Record = { arraySpec: { diff --git a/src/lib/kit/components/Layouts/AccordeonCard/__tests__/helpers.ts b/src/lib/kit/components/Layouts/AccordeonCard/__tests__/helpers.ts index 2d9014cb..58b76785 100644 --- a/src/lib/kit/components/Layouts/AccordeonCard/__tests__/helpers.ts +++ b/src/lib/kit/components/Layouts/AccordeonCard/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {ArraySpec, FormValue, ObjectSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const ACCORDEON_CARD: Record = { arraySpec: { diff --git a/src/lib/kit/components/Layouts/Column/__tests__/helpers.ts b/src/lib/kit/components/Layouts/Column/__tests__/helpers.ts index 3e2dbe8e..8c018833 100644 --- a/src/lib/kit/components/Layouts/Column/__tests__/helpers.ts +++ b/src/lib/kit/components/Layouts/Column/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {FormValue, ObjectSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const COLUMN_CARD: ObjectSpec = { defaultValue: { diff --git a/src/lib/kit/components/Layouts/Section/__tests__/helpers.ts b/src/lib/kit/components/Layouts/Section/__tests__/helpers.ts index 6a40eb62..91a91432 100644 --- a/src/lib/kit/components/Layouts/Section/__tests__/helpers.ts +++ b/src/lib/kit/components/Layouts/Section/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {ArraySpec, FormValue, ObjectSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const SECTION: Record = { sectionArraySpec: { diff --git a/src/lib/kit/components/ViewLayouts/ViewColumn/__tests__/helpers.ts b/src/lib/kit/components/ViewLayouts/ViewColumn/__tests__/helpers.ts index c8cb7b29..44eafa88 100644 --- a/src/lib/kit/components/ViewLayouts/ViewColumn/__tests__/helpers.ts +++ b/src/lib/kit/components/ViewLayouts/ViewColumn/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {FormValue, ObjectSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const VIEW_COLUMN: ObjectSpec = { defaultValue: { diff --git a/src/lib/kit/components/ViewLayouts/ViewRow/__tests__/helpers.ts b/src/lib/kit/components/ViewLayouts/ViewRow/__tests__/helpers.ts index 49cb76ef..cd13033e 100644 --- a/src/lib/kit/components/ViewLayouts/ViewRow/__tests__/helpers.ts +++ b/src/lib/kit/components/ViewLayouts/ViewRow/__tests__/helpers.ts @@ -1,5 +1,5 @@ import type {FormValue, ObjectSpec} from '../../../../../core'; -import {SpecTypes} from '../../../../../core'; +import {SpecTypes} from '../../../../../core/constants'; export const VIEW_ROW: ObjectSpec = { defaultValue: { diff --git a/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-form-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-form-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..8dbfb794 Binary files /dev/null and b/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-form-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-form-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-form-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..a9b22083 Binary files /dev/null and b/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-form-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-form-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-form-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..698b6ec8 Binary files /dev/null and b/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-form-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-form-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-form-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..99a3e0fc Binary files /dev/null and b/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-form-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-overview-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-overview-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..f1cf3359 Binary files /dev/null and b/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-overview-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-overview-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-overview-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..a70852f0 Binary files /dev/null and b/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-overview-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-overview-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-overview-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..5cf709fc Binary files /dev/null and b/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-overview-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-overview-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-overview-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..c0c91f75 Binary files /dev/null and b/src/lib/unstable/kit/entities/Alert/__snapshots__/Alert.visual.test.tsx-snapshots/SchemaRenderer-Alert-overview-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/Alert/__tests__/Alert.visual.test.tsx b/src/lib/unstable/kit/entities/Alert/__tests__/Alert.visual.test.tsx new file mode 100644 index 00000000..82b39825 --- /dev/null +++ b/src/lib/unstable/kit/entities/Alert/__tests__/Alert.visual.test.tsx @@ -0,0 +1,244 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {createSchema, value} from './helpers'; + +test.describe('SchemaRenderer Alert', () => { + for (const mode of [SchemaRendererMode.Form, SchemaRendererMode.Overview]) { + test(`${mode} layouts sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'warning theme with custom icon', + props: ((): SchemaFormProps => { + const base = createSchema('row'); + const schema = { + ...base, + nodeParameters: { + ...base.nodeParameters, + entityProps: { + ...base.nodeParameters?.entityProps, + theme: 'warning', + iconName: 'CircleExclamation', + iconProps: {size: 20}, + }, + }, + }; + return { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'danger theme with custom icon', + props: ((): SchemaFormProps => { + const base = createSchema('row'); + const schema = { + ...base, + nodeParameters: { + ...base.nodeParameters, + entityProps: { + ...base.nodeParameters?.entityProps, + theme: 'danger', + iconName: 'CircleExclamation', + iconProps: {size: 20}, + }, + }, + }; + return { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'success theme with custom icon', + props: ((): SchemaFormProps => { + const base = createSchema('row'); + const schema = { + ...base, + nodeParameters: { + ...base.nodeParameters, + entityProps: { + ...base.nodeParameters?.entityProps, + theme: 'success', + iconName: 'CircleExclamation', + iconProps: {size: 20}, + }, + }, + }; + return { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'title collapses and expands the message — Alert message collapsed', + props: ((): SchemaFormProps => { + const schema = createSchema('row'); + return { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'title collapses and expands the message — Alert message expanded', + props: ((): SchemaFormProps => { + const schema = createSchema('row'); + return { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const grid = await mount( + { + const schema = createSchema(layout); + const label = `layout=${layoutName} / size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect( + component.getByText('Review the settings before saving.', {exact: true}), + ).toBeVisible(); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'warning theme with custom icon', + exact: true, + }); + + await expect(component.getByText('Notice', {exact: true})).toBeVisible(); + await expect( + component.getByText('Review the settings before saving.', {exact: true}), + ).toBeVisible(); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'danger theme with custom icon', + exact: true, + }); + + await expect(component.getByText('Notice', {exact: true})).toBeVisible(); + await expect( + component.getByText('Review the settings before saving.', {exact: true}), + ).toBeVisible(); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'success theme with custom icon', + exact: true, + }); + + await expect(component.getByText('Notice', {exact: true})).toBeVisible(); + await expect( + component.getByText('Review the settings before saving.', {exact: true}), + ).toBeVisible(); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'title collapses and expands the message — Alert message collapsed', + exact: true, + }); + + await component.getByText('Notice', {exact: true}).click(); + await expect(component.locator('.sr-alert__message')).toBeHidden(); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'title collapses and expands the message — Alert message expanded', + exact: true, + }); + + await component.getByText('Notice', {exact: true}).click(); + await expect(component.locator('.sr-alert__message')).toBeHidden(); + await component.getByText('Notice', {exact: true}).click(); + await expect(component.locator('.sr-alert__message')).toBeVisible(); + } + await expectScreenshot({scale: 'css'}); + }); + } +}); diff --git a/src/lib/unstable/kit/entities/Alert/__tests__/helpers.ts b/src/lib/unstable/kit/entities/Alert/__tests__/helpers.ts new file mode 100644 index 00000000..56c2d2d2 --- /dev/null +++ b/src/lib/unstable/kit/entities/Alert/__tests__/helpers.ts @@ -0,0 +1,24 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +export const createSchema = (layout: string | undefined): JsonSchemaString => ({ + type: JsonSchemaType.String, + title: 'Alert', + description: 'Description for Alert', + + nodeParameters: { + type: NodeType.String, + entity: 'alert', + layout, + entityProps: { + theme: 'info', + iconName: 'CircleInfo', + title: 'Notice', + message: 'Review the settings before saving.', + }, + flags: {open: true}, + layoutProps: {descriptionType: 'bottom'}, + }, +}); + +export const value = undefined; diff --git a/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-form-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-form-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..055e27a8 Binary files /dev/null and b/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-form-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-form-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-form-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..05bc5bbc Binary files /dev/null and b/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-form-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-form-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-form-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..56a9b8aa Binary files /dev/null and b/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-form-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-form-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-form-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..f18ab067 Binary files /dev/null and b/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-form-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-overview-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-overview-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..49914381 Binary files /dev/null and b/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-overview-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-overview-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-overview-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..e9159b7a Binary files /dev/null and b/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-overview-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-overview-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-overview-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..7b91785e Binary files /dev/null and b/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-overview-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-overview-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-overview-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..8bafd7c0 Binary files /dev/null and b/src/lib/unstable/kit/entities/ArrayEntity/__snapshots__/ArrayEntity.visual.test.tsx-snapshots/SchemaRenderer-ArrayEntity-overview-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/ArrayEntity/__tests__/ArrayEntity.visual.test.tsx b/src/lib/unstable/kit/entities/ArrayEntity/__tests__/ArrayEntity.visual.test.tsx new file mode 100644 index 00000000..019e6726 --- /dev/null +++ b/src/lib/unstable/kit/entities/ArrayEntity/__tests__/ArrayEntity.visual.test.tsx @@ -0,0 +1,204 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {expectInsideScreenshot} from '~playwright/core/expectInsideScreenshot'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; +import type {JsonSchema} from '../../../../core/types'; + +import {createSchema, value} from './helpers'; + +test.describe('SchemaRenderer ArrayEntity', () => { + for (const mode of [SchemaRendererMode.Form, SchemaRendererMode.Overview]) { + test(`${mode} layouts sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'add edit and remove an item', + props: ((): SchemaFormProps => { + const schema = createSchema('section'); + return { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'tuple keeps fixed fields without add or remove controls', + props: ((): SchemaFormProps => { + const base = createSchema('section'); + const schema = { + ...base, + items: [ + base.items as JsonSchema, + {...(base.items as JsonSchema), title: 'Second name'}, + ], + }; + return { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'disabled array cannot add items', + props: ((): SchemaFormProps => { + const base = createSchema('section'); + const schema = { + ...base, + nodeParameters: { + ...base.nodeParameters, + flags: {open: true, disabled: true}, + }, + }; + return { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'empty overview uses the empty value marker', + props: ((): SchemaFormProps => { + const schema = createSchema('cell'); + return { + schema: schema, + value: [], + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const grid = await mount( + { + const schema = createSchema(layout); + const label = `layout=${layoutName} / size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 1000, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + if (mode === SchemaRendererMode.Form) { + await expect(component.getByRole('textbox').first()).toHaveValue('Ada'); + } else { + await expect(component.getByText('Ada', {exact: true})).toBeVisible(); + } + const frame = component; + if (mode === SchemaRendererMode.Form) { + for (const qa of ['input[0]', 'input[1]']) { + await expectInsideScreenshot( + component.getByTestId(qa).locator('input'), + frame, + ); + } + } else { + for (const text of ['Ada', 'Grace']) { + await expectInsideScreenshot( + component.getByText(text, {exact: true}), + frame, + ); + } + } + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'add edit and remove an item', + exact: true, + }); + await component.getByTestId('input-add-button').click(); + await component.getByTestId('input[2]').locator('input').fill('Linus'); + await component.getByTestId('input[0]-remove-button').click(); + await expect(component.getByTestId('form-value')).toHaveText('["Grace","Linus"]'); + await expect(component.getByRole('textbox')).toHaveCount(2); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'tuple keeps fixed fields without add or remove controls', + exact: true, + }); + await expect(component.getByRole('textbox')).toHaveCount(2); + await expect(component.getByTestId('input-add-button')).toHaveCount(0); + await expect(component.locator('[data-qa$="-remove-button"]')).toHaveCount(0); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'disabled array cannot add items', + exact: true, + }); + await expect(component.getByTestId('input-add-button')).toBeDisabled(); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'empty overview uses the empty value marker', + exact: true, + }); + await expect(component.getByText('—', {exact: true})).toBeVisible(); + } + await expectScreenshot({scale: 'css'}); + }); + } +}); diff --git a/src/lib/unstable/kit/entities/ArrayEntity/__tests__/helpers.ts b/src/lib/unstable/kit/entities/ArrayEntity/__tests__/helpers.ts new file mode 100644 index 00000000..926dec91 --- /dev/null +++ b/src/lib/unstable/kit/entities/ArrayEntity/__tests__/helpers.ts @@ -0,0 +1,26 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchema, JsonSchemaArray} from '../../../../core/types'; + +const nameSchema: JsonSchema = { + type: JsonSchemaType.String, + title: 'Name', + minLength: 2, + nodeParameters: {type: NodeType.String, entity: 'base', layout: 'row'}, +}; + +export const createSchema = (layout: string | undefined): JsonSchemaArray => ({ + type: JsonSchemaType.Array, + title: 'ArrayEntity', + description: 'Description for ArrayEntity', + items: nameSchema, + nodeParameters: { + type: NodeType.Array, + entity: 'base', + layout, + entityProps: {addButtonText: 'Add member'}, + flags: {open: true}, + layoutProps: {descriptionType: 'bottom'}, + }, +}); + +export const value = ['Ada', 'Grace']; diff --git a/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-form-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-form-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..fe74b363 Binary files /dev/null and b/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-form-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-form-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-form-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..1de053f8 Binary files /dev/null and b/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-form-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-form-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-form-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..eda0237d Binary files /dev/null and b/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-form-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-form-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-form-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..e64ca145 Binary files /dev/null and b/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-form-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-overview-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-overview-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..2d757073 Binary files /dev/null and b/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-overview-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-overview-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-overview-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..e4bcca2e Binary files /dev/null and b/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-overview-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-overview-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-overview-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..c28a59d5 Binary files /dev/null and b/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-overview-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-overview-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-overview-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..47d8212f Binary files /dev/null and b/src/lib/unstable/kit/entities/ArrayTable/__snapshots__/ArrayTable.visual.test.tsx-snapshots/SchemaRenderer-ArrayTable-overview-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/ArrayTable/__tests__/ArrayTable.visual.test.tsx b/src/lib/unstable/kit/entities/ArrayTable/__tests__/ArrayTable.visual.test.tsx new file mode 100644 index 00000000..e5c3ab21 --- /dev/null +++ b/src/lib/unstable/kit/entities/ArrayTable/__tests__/ArrayTable.visual.test.tsx @@ -0,0 +1,262 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {expectInsideScreenshot} from '~playwright/core/expectInsideScreenshot'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {JsonSchemaType, NodeType, SchemaRendererMode} from '../../../../core/constants'; +import type {JsonSchema} from '../../../../core/types'; + +import {createSchema, value} from './helpers'; + +test.describe('SchemaRenderer ArrayTable', () => { + for (const mode of [SchemaRendererMode.Form, SchemaRendererMode.Overview]) { + test(`${mode} layouts sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'add edit and remove a row', + props: ((): SchemaFormProps => { + const schema = createSchema('section'); + return { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'column order follows the explicit order prop', + props: ((): SchemaFormProps => { + const base = createSchema('section'); + const schema: JsonSchema = { + ...base, + nodeParameters: { + ...base.nodeParameters, + entityProps: {order: ['age', 'name'], addButtonText: 'Add person'}, + }, + }; + return { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'primitive rows use a single data column', + props: ((): SchemaFormProps => { + const base = createSchema('section'); + const schema: JsonSchema = { + ...base, + items: { + type: JsonSchemaType.String, + title: 'Name', + nodeParameters: { + type: NodeType.String, + entity: 'base', + layout: 'cell', + }, + }, + nodeParameters: { + ...base.nodeParameters, + entityProps: {addButtonText: 'Add name'}, + }, + }; + return { + schema: schema, + value: ['Ada', 'Grace'], + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'tuple rows cannot be added or removed', + props: ((): SchemaFormProps => { + const base = createSchema('section'); + const schema: JsonSchema = { + ...base, + items: [base.items as JsonSchema, base.items as JsonSchema], + }; + return { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'empty overview uses the empty value marker', + props: ((): SchemaFormProps => { + const schema = createSchema('cell'); + return { + schema: schema, + value: [], + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const grid = await mount( + { + const schema = createSchema(layout); + const label = `layout=${layoutName} / size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 1000, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + if (mode === SchemaRendererMode.Form) { + await expect( + component.getByTestId('input[0].name').locator('input'), + ).toHaveValue('Ada'); + } else { + await expect(component.getByText('Ada', {exact: true})).toBeVisible(); + } + const frame = component; + if (mode === SchemaRendererMode.Form) { + for (const qa of [ + 'input[0].name', + 'input[0].age', + 'input[1].name', + 'input[1].age', + ]) { + await expectInsideScreenshot( + component.getByTestId(qa).locator('input'), + frame, + ); + } + } else { + for (const text of ['Ada', '37', 'Grace', '45']) { + await expectInsideScreenshot( + component.getByText(text, {exact: true}), + frame, + ); + } + } + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'add edit and remove a row', + exact: true, + }); + await component.getByTestId('input-add-button').click(); + await component.getByTestId('input[2].name').locator('input').fill('Linus'); + await component.getByTestId('input[2].age').locator('input').fill('54'); + await component.getByTestId('input[2].age').locator('input').blur(); + await component.getByTestId('input[0]-remove-button').click(); + await expect(component.getByTestId('form-value')).toHaveText( + '[{"name":"Grace","age":45},{"name":"Linus","age":54}]', + ); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'column order follows the explicit order prop', + exact: true, + }); + await expect(component.locator('.sr-array-table__column-title')).toHaveText([ + 'Age', + 'Name', + ]); + await expect(component.getByTestId('input[0].age').locator('input')).toHaveValue( + '37', + ); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'primitive rows use a single data column', + exact: true, + }); + await expect(component.getByRole('textbox')).toHaveCount(2); + await expect(component.getByTestId('input[0]').locator('input')).toHaveValue('Ada'); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'tuple rows cannot be added or removed', + exact: true, + }); + await expect(component.getByTestId('input-add-button')).toHaveCount(0); + await expect(component.locator('[data-qa$="-remove-button"]')).toHaveCount(0); + await expect(component.getByTestId('input[1].name').locator('input')).toHaveValue( + 'Grace', + ); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'empty overview uses the empty value marker', + exact: true, + }); + await expect(component.getByText('—', {exact: true})).toBeVisible(); + } + await expectScreenshot({scale: 'css'}); + }); + } +}); diff --git a/src/lib/unstable/kit/entities/ArrayTable/__tests__/helpers.ts b/src/lib/unstable/kit/entities/ArrayTable/__tests__/helpers.ts new file mode 100644 index 00000000..c0cf3b51 --- /dev/null +++ b/src/lib/unstable/kit/entities/ArrayTable/__tests__/helpers.ts @@ -0,0 +1,43 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchema, JsonSchemaArray} from '../../../../core/types'; + +const nameSchema: JsonSchema = { + type: JsonSchemaType.String, + title: 'Name', + minLength: 2, + nodeParameters: {type: NodeType.String, entity: 'base', layout: 'row'}, +}; +const ageSchema: JsonSchema = { + type: JsonSchemaType.Number, + title: 'Age', + minimum: 0, + maximum: 100, + nodeParameters: {type: NodeType.Number, entity: 'base', layout: 'row'}, +}; + +export const createSchema = (layout: string | undefined): JsonSchemaArray => ({ + type: JsonSchemaType.Array, + title: 'ArrayTable', + description: 'Description for ArrayTable', + items: { + type: JsonSchemaType.Object, + properties: { + name: {...nameSchema, nodeParameters: {...nameSchema.nodeParameters, layout: 'cell'}}, + age: {...ageSchema, nodeParameters: {...ageSchema.nodeParameters, layout: 'cell'}}, + }, + nodeParameters: {type: NodeType.Object, entity: 'base', layout: 'section'}, + }, + nodeParameters: { + type: NodeType.Array, + entity: 'table', + layout, + entityProps: {order: ['name', 'age'], addButtonText: 'Add person'}, + flags: {open: true}, + layoutProps: {descriptionType: 'bottom'}, + }, +}); + +export const value = [ + {name: 'Ada', age: 37}, + {name: 'Grace', age: 45}, +]; diff --git a/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-form-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-form-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..6de8113c Binary files /dev/null and b/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-form-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-form-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-form-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..321baffa Binary files /dev/null and b/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-form-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-form-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-form-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..acbf9558 Binary files /dev/null and b/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-form-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-form-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-form-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..c64f349a Binary files /dev/null and b/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-form-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-overview-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-overview-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..a2ebbcae Binary files /dev/null and b/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-overview-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-overview-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-overview-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..c023350b Binary files /dev/null and b/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-overview-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-overview-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-overview-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..e423c376 Binary files /dev/null and b/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-overview-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-overview-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-overview-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..c852bc5e Binary files /dev/null and b/src/lib/unstable/kit/entities/DotValue/__snapshots__/DotValue.visual.test.tsx-snapshots/SchemaRenderer-DotValue-overview-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/DotValue/__tests__/DotValue.visual.test.tsx b/src/lib/unstable/kit/entities/DotValue/__tests__/DotValue.visual.test.tsx new file mode 100644 index 00000000..3777b823 --- /dev/null +++ b/src/lib/unstable/kit/entities/DotValue/__tests__/DotValue.visual.test.tsx @@ -0,0 +1,146 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {createSchema, value} from './helpers'; + +test.describe('SchemaRenderer DotValue', () => { + for (const mode of [SchemaRendererMode.Form, SchemaRendererMode.Overview]) { + test(`${mode} layouts sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'clearing the wrapped value clears the parent object', + props: ((): SchemaFormProps => { + const schema = createSchema('row'); + return { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'unspecified enum sentinel is normalized to empty', + props: ((): SchemaFormProps => { + const schema = createSchema('row'); + return { + schema: schema, + value: {value: 'STATUS_UNSPECIFIED'}, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'empty overview uses the empty value marker', + props: ((): SchemaFormProps => { + const schema = createSchema('cell'); + return { + schema: schema, + value: {}, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const grid = await mount( + { + const schema = createSchema(layout); + const label = `layout=${layoutName} / size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + if (mode === SchemaRendererMode.Form) { + await expect(component.getByTestId('input.value').locator('input')).toHaveValue( + 'Ada', + ); + } else { + await expect(component.getByText('Ada', {exact: true})).toBeVisible(); + } + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'clearing the wrapped value clears the parent object', + exact: true, + }); + await component.getByTestId('input.value').locator('input').fill(''); + await component.getByTestId('input.value').locator('input').blur(); + await expect(component.getByTestId('form-value')).toHaveText(''); + await expect(component.getByTestId('input.value').locator('input')).toHaveValue(''); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'unspecified enum sentinel is normalized to empty', + exact: true, + }); + await expect(component.getByTestId('form-value')).toHaveText(''); + await expect(component.getByTestId('input.value').locator('input')).toHaveValue(''); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'empty overview uses the empty value marker', + exact: true, + }); + await expect(component.getByText('—', {exact: true})).toBeVisible(); + } + await expectScreenshot({scale: 'css'}); + }); + } +}); diff --git a/src/lib/unstable/kit/entities/DotValue/__tests__/helpers.ts b/src/lib/unstable/kit/entities/DotValue/__tests__/helpers.ts new file mode 100644 index 00000000..fe211fd1 --- /dev/null +++ b/src/lib/unstable/kit/entities/DotValue/__tests__/helpers.ts @@ -0,0 +1,31 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchema, JsonSchemaObject} from '../../../../core/types'; + +const nameSchema: JsonSchema = { + type: JsonSchemaType.String, + title: 'Name', + minLength: 2, + nodeParameters: {type: NodeType.String, entity: 'base', layout: 'row'}, +}; + +export const createSchema = (layout: string | undefined): JsonSchemaObject => ({ + type: JsonSchemaType.Object, + title: 'DotValue', + description: 'Description for DotValue', + properties: { + value: { + ...nameSchema, + nodeParameters: {...nameSchema.nodeParameters, layout: 'transparent'}, + }, + }, + nodeParameters: { + type: NodeType.Object, + entity: 'dot_value', + layout, + entityProps: {}, + flags: {open: true}, + layoutProps: {descriptionType: 'bottom'}, + }, +}); + +export const value = {value: 'Ada'}; diff --git a/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-form-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-form-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..a054bf83 Binary files /dev/null and b/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-form-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-form-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-form-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..919ce368 Binary files /dev/null and b/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-form-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-form-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-form-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..b5f87a7a Binary files /dev/null and b/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-form-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-form-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-form-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..0c6aa693 Binary files /dev/null and b/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-form-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-overview-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-overview-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..87c4356a Binary files /dev/null and b/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-overview-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-overview-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-overview-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..92805838 Binary files /dev/null and b/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-overview-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-overview-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-overview-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..2eaccb00 Binary files /dev/null and b/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-overview-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-overview-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-overview-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..b3d977a6 Binary files /dev/null and b/src/lib/unstable/kit/entities/FewOfNested/__snapshots__/FewOfNested.visual.test.tsx-snapshots/SchemaRenderer-FewOfNested-overview-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/FewOfNested/__tests__/FewOfNested.visual.test.tsx b/src/lib/unstable/kit/entities/FewOfNested/__tests__/FewOfNested.visual.test.tsx new file mode 100644 index 00000000..c951d7d9 --- /dev/null +++ b/src/lib/unstable/kit/entities/FewOfNested/__tests__/FewOfNested.visual.test.tsx @@ -0,0 +1,180 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {expectInsideScreenshot} from '~playwright/core/expectInsideScreenshot'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {JsonSchemaType, NodeType, SchemaRendererMode} from '../../../../core/constants'; + +import {createSchema, value} from './helpers'; + +test.describe('SchemaRenderer FewOfNested', () => { + for (const mode of [SchemaRendererMode.Form, SchemaRendererMode.Overview]) { + test(`${mode} layouts sizes and views`, async ({mount, page, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'deselecting a branch preserves the other value', + props: ((): SchemaFormProps => { + const schema = createSchema('section'); + return { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'configured order selects the initial branch before adding another', + props: ((): SchemaFormProps => { + const base = createSchema('section'); + const schema = { + ...base, + nodeParameters: { + ...base.nodeParameters, + entityProps: { + order: ['age', 'name'], + withIndent: false, + toggler: { + type: JsonSchemaType.Array, + title: 'Fields', + items: {type: JsonSchemaType.String, enum: ['name', 'age']}, + nodeParameters: { + type: NodeType.Array, + entity: 'checkbox_group', + layout: 'row', + entityProps: { + enumDescriptions: {name: 'Name', age: 'Age'}, + }, + }, + }, + }, + }, + }; + return { + schema: schema, + value: undefined, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const grid = await mount( + { + const schema = createSchema(layout); + const label = `layout=${layoutName} / size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 1000, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + if (mode === SchemaRendererMode.Form) { + await expect(component.getByTestId('input.name').locator('input')).toHaveValue( + 'Ada', + ); + } else { + await expect(component.getByText('Ada', {exact: true})).toBeVisible(); + } + const frame = component; + if (mode === SchemaRendererMode.Form) { + for (const qa of ['input.name', 'input.age']) { + await expectInsideScreenshot( + component.getByTestId(qa).locator('input'), + frame, + ); + } + } else { + for (const text of ['Ada', '37']) { + await expectInsideScreenshot( + component.getByText(text, {exact: true}), + frame, + ); + } + } + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'deselecting a branch preserves the other value', + exact: true, + }); + + await component.getByRole('combobox').click(); + await page.getByRole('option', {name: 'Age', exact: true}).click(); + await page.keyboard.press('Escape'); + await expect(component.getByTestId('input.age')).toHaveCount(0); + await expect(component.getByTestId('form-value')).toHaveText('{"name":"Ada"}'); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'configured order selects the initial branch before adding another', + exact: true, + }); + + await expect( + component.getByRole('checkbox', {name: 'Age', exact: true}), + ).toBeChecked(); + await expect(component.getByTestId('input.age').locator('input')).toBeVisible(); + await expect(component.getByTestId('input.name')).toHaveCount(0); + await component.getByTestId('input.age').locator('input').fill('37'); + await component.getByTestId('input.age').locator('input').blur(); + await component.getByRole('checkbox', {name: 'Name', exact: true}).check(); + await component.getByTestId('input.name').locator('input').fill('Ada'); + const fields = component.locator('[data-qa="input.age"], [data-qa="input.name"]'); + await expect(fields).toHaveCount(2); + expect( + await fields.evaluateAll((nodes) => + nodes.map((node) => node.getAttribute('data-qa')), + ), + ).toEqual(['input.age', 'input.name']); + await expect(component.getByTestId('form-value')).toHaveText( + '{"age":37,"name":"Ada"}', + ); + } + await expectScreenshot({scale: 'css'}); + }); + } +}); diff --git a/src/lib/unstable/kit/entities/FewOfNested/__tests__/helpers.ts b/src/lib/unstable/kit/entities/FewOfNested/__tests__/helpers.ts new file mode 100644 index 00000000..15148f71 --- /dev/null +++ b/src/lib/unstable/kit/entities/FewOfNested/__tests__/helpers.ts @@ -0,0 +1,47 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchema, JsonSchemaObject} from '../../../../core/types'; + +const nameSchema: JsonSchema = { + type: JsonSchemaType.String, + title: 'Name', + minLength: 2, + nodeParameters: {type: NodeType.String, entity: 'base', layout: 'row'}, +}; +const ageSchema: JsonSchema = { + type: JsonSchemaType.Number, + title: 'Age', + minimum: 0, + maximum: 100, + nodeParameters: {type: NodeType.Number, entity: 'base', layout: 'row'}, +}; + +export const createSchema = (layout: string | undefined): JsonSchemaObject => ({ + type: JsonSchemaType.Object, + title: 'FewOfNested', + description: 'Description for FewOfNested', + properties: {name: nameSchema, age: ageSchema}, + nodeParameters: { + type: NodeType.Object, + entity: 'few_of_nested', + layout, + entityProps: { + toggler: { + type: JsonSchemaType.Array, + title: 'Fields', + items: {type: JsonSchemaType.String, enum: ['name', 'age']}, + nodeParameters: { + type: NodeType.Array, + entity: 'select', + layout: 'row', + entityProps: {enumDescriptions: {name: 'Name', age: 'Age'}}, + }, + }, + withIndent: true, + order: ['name', 'age'], + }, + flags: {open: true}, + layoutProps: {descriptionType: 'bottom'}, + }, +}); + +export const value = {name: 'Ada', age: 37}; diff --git a/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-form-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-form-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..6753e111 Binary files /dev/null and b/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-form-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-form-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-form-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..3512ff24 Binary files /dev/null and b/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-form-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-form-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-form-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..b1be086f Binary files /dev/null and b/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-form-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-form-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-form-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..dea72628 Binary files /dev/null and b/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-form-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-overview-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-overview-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..2f55e8be Binary files /dev/null and b/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-overview-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-overview-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-overview-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..047def55 Binary files /dev/null and b/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-overview-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-overview-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-overview-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..b1c45acb Binary files /dev/null and b/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-overview-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-overview-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-overview-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..c8892c11 Binary files /dev/null and b/src/lib/unstable/kit/entities/Label/__snapshots__/Label.visual.test.tsx-snapshots/SchemaRenderer-Label-overview-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/Label/__tests__/Label.visual.test.tsx b/src/lib/unstable/kit/entities/Label/__tests__/Label.visual.test.tsx new file mode 100644 index 00000000..eb72885f --- /dev/null +++ b/src/lib/unstable/kit/entities/Label/__tests__/Label.visual.test.tsx @@ -0,0 +1,139 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {createSchema, value} from './helpers'; + +test.describe('SchemaRenderer Label', () => { + for (const mode of [SchemaRendererMode.Form, SchemaRendererMode.Overview]) { + test(`${mode} layouts sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'description fallback with empty input', + props: ((): SchemaFormProps => { + const base = createSchema('row'); + const schema = { + ...base, + description: 'Unspecified status', + nodeParameters: { + ...base.nodeParameters, + entityProps: {iconName: 'CircleInfo', iconProps: {size: 20}}, + }, + }; + return { + schema: schema, + value: undefined, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'custom title and long value', + props: ((): SchemaFormProps => { + const base = createSchema('column'); + const schema = { + ...base, + nodeParameters: { + ...base.nodeParameters, + entityProps: { + title: 'Build status', + theme: 'warning', + iconName: 'Clock', + }, + }, + }; + return { + schema: schema, + value: 'Waiting for the deployment approval from the maintenance team', + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const grid = await mount( + { + const schema = createSchema(layout); + const label = `layout=${layoutName} / size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByText('Ready', {exact: true})).toBeVisible(); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'description fallback with empty input', + exact: true, + }); + await expect( + component.locator('.sr-label').getByText('Unspecified status', {exact: true}), + ).toBeVisible(); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'custom title and long value', + exact: true, + }); + await expect(component.getByText('Build status', {exact: true})).toBeVisible(); + await expect( + component.getByText( + 'Waiting for the deployment approval from the maintenance team', + { + exact: true, + }, + ), + ).toBeVisible(); + } + await expectScreenshot({scale: 'css'}); + }); + } +}); diff --git a/src/lib/unstable/kit/entities/Label/__tests__/helpers.ts b/src/lib/unstable/kit/entities/Label/__tests__/helpers.ts new file mode 100644 index 00000000..3ac669a6 --- /dev/null +++ b/src/lib/unstable/kit/entities/Label/__tests__/helpers.ts @@ -0,0 +1,19 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +export const createSchema = (layout: string | undefined): JsonSchemaString => ({ + type: JsonSchemaType.String, + title: 'Label', + description: 'Description for Label', + + nodeParameters: { + type: NodeType.String, + entity: 'label', + layout, + entityProps: {theme: 'success', iconName: 'Check', title: 'Build status'}, + flags: {open: true}, + layoutProps: {descriptionType: 'bottom'}, + }, +}); + +export const value = 'Ready'; diff --git a/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/Monaco-expanded-dialog-dark-chromium-linux.png b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/Monaco-expanded-dialog-dark-chromium-linux.png new file mode 100644 index 00000000..dc3debd6 Binary files /dev/null and b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/Monaco-expanded-dialog-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/Monaco-expanded-dialog-dark-webkit-linux.png b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/Monaco-expanded-dialog-dark-webkit-linux.png new file mode 100644 index 00000000..cce3e63b Binary files /dev/null and b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/Monaco-expanded-dialog-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/Monaco-expanded-dialog-light-chromium-linux.png b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/Monaco-expanded-dialog-light-chromium-linux.png new file mode 100644 index 00000000..402eafd4 Binary files /dev/null and b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/Monaco-expanded-dialog-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/Monaco-expanded-dialog-light-webkit-linux.png b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/Monaco-expanded-dialog-light-webkit-linux.png new file mode 100644 index 00000000..3a28e61f Binary files /dev/null and b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/Monaco-expanded-dialog-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-form-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-form-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..417ddc85 Binary files /dev/null and b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-form-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-form-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-form-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..c66078ca Binary files /dev/null and b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-form-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-form-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-form-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..fdae09d1 Binary files /dev/null and b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-form-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-form-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-form-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..1a3aafb0 Binary files /dev/null and b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-form-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-overview-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-overview-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..0d992ef3 Binary files /dev/null and b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-overview-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-overview-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-overview-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..c64a8cf4 Binary files /dev/null and b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-overview-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-overview-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-overview-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..d30fd1ad Binary files /dev/null and b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-overview-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-overview-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-overview-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..d532cb4b Binary files /dev/null and b/src/lib/unstable/kit/entities/Monaco/__snapshots__/Monaco.visual.test.tsx-snapshots/SchemaRenderer-Monaco-overview-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/Monaco/__tests__/Monaco.visual.test.tsx b/src/lib/unstable/kit/entities/Monaco/__tests__/Monaco.visual.test.tsx new file mode 100644 index 00000000..78b866bd --- /dev/null +++ b/src/lib/unstable/kit/entities/Monaco/__tests__/Monaco.visual.test.tsx @@ -0,0 +1,303 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import type {SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {MonacoSchemaForm, MonacoSchemaFormGrid} from './MonacoSchemaForm'; +import {createSchema, value} from './helpers'; + +test.describe('SchemaRenderer Monaco', () => { + // Each grid mounts 64 editors; keep these scenarios in one worker per browser. + test.describe.configure({mode: 'default'}); + + for (const mode of [SchemaRendererMode.Form, SchemaRendererMode.Overview]) { + test(`${mode} layouts sizes and views`, async ({mount, page, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'editing JSON updates the form value', + props: ((): SchemaFormProps => { + const schema = createSchema('column'); + return { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'disabled editor preserves its value', + props: ((): SchemaFormProps => { + const base = createSchema('column'); + const schema = { + ...base, + nodeParameters: { + ...base.nodeParameters, + flags: {open: true, disabled: true}, + }, + }; + return { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'overview editor is read only', + props: ((): SchemaFormProps => { + const schema = createSchema('column'); + return { + schema: schema, + value: value, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'invalid JSON can ignore editor validation without replacing the editor', + props: ((): SchemaFormProps => { + const base = createSchema('column'); + const schema = { + ...base, + nodeParameters: { + ...base.nodeParameters, + entityProps: { + ...base.nodeParameters?.entityProps, + withDialog: false, + ignoreMonacoErrors: true, + }, + }, + }; + return { + schema: schema, + value: '{broken json}', + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: true, + width: 600, + }; + })(), + }, + { + label: 'editor diagnostics reach the form validation', + props: ((): SchemaFormProps => { + const schema = createSchema('column'); + return { + schema: schema, + value: '{broken json}', + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: true, + width: 600, + }; + })(), + }, + { + label: 'empty overview uses the empty value marker', + props: ((): SchemaFormProps => { + const schema = createSchema('cell'); + return { + schema: schema, + value: undefined, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const grid = await mount( + { + const schema = createSchema(layout); + const label = `layout=${layoutName} / size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.locator('.monaco-editor')).toBeVisible(); + await expect(component.locator('.view-lines')).toContainText('enabled'); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'editing JSON updates the form value', + exact: true, + }); + + const editor = component.locator('.monaco-editor textarea.inputarea'); + await expect(component.locator('.view-lines')).toContainText('enabled'); + await editor.click(); + const selectAll = await page.evaluate(() => + navigator.userAgent.includes('Macintosh') ? 'Meta+A' : 'Control+A', + ); + await editor.press(selectAll); + await editor.press('Backspace'); + await expect(component.getByTestId('form-value')).toHaveText(JSON.stringify('')); + await page.keyboard.type('{"enabled":false}'); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify('{"enabled":false}'), + ); + await editor.blur(); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'disabled editor preserves its value', + exact: true, + }); + + await expect(component.locator('.monaco-editor')).toBeVisible(); + await component.locator('.monaco-editor textarea.inputarea').press('End'); + await page.keyboard.insertText('changed'); + await expect(component.getByTestId('form-value')).toHaveText(JSON.stringify(value)); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'overview editor is read only', + exact: true, + }); + + await expect(component.locator('.view-lines')).toContainText('enabled'); + await component.locator('.monaco-editor textarea.inputarea').press('End'); + await page.keyboard.insertText('changed'); + await expect(component.getByTestId('form-value')).toHaveText(JSON.stringify(value)); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'invalid JSON can ignore editor validation without replacing the editor', + exact: true, + }); + + await expect(component.locator('.monaco-editor')).toBeVisible(); + await expect(component.locator('.view-lines')).toContainText('broken'); + await expect(component.getByTestId('input-dialog-toggler')).toHaveCount(0); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify('{broken json}'), + ); + await expect(component.locator('.squiggly-error').first()).toBeVisible(); + await component.getByRole('button', {name: 'Submit', exact: true}).click(); + await expect(component.locator('[data-sr-error="true"]')).toHaveCount(0); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'editor diagnostics reach the form validation', + exact: true, + }); + + await expect(component.locator('.squiggly-error').first()).toBeVisible(); + await component.getByRole('button', {name: 'Submit', exact: true}).click(); + await expect(component.locator('[data-sr-error="true"]')).toContainText('Line 1'); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'empty overview uses the empty value marker', + exact: true, + }); + + await expect(component.getByText('—', {exact: true})).toBeVisible(); + } + await expectScreenshot({scale: 'css'}); + }); + } + + test('dialog opens the actual editor and closes again', async ({ + mount, + page, + expectScreenshot, + }) => { + const schema = createSchema('column'); + const component = await mount( + , + ); + await component.getByTestId('input-dialog-toggler').click(); + const dialog = page.getByRole('dialog'); + await expect(dialog.locator('.monaco-editor')).toBeVisible(); + await expect(dialog.locator('.view-lines')).toContainText('enabled'); + await expectScreenshot({component: dialog, screenshotName: 'Monaco expanded dialog'}); + await dialog.getByTestId('input-dialog-toggler').click(); + await expect(dialog).toBeHidden(); + await expect(component.locator('.monaco-editor')).toBeVisible(); + }); +}); diff --git a/src/lib/unstable/kit/entities/Monaco/__tests__/MonacoSchemaForm.tsx b/src/lib/unstable/kit/entities/Monaco/__tests__/MonacoSchemaForm.tsx new file mode 100644 index 00000000..99e6693f --- /dev/null +++ b/src/lib/unstable/kit/entities/Monaco/__tests__/MonacoSchemaForm.tsx @@ -0,0 +1,47 @@ +/// + +import React from 'react'; + +import {useThemeType} from '@gravity-ui/uikit'; +import type {Environment} from 'monaco-editor/esm/vs/editor/editor.api'; +import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'; +import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'; +import 'monaco-editor/esm/vs/language/json/monaco.contribution'; +import MonacoEditor from 'react-monaco-editor'; +import type {MonacoEditorProps} from 'react-monaco-editor/lib/types'; + +import {SchemaForm, type SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; + +const browserGlobal = self as typeof self & {MonacoEnvironment?: Environment}; + +browserGlobal.MonacoEnvironment = { + getWorker: (_moduleId, label) => (label === 'json' ? new JsonWorker() : new EditorWorker()), +}; + +const ThemedMonacoEditor = (props: MonacoEditorProps) => { + const theme = useThemeType(); + + return ; +}; + +// Functions stay on the browser side of the component-testing boundary. +export const MonacoSchemaForm = ({userContext, ...rendererProps}: SchemaFormProps) => ( + +); + +export const MonacoSchemaFormGrid = ({cases, caseHeight}: SchemaFormGridProps) => ( + ({ + ...frame, + props: { + ...props, + userContext: {...props.userContext, MonacoEditor: ThemedMonacoEditor}, + }, + }))} + /> +); diff --git a/src/lib/unstable/kit/entities/Monaco/__tests__/helpers.ts b/src/lib/unstable/kit/entities/Monaco/__tests__/helpers.ts new file mode 100644 index 00000000..7a938dda --- /dev/null +++ b/src/lib/unstable/kit/entities/Monaco/__tests__/helpers.ts @@ -0,0 +1,26 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +export const createSchema = (layout: string | undefined): JsonSchemaString => ({ + type: JsonSchemaType.String, + title: 'Monaco', + description: 'Description for Monaco', + + nodeParameters: { + type: NodeType.String, + entity: 'monaco_input', + layout, + entityProps: { + language: 'json', + height: 180, + width: '100%', + withDialog: true, + ignoreMonacoErrors: false, + options: {scrollBeyondLastLine: false, lineNumbers: 'on'}, + }, + flags: {open: true}, + layoutProps: {descriptionType: 'bottom'}, + }, +}); + +export const value = '{"enabled":true}'; diff --git a/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-form-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-form-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..442af72b Binary files /dev/null and b/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-form-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-form-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-form-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..d8cfd4f7 Binary files /dev/null and b/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-form-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-form-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-form-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..5430bad5 Binary files /dev/null and b/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-form-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-form-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-form-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..0b10e562 Binary files /dev/null and b/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-form-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-overview-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-overview-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..8ead7fca Binary files /dev/null and b/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-overview-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-overview-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-overview-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..e80aa3c8 Binary files /dev/null and b/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-overview-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-overview-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-overview-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..d095186c Binary files /dev/null and b/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-overview-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-overview-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-overview-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..5d886b4a Binary files /dev/null and b/src/lib/unstable/kit/entities/ObjectEntity/__snapshots__/ObjectEntity.visual.test.tsx-snapshots/SchemaRenderer-ObjectEntity-overview-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/ObjectEntity/__tests__/ObjectEntity.visual.test.tsx b/src/lib/unstable/kit/entities/ObjectEntity/__tests__/ObjectEntity.visual.test.tsx new file mode 100644 index 00000000..4f551f3e --- /dev/null +++ b/src/lib/unstable/kit/entities/ObjectEntity/__tests__/ObjectEntity.visual.test.tsx @@ -0,0 +1,166 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {expectInsideScreenshot} from '~playwright/core/expectInsideScreenshot'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {JsonSchemaType, NodeType, SchemaRendererMode} from '../../../../core/constants'; +import type {JsonSchema} from '../../../../core/types'; + +import {createSchema, value} from './helpers'; + +test.describe('SchemaRenderer ObjectEntity', () => { + for (const mode of [SchemaRendererMode.Form, SchemaRendererMode.Overview]) { + test(`${mode} layouts sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'explicit property order and editing preserve other values', + props: ((): SchemaFormProps => { + const base = createSchema('section'); + const schema: JsonSchema = { + ...base, + nodeParameters: { + ...base.nodeParameters, + entityProps: {order: ['age', 'name']}, + }, + }; + return { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'hidden child keeps its data without a visible control', + props: ((): SchemaFormProps => { + const base = createSchema('section'); + const schema: JsonSchema = { + ...base, + properties: { + ...base.properties, + name: { + type: JsonSchemaType.String, + title: 'Private name', + nodeParameters: { + type: NodeType.String, + entity: 'base', + layout: 'row', + flags: {hidden: true}, + }, + }, + }, + }; + return { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const grid = await mount( + { + const schema = createSchema(layout); + const label = `layout=${layoutName} / size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 1000, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + if (mode === SchemaRendererMode.Form) { + await expect(component.getByTestId('input.name').locator('input')).toHaveValue( + 'Ada', + ); + } else { + await expect(component.getByText('Ada', {exact: true})).toBeVisible(); + } + const frame = component; + if (mode === SchemaRendererMode.Form) { + for (const qa of ['input.name', 'input.age']) { + await expectInsideScreenshot( + component.getByTestId(qa).locator('input'), + frame, + ); + } + } else { + for (const text of ['Ada', '37']) { + await expectInsideScreenshot( + component.getByText(text, {exact: true}), + frame, + ); + } + } + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'explicit property order and editing preserve other values', + exact: true, + }); + await expect(component.locator('input').first()).toHaveValue('37'); + await expect(component.locator('input').nth(1)).toHaveValue('Ada'); + await component.getByTestId('input.name').locator('input').fill('Grace'); + await expect(component.getByTestId('form-value')).toHaveText( + '{"name":"Grace","age":37}', + ); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'hidden child keeps its data without a visible control', + exact: true, + }); + await expect(component.getByTestId('input.name')).not.toBeVisible(); + await expect(component.getByTestId('input.age').locator('input')).toHaveValue('37'); + await expect(component.getByTestId('form-value')).toHaveText( + '{"name":"Ada","age":37}', + ); + } + await expectScreenshot({scale: 'css'}); + }); + } +}); diff --git a/src/lib/unstable/kit/entities/ObjectEntity/__tests__/helpers.ts b/src/lib/unstable/kit/entities/ObjectEntity/__tests__/helpers.ts new file mode 100644 index 00000000..72e0d607 --- /dev/null +++ b/src/lib/unstable/kit/entities/ObjectEntity/__tests__/helpers.ts @@ -0,0 +1,33 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchema, JsonSchemaObject} from '../../../../core/types'; + +const nameSchema: JsonSchema = { + type: JsonSchemaType.String, + title: 'Name', + minLength: 2, + nodeParameters: {type: NodeType.String, entity: 'base', layout: 'row'}, +}; +const ageSchema: JsonSchema = { + type: JsonSchemaType.Number, + title: 'Age', + minimum: 0, + maximum: 100, + nodeParameters: {type: NodeType.Number, entity: 'base', layout: 'row'}, +}; + +export const createSchema = (layout: string | undefined): JsonSchemaObject => ({ + type: JsonSchemaType.Object, + title: 'ObjectEntity', + description: 'Description for ObjectEntity', + properties: {name: nameSchema, age: ageSchema}, + nodeParameters: { + type: NodeType.Object, + entity: 'base', + layout, + entityProps: {order: ['name', 'age']}, + flags: {open: true}, + layoutProps: {descriptionType: 'bottom'}, + }, +}); + +export const value = {name: 'Ada', age: 37}; diff --git a/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-form-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-form-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..54ab42d7 Binary files /dev/null and b/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-form-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-form-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-form-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..313d2def Binary files /dev/null and b/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-form-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-form-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-form-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..83970f7a Binary files /dev/null and b/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-form-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-form-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-form-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..2e2135e0 Binary files /dev/null and b/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-form-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-overview-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-overview-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..ea2d955f Binary files /dev/null and b/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-overview-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-overview-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-overview-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..37a6b901 Binary files /dev/null and b/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-overview-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-overview-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-overview-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..8ff1c4d9 Binary files /dev/null and b/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-overview-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-overview-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-overview-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..326502bf Binary files /dev/null and b/src/lib/unstable/kit/entities/ObjectInline/__snapshots__/ObjectInline.visual.test.tsx-snapshots/SchemaRenderer-ObjectInline-overview-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/ObjectInline/__tests__/ObjectInline.visual.test.tsx b/src/lib/unstable/kit/entities/ObjectInline/__tests__/ObjectInline.visual.test.tsx new file mode 100644 index 00000000..64065cfb --- /dev/null +++ b/src/lib/unstable/kit/entities/ObjectInline/__tests__/ObjectInline.visual.test.tsx @@ -0,0 +1,147 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {expectInsideScreenshot} from '~playwright/core/expectInsideScreenshot'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {createSchema, value} from './helpers'; + +test.describe('SchemaRenderer ObjectInline', () => { + for (const mode of [SchemaRendererMode.Form, SchemaRendererMode.Overview]) { + test(`${mode} layouts sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'per property delimiters respect the explicit order', + props: ((): SchemaFormProps => { + const base = createSchema('row'); + const schema = { + ...base, + nodeParameters: { + ...base.nodeParameters, + entityProps: { + order: ['age', 'name'], + delimiter: {age: 'years /', name: '!'}, + }, + }, + }; + return { + schema: schema, + value: value, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'empty overview uses the empty value marker', + props: ((): SchemaFormProps => { + const schema = createSchema('cell'); + return { + schema: schema, + value: {}, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const grid = await mount( + { + const schema = createSchema(layout); + const label = `layout=${layoutName} / size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 1000, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + if (mode === SchemaRendererMode.Form) { + await expect(component.getByTestId('input.name').locator('input')).toHaveValue( + 'Ada', + ); + } else { + await expect(component.getByText('Ada', {exact: true})).toBeVisible(); + } + const frame = component; + if (mode === SchemaRendererMode.Form) { + for (const qa of ['input.name', 'input.age']) { + await expectInsideScreenshot( + component.getByTestId(qa).locator('input'), + frame, + ); + } + } else { + for (const text of ['Ada', '37']) { + await expectInsideScreenshot( + component.getByText(text, {exact: true}), + frame, + ); + } + } + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'per property delimiters respect the explicit order', + exact: true, + }); + await expect(component.locator('.sr-object-inline__delimiter')).toHaveText([ + 'years /', + '!', + ]); + await expect(component.getByText('Ada', {exact: true})).toBeVisible(); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'empty overview uses the empty value marker', + exact: true, + }); + await expect(component.getByText('—', {exact: true})).toBeVisible(); + } + await expectScreenshot({scale: 'css'}); + }); + } +}); diff --git a/src/lib/unstable/kit/entities/ObjectInline/__tests__/helpers.ts b/src/lib/unstable/kit/entities/ObjectInline/__tests__/helpers.ts new file mode 100644 index 00000000..5d4f73a6 --- /dev/null +++ b/src/lib/unstable/kit/entities/ObjectInline/__tests__/helpers.ts @@ -0,0 +1,39 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchema, JsonSchemaObject} from '../../../../core/types'; + +const nameSchema: JsonSchema = { + type: JsonSchemaType.String, + title: 'Name', + minLength: 2, + nodeParameters: {type: NodeType.String, entity: 'base', layout: 'row'}, +}; +const ageSchema: JsonSchema = { + type: JsonSchemaType.Number, + title: 'Age', + minimum: 0, + maximum: 100, + nodeParameters: {type: NodeType.Number, entity: 'base', layout: 'row'}, +}; + +export const createSchema = (layout: string | undefined): JsonSchemaObject => ({ + type: JsonSchemaType.Object, + title: 'ObjectInline', + description: 'Description for ObjectInline', + properties: { + name: { + ...nameSchema, + nodeParameters: {...nameSchema.nodeParameters, layout: 'transparent'}, + }, + age: {...ageSchema, nodeParameters: {...ageSchema.nodeParameters, layout: 'transparent'}}, + }, + nodeParameters: { + type: NodeType.Object, + entity: 'inline', + layout, + entityProps: {delimiter: ':', order: ['name', 'age']}, + flags: {open: true}, + layoutProps: {descriptionType: 'bottom'}, + }, +}); + +export const value = {name: 'Ada', age: 37}; diff --git a/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-form-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-form-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..b54817cb Binary files /dev/null and b/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-form-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-form-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-form-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..b0f1ce60 Binary files /dev/null and b/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-form-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-form-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-form-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..31356b78 Binary files /dev/null and b/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-form-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-form-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-form-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..b7c1fbbe Binary files /dev/null and b/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-form-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-overview-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-overview-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..3138e44c Binary files /dev/null and b/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-overview-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-overview-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-overview-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..6089f049 Binary files /dev/null and b/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-overview-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-overview-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-overview-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..37f1980c Binary files /dev/null and b/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-overview-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-overview-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-overview-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..bb25d65a Binary files /dev/null and b/src/lib/unstable/kit/entities/OneOfNested/__snapshots__/OneOfNested.visual.test.tsx-snapshots/SchemaRenderer-OneOfNested-overview-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/OneOfNested/__tests__/OneOfNested.visual.test.tsx b/src/lib/unstable/kit/entities/OneOfNested/__tests__/OneOfNested.visual.test.tsx new file mode 100644 index 00000000..5cb2a151 --- /dev/null +++ b/src/lib/unstable/kit/entities/OneOfNested/__tests__/OneOfNested.visual.test.tsx @@ -0,0 +1,166 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {expectInsideScreenshot} from '~playwright/core/expectInsideScreenshot'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {JsonSchemaType, NodeType, SchemaRendererMode} from '../../../../core/constants'; + +import {createSchema, value} from './helpers'; + +test.describe('SchemaRenderer OneOfNested', () => { + for (const mode of [SchemaRendererMode.Form, SchemaRendererMode.Overview]) { + test(`${mode} layouts sizes and views`, async ({mount, page, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'switching branches discards the previous branch value', + props: ((): SchemaFormProps => { + const schema = createSchema('section'); + return { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'select toggler and order can start from a different branch', + props: ((): SchemaFormProps => { + const base = createSchema('section'); + const schema = { + ...base, + nodeParameters: { + ...base.nodeParameters, + entityProps: { + order: ['age', 'name'], + withIndent: false, + toggler: { + type: JsonSchemaType.String, + title: 'Field', + enum: ['name', 'age'], + nodeParameters: { + type: NodeType.String, + entity: 'select', + layout: 'row', + entityProps: { + enumDescriptions: {name: 'Name', age: 'Age'}, + }, + }, + }, + }, + }, + }; + return { + schema: schema, + value: undefined, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const grid = await mount( + { + const schema = createSchema(layout); + const label = `layout=${layoutName} / size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 1000, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + if (mode === SchemaRendererMode.Form) { + await expect(component.getByTestId('input.name').locator('input')).toHaveValue( + 'Ada', + ); + } else { + await expect(component.getByText('Ada', {exact: true})).toBeVisible(); + } + const frame = component; + if (mode === SchemaRendererMode.Form) { + for (const qa of ['input.name']) { + await expectInsideScreenshot( + component.getByTestId(qa).locator('input'), + frame, + ); + } + } else { + for (const text of ['Ada']) { + await expectInsideScreenshot( + component.getByText(text, {exact: true}), + frame, + ); + } + } + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'switching branches discards the previous branch value', + exact: true, + }); + + await component.getByRole('switch').press('Space'); + await component.getByTestId('input.age').locator('input').fill('37'); + await component.getByTestId('input.age').locator('input').blur(); + await expect(component.getByTestId('input.name')).toHaveCount(0); + await expect(component.getByTestId('form-value')).toHaveText('{"age":37}'); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'select toggler and order can start from a different branch', + exact: true, + }); + + await expect(component.getByTestId('input.age').locator('input')).toBeVisible(); + await component.getByRole('combobox').click(); + await page.getByRole('option', {name: 'Name', exact: true}).click(); + await component.getByTestId('input.name').locator('input').fill('Grace'); + await expect(component.getByTestId('form-value')).toHaveText('{"name":"Grace"}'); + } + await expectScreenshot({scale: 'css'}); + }); + } +}); diff --git a/src/lib/unstable/kit/entities/OneOfNested/__tests__/helpers.ts b/src/lib/unstable/kit/entities/OneOfNested/__tests__/helpers.ts new file mode 100644 index 00000000..599563f8 --- /dev/null +++ b/src/lib/unstable/kit/entities/OneOfNested/__tests__/helpers.ts @@ -0,0 +1,42 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchema, JsonSchemaObject} from '../../../../core/types'; + +const nameSchema: JsonSchema = { + type: JsonSchemaType.String, + title: 'Name', + minLength: 2, + nodeParameters: {type: NodeType.String, entity: 'base', layout: 'row'}, +}; +const ageSchema: JsonSchema = { + type: JsonSchemaType.Number, + title: 'Age', + minimum: 0, + maximum: 100, + nodeParameters: {type: NodeType.Number, entity: 'base', layout: 'row'}, +}; + +export const createSchema = (layout: string | undefined): JsonSchemaObject => ({ + type: JsonSchemaType.Object, + title: 'OneOfNested', + description: 'Description for OneOfNested', + properties: {name: nameSchema, age: ageSchema}, + nodeParameters: { + type: NodeType.Object, + entity: 'one_of_nested', + layout, + entityProps: { + booleanToKey: {true: 'name', false: 'age'}, + toggler: { + type: JsonSchemaType.Boolean, + title: 'Use name', + nodeParameters: {type: NodeType.Boolean, entity: 'switch', layout: 'row'}, + }, + withIndent: true, + order: ['name', 'age'], + }, + flags: {open: true}, + layoutProps: {descriptionType: 'bottom'}, + }, +}); + +export const value = {name: 'Ada'}; diff --git a/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-form-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-form-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..8d108437 Binary files /dev/null and b/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-form-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-form-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-form-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..422b3aae Binary files /dev/null and b/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-form-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-form-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-form-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..50af5c19 Binary files /dev/null and b/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-form-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-form-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-form-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..c8566639 Binary files /dev/null and b/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-form-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-overview-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-overview-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..c6076bd3 Binary files /dev/null and b/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-overview-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-overview-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-overview-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..bb2a0776 Binary files /dev/null and b/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-overview-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-overview-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-overview-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..365ad880 Binary files /dev/null and b/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-overview-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-overview-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-overview-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..2134d488 Binary files /dev/null and b/src/lib/unstable/kit/entities/TextContent/__snapshots__/TextContent.visual.test.tsx-snapshots/SchemaRenderer-TextContent-overview-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/entities/TextContent/__tests__/TextContent.visual.test.tsx b/src/lib/unstable/kit/entities/TextContent/__tests__/TextContent.visual.test.tsx new file mode 100644 index 00000000..e3c6c47f --- /dev/null +++ b/src/lib/unstable/kit/entities/TextContent/__tests__/TextContent.visual.test.tsx @@ -0,0 +1,141 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {createSchema, value} from './helpers'; + +test.describe('SchemaRenderer TextContent', () => { + for (const mode of [SchemaRendererMode.Form, SchemaRendererMode.Overview]) { + test(`${mode} layouts sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'description fallback with empty input', + props: ((): SchemaFormProps => { + const base = createSchema('row'); + const schema = { + ...base, + description: 'Unspecified status', + nodeParameters: { + ...base.nodeParameters, + entityProps: {iconName: 'CircleInfo', iconProps: {size: 20}}, + }, + }; + return { + schema: schema, + value: undefined, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'custom title and long value', + props: ((): SchemaFormProps => { + const base = createSchema('column'); + const schema = { + ...base, + nodeParameters: { + ...base.nodeParameters, + entityProps: { + title: 'Build status', + theme: 'warning', + iconName: 'Clock', + }, + }, + }; + return { + schema: schema, + value: 'Waiting for the deployment approval from the maintenance team', + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const grid = await mount( + { + const schema = createSchema(layout); + const label = `layout=${layoutName} / size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByText('Passed', {exact: true})).toBeVisible(); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'description fallback with empty input', + exact: true, + }); + await expect( + component + .locator('.g-label.sr-text-content') + .getByText('Unspecified status', {exact: true}), + ).toBeVisible(); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'custom title and long value', + exact: true, + }); + await expect(component.getByText('Build status', {exact: true})).toBeVisible(); + await expect( + component.getByText( + 'Waiting for the deployment approval from the maintenance team', + { + exact: true, + }, + ), + ).toBeVisible(); + } + await expectScreenshot({scale: 'css'}); + }); + } +}); diff --git a/src/lib/unstable/kit/entities/TextContent/__tests__/helpers.ts b/src/lib/unstable/kit/entities/TextContent/__tests__/helpers.ts new file mode 100644 index 00000000..69a4ffb0 --- /dev/null +++ b/src/lib/unstable/kit/entities/TextContent/__tests__/helpers.ts @@ -0,0 +1,19 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +export const createSchema = (layout: string | undefined): JsonSchemaString => ({ + type: JsonSchemaType.String, + title: 'TextContent', + description: 'Description for TextContent', + + nodeParameters: { + type: NodeType.String, + entity: 'text_content', + layout, + entityProps: {iconName: 'CircleInfo', title: 'Build result'}, + flags: {open: true}, + layoutProps: {descriptionType: 'bottom'}, + }, +}); + +export const value = 'Passed'; diff --git a/src/lib/unstable/kit/form-entities/CheckboxGroupInput/__snapshots__/CheckboxGroupInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxGroupInput-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/form-entities/CheckboxGroupInput/__snapshots__/CheckboxGroupInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxGroupInput-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..bbbb6682 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/CheckboxGroupInput/__snapshots__/CheckboxGroupInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxGroupInput-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/CheckboxGroupInput/__snapshots__/CheckboxGroupInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxGroupInput-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/form-entities/CheckboxGroupInput/__snapshots__/CheckboxGroupInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxGroupInput-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..d042ad66 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/CheckboxGroupInput/__snapshots__/CheckboxGroupInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxGroupInput-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/CheckboxGroupInput/__snapshots__/CheckboxGroupInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxGroupInput-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/form-entities/CheckboxGroupInput/__snapshots__/CheckboxGroupInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxGroupInput-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..cb32b72f Binary files /dev/null and b/src/lib/unstable/kit/form-entities/CheckboxGroupInput/__snapshots__/CheckboxGroupInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxGroupInput-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/CheckboxGroupInput/__snapshots__/CheckboxGroupInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxGroupInput-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/form-entities/CheckboxGroupInput/__snapshots__/CheckboxGroupInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxGroupInput-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..38dcaeb3 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/CheckboxGroupInput/__snapshots__/CheckboxGroupInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxGroupInput-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/CheckboxGroupInput/__tests__/CheckboxGroupInput.visual.test.tsx b/src/lib/unstable/kit/form-entities/CheckboxGroupInput/__tests__/CheckboxGroupInput.visual.test.tsx new file mode 100644 index 00000000..83422bba --- /dev/null +++ b/src/lib/unstable/kit/form-entities/CheckboxGroupInput/__tests__/CheckboxGroupInput.visual.test.tsx @@ -0,0 +1,159 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {schema, value} from './helpers'; + +test.describe('SchemaRenderer CheckboxGroupInput', () => { + test('layouts sizes and views', async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'disabled preserves the value', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + flags: {open: true, disabled: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'column layout and disabled option', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + entityProps: { + ...schema.nodeParameters!.entityProps, + direction: 'column', + }, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'empty selection', + props: { + schema: schema, + value: [], + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + ]; + const additionalCases = stateCases; + + const grid = await mount( + { + const label = `layout=${variant.layoutName} / size=${variant.size} / view=${variant.view}`; + return { + label, + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + layout: variant.layout, + flags: {open: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: variant.size, + view: variant.view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByRole('checkbox').first()).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'disabled preserves the value', + exact: true, + }); + await expect(component.getByRole('checkbox').first()).toBeDisabled(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'column layout and disabled option', + exact: true, + }); + await expect( + component.getByRole('checkbox', {name: 'Gamma', exact: true}), + ).toBeDisabled(); + await component.getByRole('checkbox', {name: 'Beta', exact: true}).check(); + await expect(component.getByTestId('form-value')).toHaveText('["alpha","beta"]'); + await component.getByRole('checkbox', {name: 'Alpha', exact: true}).uncheck(); + await expect(component.getByTestId('form-value')).toHaveText('["beta"]'); + } + { + const component = grid.getByRole('region', {name: 'empty selection', exact: true}); + await expect( + component.getByRole('checkbox', {name: 'Alpha', exact: true}), + ).not.toBeChecked(); + await expect( + component.getByRole('checkbox', {name: 'Beta', exact: true}), + ).not.toBeChecked(); + } + await expectScreenshot({scale: 'css'}); + }); +}); diff --git a/src/lib/unstable/kit/form-entities/CheckboxGroupInput/__tests__/helpers.ts b/src/lib/unstable/kit/form-entities/CheckboxGroupInput/__tests__/helpers.ts new file mode 100644 index 00000000..2d9a37de --- /dev/null +++ b/src/lib/unstable/kit/form-entities/CheckboxGroupInput/__tests__/helpers.ts @@ -0,0 +1,28 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaArray} from '../../../../core/types'; + +const settings: Partial = { + items: {type: JsonSchemaType.String, enum: ['alpha', 'beta', 'gamma']}, + nodeParameters: { + entityProps: { + enumDescriptions: {alpha: 'Alpha', beta: 'Beta', gamma: 'Gamma'}, + optionsDisabled: {gamma: true}, + direction: 'row', + }, + }, +}; + +export const schema: JsonSchemaArray = { + type: JsonSchemaType.Array, + title: 'CheckboxGroupInput', + description: 'Description for CheckboxGroupInput', + ...settings, + nodeParameters: { + type: NodeType.Array, + entity: 'checkbox_group', + layout: 'row', + ...settings.nodeParameters, + flags: {open: true}, + }, +}; +export const value = ['alpha']; diff --git a/src/lib/unstable/kit/form-entities/CheckboxInput/__snapshots__/CheckboxInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxInput-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/form-entities/CheckboxInput/__snapshots__/CheckboxInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxInput-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..347225df Binary files /dev/null and b/src/lib/unstable/kit/form-entities/CheckboxInput/__snapshots__/CheckboxInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxInput-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/CheckboxInput/__snapshots__/CheckboxInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxInput-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/form-entities/CheckboxInput/__snapshots__/CheckboxInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxInput-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..7b442ca2 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/CheckboxInput/__snapshots__/CheckboxInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxInput-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/CheckboxInput/__snapshots__/CheckboxInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxInput-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/form-entities/CheckboxInput/__snapshots__/CheckboxInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxInput-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..6eaef1aa Binary files /dev/null and b/src/lib/unstable/kit/form-entities/CheckboxInput/__snapshots__/CheckboxInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxInput-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/CheckboxInput/__snapshots__/CheckboxInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxInput-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/form-entities/CheckboxInput/__snapshots__/CheckboxInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxInput-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..5a2f3d4c Binary files /dev/null and b/src/lib/unstable/kit/form-entities/CheckboxInput/__snapshots__/CheckboxInput.visual.test.tsx-snapshots/SchemaRenderer-CheckboxInput-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/CheckboxInput/__tests__/CheckboxInput.visual.test.tsx b/src/lib/unstable/kit/form-entities/CheckboxInput/__tests__/CheckboxInput.visual.test.tsx new file mode 100644 index 00000000..3bc3caca --- /dev/null +++ b/src/lib/unstable/kit/form-entities/CheckboxInput/__tests__/CheckboxInput.visual.test.tsx @@ -0,0 +1,149 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {schema, value} from './helpers'; + +test.describe('SchemaRenderer CheckboxInput', () => { + test('layouts sizes and views', async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'disabled preserves the value', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + flags: {open: true, disabled: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'keyboard toggle updates a boolean', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'indeterminate presentation', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + entityProps: {content: 'Partial selection', indeterminate: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + ]; + const additionalCases = stateCases; + + const grid = await mount( + { + const label = `layout=${variant.layoutName} / size=${variant.size} / view=${variant.view}`; + return { + label, + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + layout: variant.layout, + flags: {open: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: variant.size, + view: variant.view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByRole('checkbox')).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'disabled preserves the value', + exact: true, + }); + await expect(component.getByRole('checkbox')).toBeDisabled(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'keyboard toggle updates a boolean', + exact: true, + }); + await component.getByRole('checkbox').press('Space'); + await expect(component.getByTestId('form-value')).toHaveText('true'); + } + { + const component = grid.getByRole('region', { + name: 'indeterminate presentation', + exact: true, + }); + await expect(component.getByRole('checkbox')).toHaveJSProperty('indeterminate', true); + } + await expectScreenshot({scale: 'css'}); + }); +}); diff --git a/src/lib/unstable/kit/form-entities/CheckboxInput/__tests__/helpers.ts b/src/lib/unstable/kit/form-entities/CheckboxInput/__tests__/helpers.ts new file mode 100644 index 00000000..bafdacd3 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/CheckboxInput/__tests__/helpers.ts @@ -0,0 +1,21 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaBoolean} from '../../../../core/types'; + +const settings: Partial = { + nodeParameters: {entityProps: {content: 'Receive updates'}}, +}; + +export const schema: JsonSchemaBoolean = { + type: JsonSchemaType.Boolean, + title: 'CheckboxInput', + description: 'Description for CheckboxInput', + ...settings, + nodeParameters: { + type: NodeType.Boolean, + entity: 'base', + layout: 'row', + ...settings.nodeParameters, + flags: {open: true}, + }, +}; +export const value = false; diff --git a/src/lib/unstable/kit/form-entities/ColorPickerInput/__snapshots__/ColorPickerInput.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerInput-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/form-entities/ColorPickerInput/__snapshots__/ColorPickerInput.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerInput-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..a36861f9 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/ColorPickerInput/__snapshots__/ColorPickerInput.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerInput-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/ColorPickerInput/__snapshots__/ColorPickerInput.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerInput-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/form-entities/ColorPickerInput/__snapshots__/ColorPickerInput.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerInput-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..c64f2325 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/ColorPickerInput/__snapshots__/ColorPickerInput.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerInput-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/ColorPickerInput/__snapshots__/ColorPickerInput.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerInput-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/form-entities/ColorPickerInput/__snapshots__/ColorPickerInput.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerInput-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..f181208b Binary files /dev/null and b/src/lib/unstable/kit/form-entities/ColorPickerInput/__snapshots__/ColorPickerInput.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerInput-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/ColorPickerInput/__snapshots__/ColorPickerInput.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerInput-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/form-entities/ColorPickerInput/__snapshots__/ColorPickerInput.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerInput-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..5aeb0d79 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/ColorPickerInput/__snapshots__/ColorPickerInput.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerInput-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/ColorPickerInput/__tests__/ColorPickerInput.visual.test.tsx b/src/lib/unstable/kit/form-entities/ColorPickerInput/__tests__/ColorPickerInput.visual.test.tsx new file mode 100644 index 00000000..0a246f06 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/ColorPickerInput/__tests__/ColorPickerInput.visual.test.tsx @@ -0,0 +1,161 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {expectPopupInCell, fitGridInViewport} from '~playwright/core/popupGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {schema, value} from './helpers'; + +test.describe('SchemaRenderer ColorPickerInput', () => { + test('layouts sizes and views', async ({mount, page, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'disabled preserves the value', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + flags: {open: true, disabled: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'editing hex updates the value', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'compact alpha picker popup', + height: 380, + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + entityProps: { + withAlpha: true, + compact: true, + popupPlacement: 'bottom-start', + }, + }, + }, + value: '#5282ff80', + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + ]; + const additionalCases = stateCases; + + const grid = await mount( + { + const label = `layout=${variant.layoutName} / size=${variant.size} / view=${variant.view}`; + return { + label, + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + layout: variant.layout, + flags: {open: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: variant.size, + view: variant.view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByRole('textbox')).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'disabled preserves the value', + exact: true, + }); + + await expect(component.getByRole('textbox')).toBeDisabled(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'editing hex updates the value', + exact: true, + }); + + await component.getByRole('textbox').fill('ff0000'); + await component.getByRole('textbox').blur(); + await expect(component.getByTestId('form-value')).toHaveText('"#ff0000"'); + } + await fitGridInViewport(page, grid); + const component = grid.getByRole('region', { + name: 'compact alpha picker popup', + exact: true, + }); + const control = component.locator('.g-color-picker__color-swatch'); + await control.click(); + await expect(component.getByRole('textbox')).toHaveCount(0); + await expectPopupInCell(page.locator('.g-color-picker__popup'), component, control); + await page.mouse.move(0, 0); + await expectScreenshot({scale: 'css'}); + }); +}); diff --git a/src/lib/unstable/kit/form-entities/ColorPickerInput/__tests__/helpers.ts b/src/lib/unstable/kit/form-entities/ColorPickerInput/__tests__/helpers.ts new file mode 100644 index 00000000..e4ab87bd --- /dev/null +++ b/src/lib/unstable/kit/form-entities/ColorPickerInput/__tests__/helpers.ts @@ -0,0 +1,21 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +const settings: Partial = { + nodeParameters: {entityProps: {withAlpha: false, compact: false}}, +}; + +export const schema: JsonSchemaString = { + type: JsonSchemaType.String, + title: 'ColorPickerInput', + description: 'Description for ColorPickerInput', + ...settings, + nodeParameters: { + type: NodeType.String, + entity: 'color_picker', + layout: 'row', + ...settings.nodeParameters, + flags: {open: true}, + }, +}; +export const value = '#5282ff'; diff --git a/src/lib/unstable/kit/form-entities/DateInput/__snapshots__/DateInput.visual.test.tsx-snapshots/SchemaRenderer-DateInput-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/form-entities/DateInput/__snapshots__/DateInput.visual.test.tsx-snapshots/SchemaRenderer-DateInput-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..df637f42 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/DateInput/__snapshots__/DateInput.visual.test.tsx-snapshots/SchemaRenderer-DateInput-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/DateInput/__snapshots__/DateInput.visual.test.tsx-snapshots/SchemaRenderer-DateInput-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/form-entities/DateInput/__snapshots__/DateInput.visual.test.tsx-snapshots/SchemaRenderer-DateInput-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..ba794331 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/DateInput/__snapshots__/DateInput.visual.test.tsx-snapshots/SchemaRenderer-DateInput-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/DateInput/__snapshots__/DateInput.visual.test.tsx-snapshots/SchemaRenderer-DateInput-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/form-entities/DateInput/__snapshots__/DateInput.visual.test.tsx-snapshots/SchemaRenderer-DateInput-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..fa4d567c Binary files /dev/null and b/src/lib/unstable/kit/form-entities/DateInput/__snapshots__/DateInput.visual.test.tsx-snapshots/SchemaRenderer-DateInput-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/DateInput/__snapshots__/DateInput.visual.test.tsx-snapshots/SchemaRenderer-DateInput-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/form-entities/DateInput/__snapshots__/DateInput.visual.test.tsx-snapshots/SchemaRenderer-DateInput-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..99c75936 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/DateInput/__snapshots__/DateInput.visual.test.tsx-snapshots/SchemaRenderer-DateInput-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/DateInput/__tests__/DateInput.visual.test.tsx b/src/lib/unstable/kit/form-entities/DateInput/__tests__/DateInput.visual.test.tsx new file mode 100644 index 00000000..ef4d1723 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/DateInput/__tests__/DateInput.visual.test.tsx @@ -0,0 +1,468 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {expectPopupInCell, fitGridInViewport} from '~playwright/core/popupGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {schema, value} from './helpers'; + +test.describe('SchemaRenderer DateInput', () => { + test('layouts sizes and views', async ({mount, page, expectScreenshot}) => { + await page.addInitScript(`{ + const NativeDate = Date; + const now = NativeDate.parse('2024-02-20T12:00:00.000Z'); + window.Date = class extends NativeDate { + constructor(...args) { super(...(args.length ? args : [now])); } + static now() { return now; } + }; +}`); + await page.reload(); + expect(await page.evaluate(() => Date.now())).toBe(Date.parse('2024-02-20T12:00:00.000Z')); + + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'disabled preserves the value', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + flags: {open: true, disabled: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'display format uses the selected time zone', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + entityProps: { + format: 'DD.MM.YYYY HH:mm', + timeZone: 'America/New_York', + hasClear: true, + }, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'clear removes the stored date', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'calendar selection default ISO string', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + entityProps: { + format: 'DD.MM.YYYY', + timeZone: 'UTC', + hasClear: true, + }, + }, + }, + value: '2024-02-20T00:00:00.000Z', + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'calendar selection output YYYY-MM-DD', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + entityProps: { + format: 'DD.MM.YYYY', + outputFormat: 'YYYY-MM-DD', + timeZone: 'UTC', + hasClear: true, + }, + }, + }, + value: '2024-02-20', + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'calendar selection output timestamp', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + entityProps: { + format: 'DD.MM.YYYY', + outputFormat: 'timestamp', + timeZone: 'UTC', + hasClear: true, + }, + }, + }, + value: {seconds: 1708387200, nanos: 0}, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'calendar selection output date', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + entityProps: { + format: 'DD.MM.YYYY', + outputFormat: 'date', + timeZone: 'UTC', + hasClear: true, + }, + }, + }, + value: undefined, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'calendar selection output date_time', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + entityProps: { + format: 'DD.MM.YYYY', + outputFormat: 'date_time', + timeZone: 'UTC', + hasClear: true, + }, + }, + }, + value: undefined, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'open calendar', + height: 360, + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + entityProps: { + format: 'DD.MM.YYYY', + timeZone: 'UTC', + hasClear: true, + popupPlacement: 'bottom-start', + }, + }, + }, + value: '2024-02-20T00:00:00.000Z', + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + ]; + const additionalCases = stateCases; + + const grid = await mount( + { + const label = `layout=${variant.layoutName} / size=${variant.size} / view=${variant.view}`; + return { + label, + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + layout: variant.layout, + flags: {open: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: variant.size, + view: variant.view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByRole('combobox')).toBeVisible(); + await expect + .poll(async () => + (await component.getByRole('combobox').inputValue()).replace( + /[\u2066-\u2069]/g, + '', + ), + ) + .toBe('20.02.2024'); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'disabled preserves the value', + exact: true, + }); + + await expect(component.getByRole('combobox')).toBeDisabled(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'display format uses the selected time zone', + exact: true, + }); + + await expect + .poll(async () => + (await component.getByRole('combobox').inputValue()).replace( + /[\u2066-\u2069]/g, + '', + ), + ) + .toBe('19.02.2024 19:00'); + await expect(component.getByTestId('form-value')).toHaveText(JSON.stringify(value)); + } + { + const component = grid.getByRole('region', { + name: 'clear removes the stored date', + exact: true, + }); + + await component.getByRole('button', {name: 'Clear', exact: true}).click(); + await expect(component.getByTestId('form-value')).toHaveText(''); + } + { + const component = grid.getByRole('region', { + name: 'calendar selection default ISO string', + exact: true, + }); + await component.getByRole('button', {name: 'Calendar', exact: true}).click(); + await expect(page.getByRole('grid')).toBeVisible(); + await page.getByRole('gridcell').filter({hasText: /^21$/}).getByRole('button').click(); + await expect(component.getByTestId('form-value')).toHaveText( + '"2024-02-21T00:00:00.000Z"', + ); + await expect(component.getByTestId('form-value-type')).toHaveText('string'); + await expect + .poll(async () => + (await component.getByRole('combobox').inputValue()).replace( + /[\u2066-\u2069]/g, + '', + ), + ) + .toBe('21.02.2024'); + } + { + const component = grid.getByRole('region', { + name: 'calendar selection output YYYY-MM-DD', + exact: true, + }); + await component.getByRole('button', {name: 'Calendar', exact: true}).click(); + await expect(page.getByRole('grid')).toBeVisible(); + await page.getByRole('gridcell').filter({hasText: /^21$/}).getByRole('button').click(); + await expect(component.getByTestId('form-value')).toHaveText('"2024-02-21"'); + await expect(component.getByTestId('form-value-type')).toHaveText('string'); + await expect + .poll(async () => + (await component.getByRole('combobox').inputValue()).replace( + /[\u2066-\u2069]/g, + '', + ), + ) + .toBe('21.02.2024'); + } + { + const component = grid.getByRole('region', { + name: 'calendar selection output timestamp', + exact: true, + }); + await component.getByRole('button', {name: 'Calendar', exact: true}).click(); + await expect(page.getByRole('grid')).toBeVisible(); + await page.getByRole('gridcell').filter({hasText: /^21$/}).getByRole('button').click(); + await expect(component.getByTestId('form-value')).toHaveText( + '{"seconds":1708473600,"nanos":0}', + ); + await expect(component.getByTestId('form-value-type')).toHaveText('object'); + await expect + .poll(async () => + (await component.getByRole('combobox').inputValue()).replace( + /[\u2066-\u2069]/g, + '', + ), + ) + .toBe('21.02.2024'); + } + { + const component = grid.getByRole('region', { + name: 'calendar selection output date', + exact: true, + }); + await component.getByRole('button', {name: 'Calendar', exact: true}).click(); + await expect(page.getByRole('grid')).toBeVisible(); + await page.getByRole('gridcell').filter({hasText: /^21$/}).getByRole('button').click(); + await expect(component.getByTestId('form-value')).toHaveText( + '"2024-02-21T00:00:00.000Z"', + ); + await expect(component.getByTestId('form-value-type')).toHaveText('date'); + await expect + .poll(async () => + (await component.getByRole('combobox').inputValue()).replace( + /[\u2066-\u2069]/g, + '', + ), + ) + .toBe('21.02.2024'); + } + { + const component = grid.getByRole('region', { + name: 'calendar selection output date_time', + exact: true, + }); + await component.getByRole('button', {name: 'Calendar', exact: true}).click(); + await expect(page.getByRole('grid')).toBeVisible(); + await page.getByRole('gridcell').filter({hasText: /^21$/}).getByRole('button').click(); + await expect(component.getByTestId('form-value')).toHaveText( + '"2024-02-21T00:00:00.000Z"', + ); + await expect(component.getByTestId('form-value-type')).toHaveText('date_time'); + await expect + .poll(async () => + (await component.getByRole('combobox').inputValue()).replace( + /[\u2066-\u2069]/g, + '', + ), + ) + .toBe('21.02.2024'); + } + await fitGridInViewport(page, grid); + const component = grid.getByRole('region', { + name: 'open calendar', + exact: true, + includeHidden: true, + }); + const control = component.getByRole('button', { + name: 'Calendar', + exact: true, + includeHidden: true, + }); + await control.click(); + await expect(page.getByRole('grid')).toBeVisible(); + await expectPopupInCell( + page.locator('.g-popup').filter({has: page.getByRole('grid')}), + component, + control, + ); + await page.mouse.move(0, 0); + await expectScreenshot({scale: 'css'}); + }); +}); diff --git a/src/lib/unstable/kit/form-entities/DateInput/__tests__/helpers.ts b/src/lib/unstable/kit/form-entities/DateInput/__tests__/helpers.ts new file mode 100644 index 00000000..0f779b17 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/DateInput/__tests__/helpers.ts @@ -0,0 +1,26 @@ +import {NodeType} from '../../../../core/constants'; +import type {JsonSchemaAny} from '../../../../core/types'; + +const settings: Partial = { + nodeParameters: { + entityProps: { + format: 'DD.MM.YYYY', + timeZone: 'UTC', + hasClear: true, + }, + }, +}; + +export const schema: JsonSchemaAny = { + title: 'DateInput', + description: 'Description for DateInput', + ...settings, + nodeParameters: { + type: NodeType.Any, + entity: 'date', + layout: 'row', + ...settings.nodeParameters, + flags: {open: true}, + }, +}; +export const value = '2024-02-20T00:00:00.000Z'; diff --git a/src/lib/unstable/kit/form-entities/FileInput/__snapshots__/FileInput.visual.test.tsx-snapshots/SchemaRenderer-FileInput-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/form-entities/FileInput/__snapshots__/FileInput.visual.test.tsx-snapshots/SchemaRenderer-FileInput-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..3df4ad34 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/FileInput/__snapshots__/FileInput.visual.test.tsx-snapshots/SchemaRenderer-FileInput-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/FileInput/__snapshots__/FileInput.visual.test.tsx-snapshots/SchemaRenderer-FileInput-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/form-entities/FileInput/__snapshots__/FileInput.visual.test.tsx-snapshots/SchemaRenderer-FileInput-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..1de033fb Binary files /dev/null and b/src/lib/unstable/kit/form-entities/FileInput/__snapshots__/FileInput.visual.test.tsx-snapshots/SchemaRenderer-FileInput-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/FileInput/__snapshots__/FileInput.visual.test.tsx-snapshots/SchemaRenderer-FileInput-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/form-entities/FileInput/__snapshots__/FileInput.visual.test.tsx-snapshots/SchemaRenderer-FileInput-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..ab702981 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/FileInput/__snapshots__/FileInput.visual.test.tsx-snapshots/SchemaRenderer-FileInput-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/FileInput/__snapshots__/FileInput.visual.test.tsx-snapshots/SchemaRenderer-FileInput-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/form-entities/FileInput/__snapshots__/FileInput.visual.test.tsx-snapshots/SchemaRenderer-FileInput-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..e71f9a6b Binary files /dev/null and b/src/lib/unstable/kit/form-entities/FileInput/__snapshots__/FileInput.visual.test.tsx-snapshots/SchemaRenderer-FileInput-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/FileInput/__tests__/FileInput.visual.test.tsx b/src/lib/unstable/kit/form-entities/FileInput/__tests__/FileInput.visual.test.tsx new file mode 100644 index 00000000..f0fd3d40 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/FileInput/__tests__/FileInput.visual.test.tsx @@ -0,0 +1,268 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {schema, value} from './helpers'; + +test.describe('SchemaRenderer FileInput', () => { + test('layouts sizes and views', async ({mount, page, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'disabled preserves the value', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + flags: {open: true, disabled: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'readAsText uploads file content and removes the file — uploaded', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + entityProps: {readAsMethod: 'readAsText', accept: ['text/plain']}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'readAsText uploads file content and removes the file — removed', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + entityProps: {readAsMethod: 'readAsText', accept: ['text/plain']}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'readAsBinaryString uploads file content', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + entityProps: { + readAsMethod: 'readAsBinaryString', + accept: ['text/plain'], + }, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'readAsDataURL uploads file content', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + entityProps: {readAsMethod: 'readAsDataURL', accept: ['text/plain']}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + ]; + const additionalCases = stateCases; + + const grid = await mount( + { + const label = `layout=${variant.layoutName} / size=${variant.size} / view=${variant.view}`; + return { + label, + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + layout: variant.layout, + flags: {open: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: variant.size, + view: variant.view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.locator('.g-file-drop-zone')).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'disabled preserves the value', + exact: true, + }); + + const dropZone = component.locator('.g-file-drop-zone'); + await expect(dropZone).toHaveClass(/g-file-drop-zone_disabled/); + await expect(dropZone).toHaveCSS('pointer-events', 'none'); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'readAsText uploads file content and removes the file — uploaded', + exact: true, + }); + + const fileInput = component.locator('input[type="file"]'); + await expect(fileInput).toHaveAttribute('accept', 'text/plain'); + await fileInput.setInputFiles({ + name: 'config.txt', + mimeType: 'text/plain', + buffer: Buffer.from('service=gravity'), + }); + await expect(component.getByText('config.txt', {exact: true})).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify('service=gravity'), + ); + } + { + const component = grid.getByRole('region', { + name: 'readAsText uploads file content and removes the file — removed', + exact: true, + }); + + const fileInput = component.locator('input[type="file"]'); + await expect(fileInput).toHaveAttribute('accept', 'text/plain'); + await fileInput.setInputFiles({ + name: 'config.txt', + mimeType: 'text/plain', + buffer: Buffer.from('service=gravity'), + }); + await expect(component.getByText('config.txt', {exact: true})).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify('service=gravity'), + ); + const preview = component.locator('.g-file-preview'); + await preview.hover(); + await preview.getByRole('button').click(); + await expect(component.locator('.g-file-drop-zone')).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText(''); + } + { + const component = grid.getByRole('region', { + name: 'readAsBinaryString uploads file content', + exact: true, + }); + + const fileInput = component.locator('input[type="file"]'); + const content = Buffer.from('Сервис Gravity 🌍', 'utf8'); + await fileInput.setInputFiles({ + name: 'config.txt', + mimeType: 'text/plain', + buffer: content, + }); + await expect(component.getByText('config.txt', {exact: true})).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(content.toString('latin1')), + ); + } + { + const component = grid.getByRole('region', { + name: 'readAsDataURL uploads file content', + exact: true, + }); + + const fileInput = component.locator('input[type="file"]'); + await fileInput.setInputFiles({ + name: 'config.txt', + mimeType: 'text/plain', + buffer: Buffer.from('service=gravity'), + }); + await expect(component.getByText('config.txt', {exact: true})).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify('data:text/plain;base64,c2VydmljZT1ncmF2aXR5'), + ); + } + // Removing the preview leaves the pointer over the new drop zone. + await page.mouse.move(0, 0); + await expectScreenshot({scale: 'css'}); + }); +}); diff --git a/src/lib/unstable/kit/form-entities/FileInput/__tests__/helpers.ts b/src/lib/unstable/kit/form-entities/FileInput/__tests__/helpers.ts new file mode 100644 index 00000000..1fc1c264 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/FileInput/__tests__/helpers.ts @@ -0,0 +1,21 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +const settings: Partial = { + nodeParameters: {entityProps: {readAsMethod: 'readAsText', accept: ['text/plain']}}, +}; + +export const schema: JsonSchemaString = { + type: JsonSchemaType.String, + title: 'FileInput', + description: 'Description for FileInput', + ...settings, + nodeParameters: { + type: NodeType.String, + entity: 'file_input', + layout: 'row', + ...settings.nodeParameters, + flags: {open: true}, + }, +}; +export const value = undefined; diff --git a/src/lib/unstable/kit/form-entities/MultiSelectInput/__snapshots__/MultiSelectInput.visual.test.tsx-snapshots/SchemaRenderer-MultiSelectInput-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/form-entities/MultiSelectInput/__snapshots__/MultiSelectInput.visual.test.tsx-snapshots/SchemaRenderer-MultiSelectInput-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..13dc091a Binary files /dev/null and b/src/lib/unstable/kit/form-entities/MultiSelectInput/__snapshots__/MultiSelectInput.visual.test.tsx-snapshots/SchemaRenderer-MultiSelectInput-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/MultiSelectInput/__snapshots__/MultiSelectInput.visual.test.tsx-snapshots/SchemaRenderer-MultiSelectInput-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/form-entities/MultiSelectInput/__snapshots__/MultiSelectInput.visual.test.tsx-snapshots/SchemaRenderer-MultiSelectInput-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..4d78cde0 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/MultiSelectInput/__snapshots__/MultiSelectInput.visual.test.tsx-snapshots/SchemaRenderer-MultiSelectInput-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/MultiSelectInput/__snapshots__/MultiSelectInput.visual.test.tsx-snapshots/SchemaRenderer-MultiSelectInput-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/form-entities/MultiSelectInput/__snapshots__/MultiSelectInput.visual.test.tsx-snapshots/SchemaRenderer-MultiSelectInput-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..8a59a58a Binary files /dev/null and b/src/lib/unstable/kit/form-entities/MultiSelectInput/__snapshots__/MultiSelectInput.visual.test.tsx-snapshots/SchemaRenderer-MultiSelectInput-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/MultiSelectInput/__snapshots__/MultiSelectInput.visual.test.tsx-snapshots/SchemaRenderer-MultiSelectInput-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/form-entities/MultiSelectInput/__snapshots__/MultiSelectInput.visual.test.tsx-snapshots/SchemaRenderer-MultiSelectInput-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..741eab0d Binary files /dev/null and b/src/lib/unstable/kit/form-entities/MultiSelectInput/__snapshots__/MultiSelectInput.visual.test.tsx-snapshots/SchemaRenderer-MultiSelectInput-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/MultiSelectInput/__tests__/MultiSelectInput.visual.test.tsx b/src/lib/unstable/kit/form-entities/MultiSelectInput/__tests__/MultiSelectInput.visual.test.tsx new file mode 100644 index 00000000..d0f951b9 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/MultiSelectInput/__tests__/MultiSelectInput.visual.test.tsx @@ -0,0 +1,253 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {expectPopupInCell, fitGridInViewport, getSelectPopup} from '~playwright/core/popupGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {JsonSchemaType, SchemaRendererMode} from '../../../../core/constants'; + +import {schema, value} from './helpers'; + +test.describe('SchemaRenderer MultiSelectInput', () => { + test('layouts sizes and views', async ({mount, page, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'disabled preserves the value', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + flags: {open: true, disabled: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'selection updates the form and clear removes it — selected', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'selection updates the form and clear removes it — cleared', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'filtering more than nine options selects the matching value', + props: ((): SchemaFormProps => { + const options = Array.from({length: 12}, (_, index) => `choice-${index + 1}`); + return { + schema: {...schema, items: {type: JsonSchemaType.String, enum: options}}, + value: ['choice-1'], + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'open filtered options', + + props: ((): SchemaFormProps => { + const options = Array.from({length: 12}, (_, index) => `choice-${index + 1}`); + return { + schema: { + ...schema, + items: {type: JsonSchemaType.String, enum: options}, + nodeParameters: { + ...schema.nodeParameters, + entityProps: { + ...schema.nodeParameters?.entityProps, + open: true, + filter: 'choice-10', + popupPlacement: 'bottom-start', + }, + }, + }, + value: ['choice-1'], + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases; + + const grid = await mount( + { + const label = `layout=${variant.layoutName} / size=${variant.size} / view=${variant.view}`; + return { + label, + + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + layout: variant.layout, + entityProps: { + ...schema.nodeParameters?.entityProps, + open: true, + popupPlacement: 'bottom-start', + }, + flags: {open: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: variant.size, + view: variant.view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + await fitGridInViewport(page, grid); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByTestId('input')).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'disabled preserves the value', + exact: true, + }); + + await expect(component.getByTestId('input')).toBeDisabled(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'selection updates the form and clear removes it — selected', + exact: true, + }); + + await component.getByTestId('input').click(); + const popup = await getSelectPopup(page, component.getByTestId('input')); + await popup.getByRole('option', {name: /Beta/}).click(); + await page.keyboard.press('Escape'); + await expect(component.getByTestId('form-value')).toHaveText('["alpha","beta"]'); + } + { + const component = grid.getByRole('region', { + name: 'selection updates the form and clear removes it — cleared', + exact: true, + }); + + await component.getByTestId('input').click(); + const popup = await getSelectPopup(page, component.getByTestId('input')); + await popup.getByRole('option', {name: /Beta/}).click(); + await page.keyboard.press('Escape'); + await expect(component.getByTestId('form-value')).toHaveText('["alpha","beta"]'); + await component.getByRole('button', {name: 'Clear', exact: true}).click(); + await expect(component.getByTestId('form-value')).toHaveText('[]'); + } + { + const component = grid.getByRole('region', { + name: 'filtering more than nine options selects the matching value', + exact: true, + }); + await component.getByTestId('input').click(); + const popup = await getSelectPopup(page, component.getByTestId('input')); + await popup.getByRole('textbox', {name: 'Filter', exact: true}).fill('choice-10'); + await expect(popup.getByRole('option')).toHaveCount(1); + await expect(popup.getByRole('option', {name: 'choice-10', exact: true})).toBeVisible(); + await popup.getByRole('option', {name: 'choice-10', exact: true}).click(); + await page.keyboard.press('Escape'); + await expect(component.getByTestId('form-value')).toHaveText( + '["choice-1","choice-10"]', + ); + } + // Restore the viewport after interactions that may scroll to a control. + await fitGridInViewport(page, grid); + for (let index = 0; index < rendererVariants.length; index++) { + const cell = cases.nth(index); + const control = cell.getByTestId('input'); + const popup = await getSelectPopup(page, control); + await expect(popup.getByRole('option', {name: /Beta/})).toBeVisible(); + await expect(popup.getByText('The second team', {exact: true})).toBeVisible(); + await expectPopupInCell(popup, cell, control); + } + const filteredCell = grid.getByRole('region', {name: 'open filtered options', exact: true}); + const filteredControl = filteredCell.getByTestId('input'); + const filteredPopup = await getSelectPopup(page, filteredControl); + await expect(filteredPopup.getByRole('textbox', {name: 'Filter', exact: true})).toHaveValue( + 'choice-10', + ); + await expect(filteredPopup.getByRole('option')).toHaveCount(1); + await expect( + filteredPopup.getByRole('option', {name: 'choice-10', exact: true}), + ).toBeVisible(); + await expectPopupInCell(filteredPopup, filteredCell, filteredControl); + await page.mouse.move(0, 0); + await expectScreenshot({scale: 'css'}); + }); +}); diff --git a/src/lib/unstable/kit/form-entities/MultiSelectInput/__tests__/helpers.ts b/src/lib/unstable/kit/form-entities/MultiSelectInput/__tests__/helpers.ts new file mode 100644 index 00000000..d8de20ab --- /dev/null +++ b/src/lib/unstable/kit/form-entities/MultiSelectInput/__tests__/helpers.ts @@ -0,0 +1,29 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaArray} from '../../../../core/types'; + +const settings: Partial = { + items: {type: JsonSchemaType.String, enum: ['alpha', 'beta', 'gamma']}, + examples: [['Choose teams']], + nodeParameters: { + entityProps: { + enumDescriptions: {alpha: 'Alpha', beta: 'Beta', gamma: 'Gamma'}, + optionsMeta: {beta: 'The second team'}, + hasClear: true, + }, + }, +}; + +export const schema: JsonSchemaArray = { + type: JsonSchemaType.Array, + title: 'MultiSelectInput', + description: 'Description for MultiSelectInput', + ...settings, + nodeParameters: { + type: NodeType.Array, + entity: 'select', + layout: 'row', + ...settings.nodeParameters, + flags: {open: true}, + }, +}; +export const value = ['alpha']; diff --git a/src/lib/unstable/kit/form-entities/NumberInput/__snapshots__/NumberInput.visual.test.tsx-snapshots/SchemaRenderer-NumberInput-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/form-entities/NumberInput/__snapshots__/NumberInput.visual.test.tsx-snapshots/SchemaRenderer-NumberInput-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..c77729ac Binary files /dev/null and b/src/lib/unstable/kit/form-entities/NumberInput/__snapshots__/NumberInput.visual.test.tsx-snapshots/SchemaRenderer-NumberInput-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/NumberInput/__snapshots__/NumberInput.visual.test.tsx-snapshots/SchemaRenderer-NumberInput-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/form-entities/NumberInput/__snapshots__/NumberInput.visual.test.tsx-snapshots/SchemaRenderer-NumberInput-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..8ea5bcc4 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/NumberInput/__snapshots__/NumberInput.visual.test.tsx-snapshots/SchemaRenderer-NumberInput-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/NumberInput/__snapshots__/NumberInput.visual.test.tsx-snapshots/SchemaRenderer-NumberInput-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/form-entities/NumberInput/__snapshots__/NumberInput.visual.test.tsx-snapshots/SchemaRenderer-NumberInput-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..dd691280 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/NumberInput/__snapshots__/NumberInput.visual.test.tsx-snapshots/SchemaRenderer-NumberInput-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/NumberInput/__snapshots__/NumberInput.visual.test.tsx-snapshots/SchemaRenderer-NumberInput-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/form-entities/NumberInput/__snapshots__/NumberInput.visual.test.tsx-snapshots/SchemaRenderer-NumberInput-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..3c8663f1 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/NumberInput/__snapshots__/NumberInput.visual.test.tsx-snapshots/SchemaRenderer-NumberInput-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/NumberInput/__tests__/NumberInput.visual.test.tsx b/src/lib/unstable/kit/form-entities/NumberInput/__tests__/NumberInput.visual.test.tsx new file mode 100644 index 00000000..bdce67da --- /dev/null +++ b/src/lib/unstable/kit/form-entities/NumberInput/__tests__/NumberInput.visual.test.tsx @@ -0,0 +1,173 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {schema, value} from './helpers'; + +test.describe('SchemaRenderer NumberInput', () => { + test('layouts sizes and views', async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'disabled preserves the value', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + flags: {open: true, disabled: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'decimal edit respects multipleOf', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'empty placeholder', + props: { + schema: schema, + value: undefined, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'numeric minimum validation', + props: { + schema: { + ...schema, + minimum: 10, + nodeParameters: { + ...schema.nodeParameters, + errorMessages: {minimum: 'Minimum is ten'}, + }, + }, + value: 5, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: true, + width: 600, + }, + }, + ]; + const additionalCases = stateCases; + + const grid = await mount( + { + const label = `layout=${variant.layoutName} / size=${variant.size} / view=${variant.view}`; + return { + label, + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + layout: variant.layout, + flags: {open: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: variant.size, + view: variant.view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByRole('spinbutton')).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'disabled preserves the value', + exact: true, + }); + await expect(component.getByRole('spinbutton')).toBeDisabled(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'decimal edit respects multipleOf', + exact: true, + }); + await component.getByRole('spinbutton').fill('37.5'); + await component.getByRole('spinbutton').blur(); + await expect(component.getByTestId('form-value')).toHaveText('37.5'); + } + { + const component = grid.getByRole('region', {name: 'empty placeholder', exact: true}); + await expect(component.getByRole('spinbutton')).toHaveValue(''); + await expect(component.getByRole('spinbutton')).toHaveAttribute('placeholder', '10'); + } + { + const component = grid.getByRole('region', { + name: 'numeric minimum validation', + exact: true, + }); + await component.getByRole('button', {name: 'Submit', exact: true}).click(); + await expect(component.locator('[data-sr-error]')).toHaveText('Minimum is ten'); + } + await expectScreenshot({scale: 'css'}); + }); +}); diff --git a/src/lib/unstable/kit/form-entities/NumberInput/__tests__/helpers.ts b/src/lib/unstable/kit/form-entities/NumberInput/__tests__/helpers.ts new file mode 100644 index 00000000..3f0ef98d --- /dev/null +++ b/src/lib/unstable/kit/form-entities/NumberInput/__tests__/helpers.ts @@ -0,0 +1,25 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaNumber} from '../../../../core/types'; + +const settings: Partial = { + minimum: 0, + maximum: 100, + multipleOf: 0.5, + examples: [10], + nodeParameters: {entityProps: {hasClear: true}}, +}; + +export const schema: JsonSchemaNumber = { + type: JsonSchemaType.Number, + title: 'NumberInput', + description: 'Description for NumberInput', + ...settings, + nodeParameters: { + type: NodeType.Number, + entity: 'base', + layout: 'row', + ...settings.nodeParameters, + flags: {open: true}, + }, +}; +export const value = 12.5; diff --git a/src/lib/unstable/kit/form-entities/NumberWithScaleInput/__snapshots__/NumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleInput-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/form-entities/NumberWithScaleInput/__snapshots__/NumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleInput-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..5060fd25 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/NumberWithScaleInput/__snapshots__/NumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleInput-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/NumberWithScaleInput/__snapshots__/NumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleInput-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/form-entities/NumberWithScaleInput/__snapshots__/NumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleInput-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..64495c9f Binary files /dev/null and b/src/lib/unstable/kit/form-entities/NumberWithScaleInput/__snapshots__/NumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleInput-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/NumberWithScaleInput/__snapshots__/NumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleInput-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/form-entities/NumberWithScaleInput/__snapshots__/NumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleInput-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..111d6ef3 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/NumberWithScaleInput/__snapshots__/NumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleInput-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/NumberWithScaleInput/__snapshots__/NumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleInput-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/form-entities/NumberWithScaleInput/__snapshots__/NumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleInput-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..62114b51 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/NumberWithScaleInput/__snapshots__/NumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleInput-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/NumberWithScaleInput/__tests__/NumberWithScaleInput.visual.test.tsx b/src/lib/unstable/kit/form-entities/NumberWithScaleInput/__tests__/NumberWithScaleInput.visual.test.tsx new file mode 100644 index 00000000..acc8fbc8 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/NumberWithScaleInput/__tests__/NumberWithScaleInput.visual.test.tsx @@ -0,0 +1,285 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {schema, value} from './helpers'; + +test.describe('SchemaRenderer NumberWithScaleInput', () => { + test('layouts sizes and views', async ({mount, page, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'disabled preserves the value — state 1', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + flags: {open: true, disabled: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'disabled preserves the value — Disabled input after scale change', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + flags: {open: true, disabled: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'converts an edited display value to base units', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'changing scale preserves the base value', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'without scale renders just the input', + props: { + schema: { + ...schema, + nodeParameters: {...schema.nodeParameters, entityProps: {}}, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'omitting viewType uses defaultType', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + entityProps: { + ...schema.nodeParameters?.entityProps, + viewType: undefined, + }, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'negative scaled values preserve numeric payload', + props: { + schema: {...schema, minimum: -10000, multipleOf: 0.5}, + value: -1500, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + ]; + const additionalCases = stateCases; + + const grid = await mount( + { + const label = `layout=${variant.layoutName} / size=${variant.size} / view=${variant.view}`; + return { + label, + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + layout: variant.layout, + flags: {open: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: variant.size, + view: variant.view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByRole('spinbutton')).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'disabled preserves the value — state 1', + exact: true, + }); + + await expect(component.getByRole('spinbutton')).toBeDisabled(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + const scaleSelect = component.locator('button[aria-haspopup="listbox"]'); + await expect(scaleSelect).toBeEnabled(); + await expect(component.getByRole('spinbutton')).toHaveValue('1.5'); + } + { + const component = grid.getByRole('region', { + name: 'disabled preserves the value — Disabled input after scale change', + exact: true, + }); + + await expect(component.getByRole('spinbutton')).toBeDisabled(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + const scaleSelect = component.locator('button[aria-haspopup="listbox"]'); + await expect(scaleSelect).toBeEnabled(); + await expect(component.getByRole('spinbutton')).toHaveValue('1.5'); + await scaleSelect.click(); + await page.getByRole('option', {name: 'm', exact: true}).click(); + await expect(component.getByRole('spinbutton')).toBeDisabled(); + await expect(component.getByRole('spinbutton')).toHaveValue('1500'); + await expect(component.getByTestId('form-value')).toHaveText(JSON.stringify(value)); + } + { + const component = grid.getByRole('region', { + name: 'converts an edited display value to base units', + exact: true, + }); + + await expect(component.getByRole('spinbutton')).toHaveValue('1.5'); + await component.getByRole('spinbutton').fill('2'); + await component.getByRole('spinbutton').blur(); + await expect(component.getByTestId('form-value')).toHaveText('2000'); + } + { + const component = grid.getByRole('region', { + name: 'changing scale preserves the base value', + exact: true, + }); + + await component.locator('button[aria-haspopup="listbox"]').click(); + await page.getByRole('option', {name: 'm', exact: true}).click(); + await expect(component.getByRole('spinbutton')).toHaveValue('1500'); + await expect(component.getByTestId('form-value')).toHaveText(JSON.stringify(value)); + } + { + const component = grid.getByRole('region', { + name: 'without scale renders just the input', + exact: true, + }); + + await expect(component.getByRole('spinbutton')).toHaveValue('1500'); + await expect(component.locator('button[aria-haspopup="listbox"]')).toHaveCount(0); + } + { + const component = grid.getByRole('region', { + name: 'omitting viewType uses defaultType', + exact: true, + }); + + await expect(component.getByRole('spinbutton')).toHaveValue('1500'); + await expect(component.locator('button[aria-haspopup="listbox"]')).toHaveText('m'); + await expect(component.getByTestId('form-value')).toHaveText(JSON.stringify(value)); + } + { + const component = grid.getByRole('region', { + name: 'negative scaled values preserve numeric payload', + exact: true, + }); + + await expect(component.getByRole('spinbutton')).toHaveValue('-1.5'); + await component.getByRole('spinbutton').fill('-2.5'); + await component.getByRole('spinbutton').blur(); + await expect(component.getByTestId('form-value')).toHaveText('-2500'); + } + await expectScreenshot({scale: 'css'}); + }); +}); diff --git a/src/lib/unstable/kit/form-entities/NumberWithScaleInput/__tests__/helpers.ts b/src/lib/unstable/kit/form-entities/NumberWithScaleInput/__tests__/helpers.ts new file mode 100644 index 00000000..83a9d7b3 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/NumberWithScaleInput/__tests__/helpers.ts @@ -0,0 +1,30 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaNumber} from '../../../../core/types'; + +const settings: Partial = { + minimum: 0, + maximum: 10000, + multipleOf: 100, + nodeParameters: { + entityProps: { + scale: {m: {title: 'm', factor: 1}, km: {title: 'km', factor: 1000}}, + defaultType: 'm', + viewType: 'km', + }, + }, +}; + +export const schema: JsonSchemaNumber = { + type: JsonSchemaType.Number, + title: 'NumberWithScaleInput', + description: 'Description for NumberWithScaleInput', + ...settings, + nodeParameters: { + type: NodeType.Number, + entity: 'number_with_scale', + layout: 'row', + ...settings.nodeParameters, + flags: {open: true}, + }, +}; +export const value = 1500; diff --git a/src/lib/unstable/kit/form-entities/PasswordInput/__snapshots__/PasswordInput.visual.test.tsx-snapshots/SchemaRenderer-PasswordInput-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/form-entities/PasswordInput/__snapshots__/PasswordInput.visual.test.tsx-snapshots/SchemaRenderer-PasswordInput-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..7ae1443c Binary files /dev/null and b/src/lib/unstable/kit/form-entities/PasswordInput/__snapshots__/PasswordInput.visual.test.tsx-snapshots/SchemaRenderer-PasswordInput-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/PasswordInput/__snapshots__/PasswordInput.visual.test.tsx-snapshots/SchemaRenderer-PasswordInput-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/form-entities/PasswordInput/__snapshots__/PasswordInput.visual.test.tsx-snapshots/SchemaRenderer-PasswordInput-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..184dd59b Binary files /dev/null and b/src/lib/unstable/kit/form-entities/PasswordInput/__snapshots__/PasswordInput.visual.test.tsx-snapshots/SchemaRenderer-PasswordInput-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/PasswordInput/__snapshots__/PasswordInput.visual.test.tsx-snapshots/SchemaRenderer-PasswordInput-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/form-entities/PasswordInput/__snapshots__/PasswordInput.visual.test.tsx-snapshots/SchemaRenderer-PasswordInput-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..0993ef95 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/PasswordInput/__snapshots__/PasswordInput.visual.test.tsx-snapshots/SchemaRenderer-PasswordInput-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/PasswordInput/__snapshots__/PasswordInput.visual.test.tsx-snapshots/SchemaRenderer-PasswordInput-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/form-entities/PasswordInput/__snapshots__/PasswordInput.visual.test.tsx-snapshots/SchemaRenderer-PasswordInput-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..81a98d69 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/PasswordInput/__snapshots__/PasswordInput.visual.test.tsx-snapshots/SchemaRenderer-PasswordInput-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/PasswordInput/__tests__/PasswordInput.visual.test.tsx b/src/lib/unstable/kit/form-entities/PasswordInput/__tests__/PasswordInput.visual.test.tsx new file mode 100644 index 00000000..f84880c3 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/PasswordInput/__tests__/PasswordInput.visual.test.tsx @@ -0,0 +1,190 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {schema, value} from './helpers'; + +test.describe('SchemaRenderer PasswordInput', () => { + test('layouts sizes and views', async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'disabled preserves the value', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + flags: {open: true, disabled: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'reveals edits and hides the password — revealed', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'reveals edits and hides the password — masked', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'hides copy and reveal actions', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + entityProps: { + hideCopyButton: true, + hideRevealButton: true, + hasClear: false, + }, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + ]; + const additionalCases = stateCases; + + const grid = await mount( + { + const label = `layout=${variant.layoutName} / size=${variant.size} / view=${variant.view}`; + return { + label, + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + layout: variant.layout, + flags: {open: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: variant.size, + view: variant.view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.locator('input[type="password"]')).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'disabled preserves the value', + exact: true, + }); + + await expect(component.locator('input[type="password"]')).toBeDisabled(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'reveals edits and hides the password — revealed', + exact: true, + }); + + await component.locator('input[type="password"]').fill('changed-secret'); + await component.getByRole('button', {name: 'Show password', exact: true}).click(); + await expect(component.getByRole('textbox')).toHaveValue('changed-secret'); + await expect(component.getByTestId('form-value')).toHaveText('"changed-secret"'); + } + { + const component = grid.getByRole('region', { + name: 'reveals edits and hides the password — masked', + exact: true, + }); + + await component.locator('input[type="password"]').fill('changed-secret'); + await component.getByRole('button', {name: 'Show password', exact: true}).click(); + await expect(component.getByRole('textbox')).toHaveValue('changed-secret'); + await expect(component.getByTestId('form-value')).toHaveText('"changed-secret"'); + await component.getByRole('button', {name: 'Hide password', exact: true}).click(); + await expect(component.locator('input[type="password"]')).toHaveValue('changed-secret'); + } + { + const component = grid.getByRole('region', { + name: 'hides copy and reveal actions', + exact: true, + }); + + await expect( + component.getByRole('button', {name: 'Show password', exact: true}), + ).toHaveCount(0); + await expect(component.locator('input[type="password"]')).toHaveValue(value); + } + await expectScreenshot({scale: 'css'}); + }); +}); diff --git a/src/lib/unstable/kit/form-entities/PasswordInput/__tests__/helpers.ts b/src/lib/unstable/kit/form-entities/PasswordInput/__tests__/helpers.ts new file mode 100644 index 00000000..316f5ac5 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/PasswordInput/__tests__/helpers.ts @@ -0,0 +1,22 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +const settings: Partial = { + examples: ['Password'], + nodeParameters: {entityProps: {hasClear: true, hideCopyButton: false, hideRevealButton: false}}, +}; + +export const schema: JsonSchemaString = { + type: JsonSchemaType.String, + title: 'PasswordInput', + description: 'Description for PasswordInput', + ...settings, + nodeParameters: { + type: NodeType.String, + entity: 'password', + layout: 'row', + ...settings.nodeParameters, + flags: {open: true}, + }, +}; +export const value = 'test-secret'; diff --git a/src/lib/unstable/kit/form-entities/RadioGroupInput/__snapshots__/RadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-RadioGroupInput-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/form-entities/RadioGroupInput/__snapshots__/RadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-RadioGroupInput-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..f23d720b Binary files /dev/null and b/src/lib/unstable/kit/form-entities/RadioGroupInput/__snapshots__/RadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-RadioGroupInput-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/RadioGroupInput/__snapshots__/RadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-RadioGroupInput-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/form-entities/RadioGroupInput/__snapshots__/RadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-RadioGroupInput-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..57d3b4e1 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/RadioGroupInput/__snapshots__/RadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-RadioGroupInput-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/RadioGroupInput/__snapshots__/RadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-RadioGroupInput-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/form-entities/RadioGroupInput/__snapshots__/RadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-RadioGroupInput-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..d0936452 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/RadioGroupInput/__snapshots__/RadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-RadioGroupInput-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/RadioGroupInput/__snapshots__/RadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-RadioGroupInput-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/form-entities/RadioGroupInput/__snapshots__/RadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-RadioGroupInput-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..5effe1cc Binary files /dev/null and b/src/lib/unstable/kit/form-entities/RadioGroupInput/__snapshots__/RadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-RadioGroupInput-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/RadioGroupInput/__tests__/RadioGroupInput.visual.test.tsx b/src/lib/unstable/kit/form-entities/RadioGroupInput/__tests__/RadioGroupInput.visual.test.tsx new file mode 100644 index 00000000..c8a9e018 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/RadioGroupInput/__tests__/RadioGroupInput.visual.test.tsx @@ -0,0 +1,132 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {schema, value} from './helpers'; + +test.describe('SchemaRenderer RadioGroupInput', () => { + test('layouts sizes and views', async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'disabled preserves the value', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + flags: {open: true, disabled: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'vertical options and disabled option', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + entityProps: { + ...schema.nodeParameters!.entityProps, + direction: 'vertical', + }, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + ]; + const additionalCases = stateCases; + + const grid = await mount( + { + const label = `layout=${variant.layoutName} / size=${variant.size} / view=${variant.view}`; + return { + label, + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + layout: variant.layout, + flags: {open: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: variant.size, + view: variant.view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByRole('radio').first()).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'disabled preserves the value', + exact: true, + }); + await expect(component.getByRole('radio').first()).toBeDisabled(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'vertical options and disabled option', + exact: true, + }); + await expect( + component.getByRole('radio', {name: 'Public', exact: true}), + ).toBeDisabled(); + await component.getByRole('radio', {name: 'Team', exact: true}).check(); + await expect(component.getByTestId('form-value')).toHaveText('"team"'); + } + await expectScreenshot({scale: 'css'}); + }); +}); diff --git a/src/lib/unstable/kit/form-entities/RadioGroupInput/__tests__/helpers.ts b/src/lib/unstable/kit/form-entities/RadioGroupInput/__tests__/helpers.ts new file mode 100644 index 00000000..7dee1e8d --- /dev/null +++ b/src/lib/unstable/kit/form-entities/RadioGroupInput/__tests__/helpers.ts @@ -0,0 +1,28 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +const settings: Partial = { + enum: ['private', 'team', 'public'], + nodeParameters: { + entityProps: { + enumDescriptions: {private: 'Private', team: 'Team', public: 'Public'}, + optionsDisabled: {public: true}, + direction: 'horizontal', + }, + }, +}; + +export const schema: JsonSchemaString = { + type: JsonSchemaType.String, + title: 'RadioGroupInput', + description: 'Description for RadioGroupInput', + ...settings, + nodeParameters: { + type: NodeType.String, + entity: 'radio_group', + layout: 'row', + ...settings.nodeParameters, + flags: {open: true}, + }, +}; +export const value = 'private'; diff --git a/src/lib/unstable/kit/form-entities/RangeInput/__snapshots__/RangeInput.visual.test.tsx-snapshots/SchemaRenderer-RangeInput-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/form-entities/RangeInput/__snapshots__/RangeInput.visual.test.tsx-snapshots/SchemaRenderer-RangeInput-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..daeb33e3 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/RangeInput/__snapshots__/RangeInput.visual.test.tsx-snapshots/SchemaRenderer-RangeInput-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/RangeInput/__snapshots__/RangeInput.visual.test.tsx-snapshots/SchemaRenderer-RangeInput-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/form-entities/RangeInput/__snapshots__/RangeInput.visual.test.tsx-snapshots/SchemaRenderer-RangeInput-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..38425493 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/RangeInput/__snapshots__/RangeInput.visual.test.tsx-snapshots/SchemaRenderer-RangeInput-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/RangeInput/__snapshots__/RangeInput.visual.test.tsx-snapshots/SchemaRenderer-RangeInput-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/form-entities/RangeInput/__snapshots__/RangeInput.visual.test.tsx-snapshots/SchemaRenderer-RangeInput-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..e5b74371 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/RangeInput/__snapshots__/RangeInput.visual.test.tsx-snapshots/SchemaRenderer-RangeInput-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/RangeInput/__snapshots__/RangeInput.visual.test.tsx-snapshots/SchemaRenderer-RangeInput-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/form-entities/RangeInput/__snapshots__/RangeInput.visual.test.tsx-snapshots/SchemaRenderer-RangeInput-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..dba40b89 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/RangeInput/__snapshots__/RangeInput.visual.test.tsx-snapshots/SchemaRenderer-RangeInput-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/RangeInput/__tests__/RangeInput.visual.test.tsx b/src/lib/unstable/kit/form-entities/RangeInput/__tests__/RangeInput.visual.test.tsx new file mode 100644 index 00000000..1118f972 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/RangeInput/__tests__/RangeInput.visual.test.tsx @@ -0,0 +1,170 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {fromSchema, schema, toSchema, value} from './helpers'; + +test.describe('SchemaRenderer RangeInput', () => { + test('layouts sizes and views', async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'disabled preserves the value', + props: { + schema: { + ...schema, + properties: { + from: { + ...fromSchema, + nodeParameters: { + ...fromSchema.nodeParameters, + flags: {disabled: true}, + }, + }, + to: { + ...toSchema, + nodeParameters: { + ...toSchema.nodeParameters, + flags: {disabled: true}, + }, + }, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'editing lower bound updates the upper minimum', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'custom keys and separator', + props: { + schema: { + ...schema, + properties: {start: schema.properties!.from, end: schema.properties!.to}, + nodeParameters: { + ...schema.nodeParameters, + entityProps: {propertyKeys: ['start', 'end'], separator: 'through'}, + }, + }, + value: {start: 20, end: 70}, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + ]; + const additionalCases = stateCases; + + const grid = await mount( + { + const label = `layout=${variant.layoutName} / size=${variant.size} / view=${variant.view}`; + return { + label, + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + layout: variant.layout, + flags: {open: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: variant.size, + view: variant.view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByRole('spinbutton').first()).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'disabled preserves the value', + exact: true, + }); + await expect(component.getByRole('spinbutton').first()).toBeDisabled(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'editing lower bound updates the upper minimum', + exact: true, + }); + await component.getByRole('spinbutton').first().fill('30'); + await component.getByRole('spinbutton').first().blur(); + await expect(component.getByTestId('form-value')).toHaveText('{"from":30,"to":70}'); + await expect(component.getByRole('spinbutton').last()).toHaveAttribute( + 'aria-valuemin', + '30', + ); + } + { + const component = grid.getByRole('region', { + name: 'custom keys and separator', + exact: true, + }); + await expect(component.getByText('through', {exact: true})).toBeVisible(); + await component.getByRole('spinbutton').last().fill('80'); + await component.getByRole('spinbutton').last().blur(); + await expect(component.getByTestId('form-value')).toHaveText('{"start":20,"end":80}'); + } + await expectScreenshot({scale: 'css'}); + }); +}); diff --git a/src/lib/unstable/kit/form-entities/RangeInput/__tests__/helpers.ts b/src/lib/unstable/kit/form-entities/RangeInput/__tests__/helpers.ts new file mode 100644 index 00000000..aa908150 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/RangeInput/__tests__/helpers.ts @@ -0,0 +1,37 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaNumber, JsonSchemaObject} from '../../../../core/types'; + +export const fromSchema: JsonSchemaNumber = { + type: JsonSchemaType.Number, + minimum: 0, + nodeParameters: {type: NodeType.Number, entity: 'base', layout: 'cell'}, +}; + +export const toSchema: JsonSchemaNumber = { + type: JsonSchemaType.Number, + maximum: 100, + nodeParameters: {type: NodeType.Number, entity: 'base', layout: 'cell'}, +}; + +const settings: Partial = { + properties: { + from: fromSchema, + to: toSchema, + }, + nodeParameters: {entityProps: {propertyKeys: ['from', 'to'], separator: 'to'}}, +}; + +export const schema: JsonSchemaObject = { + type: JsonSchemaType.Object, + title: 'RangeInput', + description: 'Description for RangeInput', + ...settings, + nodeParameters: { + type: NodeType.Object, + entity: 'range_input', + layout: 'row', + ...settings.nodeParameters, + flags: {open: true}, + }, +}; +export const value = {from: 20, to: 70}; diff --git a/src/lib/unstable/kit/form-entities/RangeSliderInput/__snapshots__/RangeSliderInput.visual.test.tsx-snapshots/SchemaRenderer-RangeSliderInput-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/form-entities/RangeSliderInput/__snapshots__/RangeSliderInput.visual.test.tsx-snapshots/SchemaRenderer-RangeSliderInput-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..827d1256 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/RangeSliderInput/__snapshots__/RangeSliderInput.visual.test.tsx-snapshots/SchemaRenderer-RangeSliderInput-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/RangeSliderInput/__snapshots__/RangeSliderInput.visual.test.tsx-snapshots/SchemaRenderer-RangeSliderInput-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/form-entities/RangeSliderInput/__snapshots__/RangeSliderInput.visual.test.tsx-snapshots/SchemaRenderer-RangeSliderInput-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..ff432535 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/RangeSliderInput/__snapshots__/RangeSliderInput.visual.test.tsx-snapshots/SchemaRenderer-RangeSliderInput-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/RangeSliderInput/__snapshots__/RangeSliderInput.visual.test.tsx-snapshots/SchemaRenderer-RangeSliderInput-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/form-entities/RangeSliderInput/__snapshots__/RangeSliderInput.visual.test.tsx-snapshots/SchemaRenderer-RangeSliderInput-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..c1401e18 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/RangeSliderInput/__snapshots__/RangeSliderInput.visual.test.tsx-snapshots/SchemaRenderer-RangeSliderInput-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/RangeSliderInput/__snapshots__/RangeSliderInput.visual.test.tsx-snapshots/SchemaRenderer-RangeSliderInput-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/form-entities/RangeSliderInput/__snapshots__/RangeSliderInput.visual.test.tsx-snapshots/SchemaRenderer-RangeSliderInput-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..139cd8e2 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/RangeSliderInput/__snapshots__/RangeSliderInput.visual.test.tsx-snapshots/SchemaRenderer-RangeSliderInput-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/RangeSliderInput/__tests__/RangeSliderInput.visual.test.tsx b/src/lib/unstable/kit/form-entities/RangeSliderInput/__tests__/RangeSliderInput.visual.test.tsx new file mode 100644 index 00000000..686e5d01 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/RangeSliderInput/__tests__/RangeSliderInput.visual.test.tsx @@ -0,0 +1,155 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {schema, value} from './helpers'; + +test.describe('SchemaRenderer RangeSliderInput', () => { + test('layouts sizes and views', async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'disabled preserves the value', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + flags: {open: true, disabled: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'lower handle respects configured step', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'custom property keys', + props: { + schema: { + ...schema, + properties: {start: {minimum: 0}, end: {maximum: 100}}, + nodeParameters: { + ...schema.nodeParameters, + entityProps: { + ...schema.nodeParameters!.entityProps, + propertyKeys: ['start', 'end'], + }, + }, + }, + value: {start: 20, end: 70}, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + ]; + const additionalCases = stateCases; + + const grid = await mount( + { + const label = `layout=${variant.layoutName} / size=${variant.size} / view=${variant.view}`; + return { + label, + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + layout: variant.layout, + flags: {open: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: variant.size, + view: variant.view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByRole('slider').first()).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'disabled preserves the value', + exact: true, + }); + await expect(component.getByRole('slider').first()).toHaveAttribute( + 'aria-disabled', + 'true', + ); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'lower handle respects configured step', + exact: true, + }); + await component.getByRole('slider').first().press('ArrowRight'); + await expect(component.getByTestId('form-value')).toHaveText('{"from":25,"to":70}'); + } + { + const component = grid.getByRole('region', {name: 'custom property keys', exact: true}); + await expect(component.getByRole('slider')).toHaveCount(2); + await component.getByRole('slider').last().press('ArrowLeft'); + await expect(component.getByTestId('form-value')).toHaveText('{"start":20,"end":65}'); + } + await expectScreenshot({scale: 'css'}); + }); +}); diff --git a/src/lib/unstable/kit/form-entities/RangeSliderInput/__tests__/helpers.ts b/src/lib/unstable/kit/form-entities/RangeSliderInput/__tests__/helpers.ts new file mode 100644 index 00000000..1982f747 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/RangeSliderInput/__tests__/helpers.ts @@ -0,0 +1,27 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaObject} from '../../../../core/types'; + +const settings: Partial = { + properties: { + from: {type: JsonSchemaType.Number, minimum: 0}, + to: {type: JsonSchemaType.Number, maximum: 100}, + }, + nodeParameters: { + entityProps: {propertyKeys: ['from', 'to'], step: 5, marks: 3, tooltipDisplay: 'on'}, + }, +}; + +export const schema: JsonSchemaObject = { + type: JsonSchemaType.Object, + title: 'RangeSliderInput', + description: 'Description for RangeSliderInput', + ...settings, + nodeParameters: { + type: NodeType.Object, + entity: 'range_slider', + layout: 'row', + ...settings.nodeParameters, + flags: {open: true}, + }, +}; +export const value = {from: 20, to: 70}; diff --git a/src/lib/unstable/kit/form-entities/SegmentedRadioGroupInput/__snapshots__/SegmentedRadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-SegmentedRadioGroupInput-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/form-entities/SegmentedRadioGroupInput/__snapshots__/SegmentedRadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-SegmentedRadioGroupInput-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..a2899c21 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/SegmentedRadioGroupInput/__snapshots__/SegmentedRadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-SegmentedRadioGroupInput-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/SegmentedRadioGroupInput/__snapshots__/SegmentedRadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-SegmentedRadioGroupInput-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/form-entities/SegmentedRadioGroupInput/__snapshots__/SegmentedRadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-SegmentedRadioGroupInput-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..0574be56 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/SegmentedRadioGroupInput/__snapshots__/SegmentedRadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-SegmentedRadioGroupInput-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/SegmentedRadioGroupInput/__snapshots__/SegmentedRadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-SegmentedRadioGroupInput-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/form-entities/SegmentedRadioGroupInput/__snapshots__/SegmentedRadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-SegmentedRadioGroupInput-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..9a4172cd Binary files /dev/null and b/src/lib/unstable/kit/form-entities/SegmentedRadioGroupInput/__snapshots__/SegmentedRadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-SegmentedRadioGroupInput-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/SegmentedRadioGroupInput/__snapshots__/SegmentedRadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-SegmentedRadioGroupInput-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/form-entities/SegmentedRadioGroupInput/__snapshots__/SegmentedRadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-SegmentedRadioGroupInput-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..ca81b8fb Binary files /dev/null and b/src/lib/unstable/kit/form-entities/SegmentedRadioGroupInput/__snapshots__/SegmentedRadioGroupInput.visual.test.tsx-snapshots/SchemaRenderer-SegmentedRadioGroupInput-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/SegmentedRadioGroupInput/__tests__/SegmentedRadioGroupInput.visual.test.tsx b/src/lib/unstable/kit/form-entities/SegmentedRadioGroupInput/__tests__/SegmentedRadioGroupInput.visual.test.tsx new file mode 100644 index 00000000..af045d1f --- /dev/null +++ b/src/lib/unstable/kit/form-entities/SegmentedRadioGroupInput/__tests__/SegmentedRadioGroupInput.visual.test.tsx @@ -0,0 +1,164 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {schema, value} from './helpers'; + +test.describe('SchemaRenderer SegmentedRadioGroupInput', () => { + test('layouts sizes and views', async ({mount, page, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'disabled preserves the value', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + flags: {open: true, disabled: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'wide radio uses descriptions and disabled options', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'narrow control falls back to select', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + entityProps: { + enumDescriptions: { + private: 'Private workspace', + team: 'Team workspace', + public: 'Public workspace', + }, + }, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 260, + }, + }, + ]; + const additionalCases = stateCases; + + const grid = await mount( + { + const label = `layout=${variant.layoutName} / size=${variant.size} / view=${variant.view}`; + return { + label, + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + layout: variant.layout, + flags: {open: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: variant.size, + view: variant.view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByRole('radio').first()).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'disabled preserves the value', + exact: true, + }); + + await expect(component.getByRole('radio').first()).toBeDisabled(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'wide radio uses descriptions and disabled options', + exact: true, + }); + + await expect( + component.getByRole('radio', {name: 'Public', exact: true}), + ).toBeDisabled(); + await component.getByRole('radio', {name: 'Team', exact: true}).check(); + await expect(component.getByTestId('form-value')).toHaveText('"team"'); + } + { + const component = grid.getByRole('region', { + name: 'narrow control falls back to select', + exact: true, + }); + + await expect(component.getByRole('radio')).toHaveCount(0); + await component.getByTestId('input').click(); + await page.getByRole('option', {name: 'Team workspace', exact: true}).click(); + await expect(component.getByTestId('form-value')).toHaveText('"team"'); + } + await expectScreenshot({scale: 'css'}); + }); +}); diff --git a/src/lib/unstable/kit/form-entities/SegmentedRadioGroupInput/__tests__/helpers.ts b/src/lib/unstable/kit/form-entities/SegmentedRadioGroupInput/__tests__/helpers.ts new file mode 100644 index 00000000..3778e514 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/SegmentedRadioGroupInput/__tests__/helpers.ts @@ -0,0 +1,27 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +const settings: Partial = { + enum: ['private', 'team', 'public'], + nodeParameters: { + entityProps: { + enumDescriptions: {private: 'Private', team: 'Team', public: 'Public'}, + optionsDisabled: {public: true}, + }, + }, +}; + +export const schema: JsonSchemaString = { + type: JsonSchemaType.String, + title: 'SegmentedRadioGroupInput', + description: 'Description for SegmentedRadioGroupInput', + ...settings, + nodeParameters: { + type: NodeType.String, + entity: 'segmented_radio_group', + layout: 'row', + ...settings.nodeParameters, + flags: {open: true}, + }, +}; +export const value = 'private'; diff --git a/src/lib/unstable/kit/form-entities/SelectInput/__snapshots__/SelectInput.visual.test.tsx-snapshots/SchemaRenderer-SelectInput-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/form-entities/SelectInput/__snapshots__/SelectInput.visual.test.tsx-snapshots/SchemaRenderer-SelectInput-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..e54b05cb Binary files /dev/null and b/src/lib/unstable/kit/form-entities/SelectInput/__snapshots__/SelectInput.visual.test.tsx-snapshots/SchemaRenderer-SelectInput-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/SelectInput/__snapshots__/SelectInput.visual.test.tsx-snapshots/SchemaRenderer-SelectInput-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/form-entities/SelectInput/__snapshots__/SelectInput.visual.test.tsx-snapshots/SchemaRenderer-SelectInput-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..0fc99db1 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/SelectInput/__snapshots__/SelectInput.visual.test.tsx-snapshots/SchemaRenderer-SelectInput-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/SelectInput/__snapshots__/SelectInput.visual.test.tsx-snapshots/SchemaRenderer-SelectInput-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/form-entities/SelectInput/__snapshots__/SelectInput.visual.test.tsx-snapshots/SchemaRenderer-SelectInput-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..23428005 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/SelectInput/__snapshots__/SelectInput.visual.test.tsx-snapshots/SchemaRenderer-SelectInput-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/SelectInput/__snapshots__/SelectInput.visual.test.tsx-snapshots/SchemaRenderer-SelectInput-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/form-entities/SelectInput/__snapshots__/SelectInput.visual.test.tsx-snapshots/SchemaRenderer-SelectInput-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..4a1565ef Binary files /dev/null and b/src/lib/unstable/kit/form-entities/SelectInput/__snapshots__/SelectInput.visual.test.tsx-snapshots/SchemaRenderer-SelectInput-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/SelectInput/__tests__/SelectInput.visual.test.tsx b/src/lib/unstable/kit/form-entities/SelectInput/__tests__/SelectInput.visual.test.tsx new file mode 100644 index 00000000..89b9ba4e --- /dev/null +++ b/src/lib/unstable/kit/form-entities/SelectInput/__tests__/SelectInput.visual.test.tsx @@ -0,0 +1,251 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {expectPopupInCell, fitGridInViewport, getSelectPopup} from '~playwright/core/popupGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {schema, value} from './helpers'; + +test.describe('SchemaRenderer SelectInput', () => { + test('layouts sizes and views', async ({mount, page, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'disabled preserves the value', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + flags: {open: true, disabled: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'selection updates the form and clear removes it — selected', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'selection updates the form and clear removes it — cleared', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'filtering more than nine options selects the matching value', + props: ((): SchemaFormProps => { + const options = Array.from({length: 12}, (_, index) => `choice-${index + 1}`); + return { + schema: {...schema, enum: options}, + value: 'choice-1', + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'open filtered options', + + props: ((): SchemaFormProps => { + const options = Array.from({length: 12}, (_, index) => `choice-${index + 1}`); + return { + schema: { + ...schema, + enum: options, + nodeParameters: { + ...schema.nodeParameters, + entityProps: { + ...schema.nodeParameters?.entityProps, + open: true, + filter: 'choice-10', + popupPlacement: 'bottom-start', + }, + }, + }, + value: 'choice-1', + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases; + + const grid = await mount( + { + const label = `layout=${variant.layoutName} / size=${variant.size} / view=${variant.view}`; + return { + label, + + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + layout: variant.layout, + entityProps: { + ...schema.nodeParameters?.entityProps, + open: true, + popupPlacement: 'bottom-start', + }, + flags: {open: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: variant.size, + view: variant.view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + await fitGridInViewport(page, grid); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByTestId('input')).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'disabled preserves the value', + exact: true, + }); + + await expect(component.getByTestId('input')).toBeDisabled(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'selection updates the form and clear removes it — selected', + exact: true, + }); + + await component.getByTestId('input').click(); + const popup = await getSelectPopup(page, component.getByTestId('input')); + await popup.getByRole('option', {name: /Published/}).click(); + await page.keyboard.press('Escape'); + await expect(component.getByTestId('form-value')).toHaveText('"published"'); + } + { + const component = grid.getByRole('region', { + name: 'selection updates the form and clear removes it — cleared', + exact: true, + }); + + await component.getByTestId('input').click(); + const popup = await getSelectPopup(page, component.getByTestId('input')); + await popup.getByRole('option', {name: /Published/}).click(); + await page.keyboard.press('Escape'); + await expect(component.getByTestId('form-value')).toHaveText('"published"'); + await component.getByRole('button', {name: 'Clear', exact: true}).click(); + await expect(component.getByTestId('form-value')).toHaveText(''); + } + { + const component = grid.getByRole('region', { + name: 'filtering more than nine options selects the matching value', + exact: true, + }); + await component.getByTestId('input').click(); + const popup = await getSelectPopup(page, component.getByTestId('input')); + await popup.getByRole('textbox', {name: 'Filter', exact: true}).fill('choice-10'); + await expect(popup.getByRole('option')).toHaveCount(1); + await expect(popup.getByRole('option', {name: 'choice-10', exact: true})).toBeVisible(); + await popup.getByRole('option', {name: 'choice-10', exact: true}).click(); + await page.keyboard.press('Escape'); + await expect(component.getByTestId('form-value')).toHaveText('"choice-10"'); + } + // Restore the viewport after interactions that may scroll to a control. + await fitGridInViewport(page, grid); + for (let index = 0; index < rendererVariants.length; index++) { + const cell = cases.nth(index); + const control = cell.getByTestId('input'); + const popup = await getSelectPopup(page, control); + await expect(popup.getByRole('option', {name: /Published/})).toBeVisible(); + await expect(popup.getByText('Visible to every reader', {exact: true})).toBeVisible(); + await expectPopupInCell(popup, cell, control); + } + const filteredCell = grid.getByRole('region', {name: 'open filtered options', exact: true}); + const filteredControl = filteredCell.getByTestId('input'); + const filteredPopup = await getSelectPopup(page, filteredControl); + await expect(filteredPopup.getByRole('textbox', {name: 'Filter', exact: true})).toHaveValue( + 'choice-10', + ); + await expect(filteredPopup.getByRole('option')).toHaveCount(1); + await expect( + filteredPopup.getByRole('option', {name: 'choice-10', exact: true}), + ).toBeVisible(); + await expectPopupInCell(filteredPopup, filteredCell, filteredControl); + await page.mouse.move(0, 0); + await expectScreenshot({scale: 'css'}); + }); +}); diff --git a/src/lib/unstable/kit/form-entities/SelectInput/__tests__/helpers.ts b/src/lib/unstable/kit/form-entities/SelectInput/__tests__/helpers.ts new file mode 100644 index 00000000..363145e0 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/SelectInput/__tests__/helpers.ts @@ -0,0 +1,29 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +const settings: Partial = { + enum: ['draft', 'published', 'archived'], + examples: ['Choose status'], + nodeParameters: { + entityProps: { + enumDescriptions: {draft: 'Draft', published: 'Published', archived: 'Archived'}, + optionsMeta: {published: 'Visible to every reader'}, + hasClear: true, + }, + }, +}; + +export const schema: JsonSchemaString = { + type: JsonSchemaType.String, + title: 'SelectInput', + description: 'Description for SelectInput', + ...settings, + nodeParameters: { + type: NodeType.String, + entity: 'select', + layout: 'row', + ...settings.nodeParameters, + flags: {open: true}, + }, +}; +export const value = 'draft'; diff --git a/src/lib/unstable/kit/form-entities/SliderInput/__snapshots__/SliderInput.visual.test.tsx-snapshots/SchemaRenderer-SliderInput-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/form-entities/SliderInput/__snapshots__/SliderInput.visual.test.tsx-snapshots/SchemaRenderer-SliderInput-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..9d746fa9 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/SliderInput/__snapshots__/SliderInput.visual.test.tsx-snapshots/SchemaRenderer-SliderInput-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/SliderInput/__snapshots__/SliderInput.visual.test.tsx-snapshots/SchemaRenderer-SliderInput-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/form-entities/SliderInput/__snapshots__/SliderInput.visual.test.tsx-snapshots/SchemaRenderer-SliderInput-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..a6c55dbb Binary files /dev/null and b/src/lib/unstable/kit/form-entities/SliderInput/__snapshots__/SliderInput.visual.test.tsx-snapshots/SchemaRenderer-SliderInput-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/SliderInput/__snapshots__/SliderInput.visual.test.tsx-snapshots/SchemaRenderer-SliderInput-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/form-entities/SliderInput/__snapshots__/SliderInput.visual.test.tsx-snapshots/SchemaRenderer-SliderInput-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..cecf87a1 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/SliderInput/__snapshots__/SliderInput.visual.test.tsx-snapshots/SchemaRenderer-SliderInput-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/SliderInput/__snapshots__/SliderInput.visual.test.tsx-snapshots/SchemaRenderer-SliderInput-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/form-entities/SliderInput/__snapshots__/SliderInput.visual.test.tsx-snapshots/SchemaRenderer-SliderInput-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..fd1e8873 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/SliderInput/__snapshots__/SliderInput.visual.test.tsx-snapshots/SchemaRenderer-SliderInput-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/SliderInput/__tests__/SliderInput.visual.test.tsx b/src/lib/unstable/kit/form-entities/SliderInput/__tests__/SliderInput.visual.test.tsx new file mode 100644 index 00000000..4633de2d --- /dev/null +++ b/src/lib/unstable/kit/form-entities/SliderInput/__tests__/SliderInput.visual.test.tsx @@ -0,0 +1,149 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {schema, value} from './helpers'; + +test.describe('SchemaRenderer SliderInput', () => { + test('layouts sizes and views', async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'disabled preserves the value', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + flags: {open: true, disabled: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'keyboard changes value using step', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'inverted track without tooltip', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + entityProps: {inverted: true, step: 5, marks: 5, tooltipDisplay: 'off'}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + ]; + const additionalCases = stateCases; + + const grid = await mount( + { + const label = `layout=${variant.layoutName} / size=${variant.size} / view=${variant.view}`; + return { + label, + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + layout: variant.layout, + flags: {open: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: variant.size, + view: variant.view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByRole('slider')).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'disabled preserves the value', + exact: true, + }); + await expect(component.getByRole('slider')).toHaveAttribute('aria-disabled', 'true'); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'keyboard changes value using step', + exact: true, + }); + await component.getByRole('slider').press('ArrowRight'); + await expect(component.getByTestId('form-value')).toHaveText('30'); + } + { + const component = grid.getByRole('region', { + name: 'inverted track without tooltip', + exact: true, + }); + await expect(component.getByRole('slider')).toHaveAttribute('aria-valuenow', '25'); + } + await expectScreenshot({scale: 'css'}); + }); +}); diff --git a/src/lib/unstable/kit/form-entities/SliderInput/__tests__/helpers.ts b/src/lib/unstable/kit/form-entities/SliderInput/__tests__/helpers.ts new file mode 100644 index 00000000..a1cce807 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/SliderInput/__tests__/helpers.ts @@ -0,0 +1,23 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaNumber} from '../../../../core/types'; + +const settings: Partial = { + minimum: 0, + maximum: 100, + nodeParameters: {entityProps: {step: 5, marks: 3, tooltipDisplay: 'on'}}, +}; + +export const schema: JsonSchemaNumber = { + type: JsonSchemaType.Number, + title: 'SliderInput', + description: 'Description for SliderInput', + ...settings, + nodeParameters: { + type: NodeType.Number, + entity: 'slider', + layout: 'row', + ...settings.nodeParameters, + flags: {open: true}, + }, +}; +export const value = 25; diff --git a/src/lib/unstable/kit/form-entities/StringInput/__snapshots__/StringInput.visual.test.tsx-snapshots/SchemaRenderer-StringInput-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/form-entities/StringInput/__snapshots__/StringInput.visual.test.tsx-snapshots/SchemaRenderer-StringInput-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..7b262763 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/StringInput/__snapshots__/StringInput.visual.test.tsx-snapshots/SchemaRenderer-StringInput-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/StringInput/__snapshots__/StringInput.visual.test.tsx-snapshots/SchemaRenderer-StringInput-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/form-entities/StringInput/__snapshots__/StringInput.visual.test.tsx-snapshots/SchemaRenderer-StringInput-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..76b90b25 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/StringInput/__snapshots__/StringInput.visual.test.tsx-snapshots/SchemaRenderer-StringInput-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/StringInput/__snapshots__/StringInput.visual.test.tsx-snapshots/SchemaRenderer-StringInput-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/form-entities/StringInput/__snapshots__/StringInput.visual.test.tsx-snapshots/SchemaRenderer-StringInput-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..ab57ae56 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/StringInput/__snapshots__/StringInput.visual.test.tsx-snapshots/SchemaRenderer-StringInput-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/StringInput/__snapshots__/StringInput.visual.test.tsx-snapshots/SchemaRenderer-StringInput-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/form-entities/StringInput/__snapshots__/StringInput.visual.test.tsx-snapshots/SchemaRenderer-StringInput-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..2823f1be Binary files /dev/null and b/src/lib/unstable/kit/form-entities/StringInput/__snapshots__/StringInput.visual.test.tsx-snapshots/SchemaRenderer-StringInput-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/StringInput/__tests__/StringInput.visual.test.tsx b/src/lib/unstable/kit/form-entities/StringInput/__tests__/StringInput.visual.test.tsx new file mode 100644 index 00000000..2347a768 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/StringInput/__tests__/StringInput.visual.test.tsx @@ -0,0 +1,213 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {schema, value} from './helpers'; + +test.describe('SchemaRenderer StringInput', () => { + test('layouts sizes and views', async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'disabled preserves the value', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + flags: {open: true, disabled: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'edits and clears the value — edited', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'edits and clears the value — cleared', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'empty placeholder', + props: { + schema: schema, + value: undefined, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'reports minimum length after submit', + props: { + schema: { + ...schema, + minLength: 5, + nodeParameters: { + ...schema.nodeParameters, + errorMessages: {minLength: 'At least five characters'}, + }, + }, + value: 'ab', + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: true, + width: 600, + }, + }, + ]; + const additionalCases = stateCases; + + const grid = await mount( + { + const label = `layout=${variant.layoutName} / size=${variant.size} / view=${variant.view}`; + return { + label, + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + layout: variant.layout, + flags: {open: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: variant.size, + view: variant.view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByRole('textbox')).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'disabled preserves the value', + exact: true, + }); + + await expect(component.getByRole('textbox')).toBeDisabled(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'edits and clears the value — edited', + exact: true, + }); + + await component.getByRole('textbox').fill('Updated service'); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify('Updated service'), + ); + } + { + const component = grid.getByRole('region', { + name: 'edits and clears the value — cleared', + exact: true, + }); + + await component.getByRole('textbox').fill('Updated service'); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify('Updated service'), + ); + await component.getByRole('button', {name: 'Clear', exact: true}).click(); + await expect(component.getByRole('textbox')).toHaveValue(''); + await expect(component.getByTestId('form-value')).toHaveText(JSON.stringify('')); + } + { + const component = grid.getByRole('region', {name: 'empty placeholder', exact: true}); + + await expect(component.getByRole('textbox')).toHaveValue(''); + await expect(component.getByRole('textbox')).toHaveAttribute( + 'placeholder', + schema.examples![0], + ); + } + { + const component = grid.getByRole('region', { + name: 'reports minimum length after submit', + exact: true, + }); + + await component.getByRole('button', {name: 'Submit', exact: true}).click(); + await expect(component.locator('[data-sr-error]')).toHaveText( + 'At least five characters', + ); + } + await expectScreenshot({scale: 'css'}); + }); +}); diff --git a/src/lib/unstable/kit/form-entities/StringInput/__tests__/helpers.ts b/src/lib/unstable/kit/form-entities/StringInput/__tests__/helpers.ts new file mode 100644 index 00000000..42cba9eb --- /dev/null +++ b/src/lib/unstable/kit/form-entities/StringInput/__tests__/helpers.ts @@ -0,0 +1,22 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +const settings: Partial = { + examples: ['Service name'], + nodeParameters: {entityProps: {hasClear: true}}, +}; + +export const schema: JsonSchemaString = { + type: JsonSchemaType.String, + title: 'StringInput', + description: 'Description for StringInput', + ...settings, + nodeParameters: { + type: NodeType.String, + entity: 'base', + layout: 'row', + ...settings.nodeParameters, + flags: {open: true}, + }, +}; +export const value = 'Gravity service'; diff --git a/src/lib/unstable/kit/form-entities/StringNumberWithScaleInput/__snapshots__/StringNumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleInput-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/form-entities/StringNumberWithScaleInput/__snapshots__/StringNumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleInput-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..ef2867d8 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/StringNumberWithScaleInput/__snapshots__/StringNumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleInput-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/StringNumberWithScaleInput/__snapshots__/StringNumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleInput-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/form-entities/StringNumberWithScaleInput/__snapshots__/StringNumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleInput-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..f8641444 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/StringNumberWithScaleInput/__snapshots__/StringNumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleInput-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/StringNumberWithScaleInput/__snapshots__/StringNumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleInput-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/form-entities/StringNumberWithScaleInput/__snapshots__/StringNumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleInput-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..46ebabd0 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/StringNumberWithScaleInput/__snapshots__/StringNumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleInput-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/StringNumberWithScaleInput/__snapshots__/StringNumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleInput-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/form-entities/StringNumberWithScaleInput/__snapshots__/StringNumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleInput-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..1977b8e3 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/StringNumberWithScaleInput/__snapshots__/StringNumberWithScaleInput.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleInput-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/StringNumberWithScaleInput/__tests__/StringNumberWithScaleInput.visual.test.tsx b/src/lib/unstable/kit/form-entities/StringNumberWithScaleInput/__tests__/StringNumberWithScaleInput.visual.test.tsx new file mode 100644 index 00000000..89156a36 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/StringNumberWithScaleInput/__tests__/StringNumberWithScaleInput.visual.test.tsx @@ -0,0 +1,253 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {schema, value} from './helpers'; + +test.describe('SchemaRenderer StringNumberWithScaleInput', () => { + test('layouts sizes and views', async ({mount, page, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'disabled preserves the value', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + flags: {open: true, disabled: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'converts an edited display value to base units', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'changing scale preserves the base value', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'without scale renders just the input', + props: { + schema: { + ...schema, + nodeParameters: {...schema.nodeParameters, entityProps: {}}, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'omitting viewType uses defaultType', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + entityProps: { + ...schema.nodeParameters?.entityProps, + viewType: undefined, + }, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'large negative decimal strings keep precision during scaling', + props: { + schema: { + ...schema, + stringNumber: { + ...schema.stringNumber, + minimum: '-999999999999999999', + maximum: '999999999999999999', + multipleOf: '0.001', + }, + }, + value: '-123456789012345.125', + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + ]; + const additionalCases = stateCases; + + const grid = await mount( + { + const label = `layout=${variant.layoutName} / size=${variant.size} / view=${variant.view}`; + return { + label, + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + layout: variant.layout, + flags: {open: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: variant.size, + view: variant.view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByRole('textbox')).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'disabled preserves the value', + exact: true, + }); + + await expect(component.getByRole('textbox')).toBeDisabled(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'converts an edited display value to base units', + exact: true, + }); + + await expect(component.getByRole('textbox')).toHaveValue('1.5'); + await component.getByRole('textbox').fill('2.125'); + await component.getByRole('textbox').blur(); + await expect(component.getByTestId('form-value')).toHaveText('"2125"'); + } + { + const component = grid.getByRole('region', { + name: 'changing scale preserves the base value', + exact: true, + }); + + await component.locator('button[aria-haspopup="listbox"]').click(); + await page.getByRole('option', {name: 'm', exact: true}).click(); + await expect(component.getByRole('textbox')).toHaveValue('1500'); + await expect(component.getByTestId('form-value')).toHaveText(JSON.stringify(value)); + } + { + const component = grid.getByRole('region', { + name: 'without scale renders just the input', + exact: true, + }); + + await expect(component.getByRole('textbox')).toHaveValue('1500'); + await expect(component.locator('button[aria-haspopup="listbox"]')).toHaveCount(0); + } + { + const component = grid.getByRole('region', { + name: 'omitting viewType uses defaultType', + exact: true, + }); + + await expect(component.getByRole('textbox')).toHaveValue('1500'); + await expect(component.locator('button[aria-haspopup="listbox"]')).toHaveText('m'); + await expect(component.getByTestId('form-value')).toHaveText(JSON.stringify(value)); + } + { + const component = grid.getByRole('region', { + name: 'large negative decimal strings keep precision during scaling', + exact: true, + }); + + await expect(component.getByRole('textbox')).toHaveValue('-123456789012.345125'); + await component.getByRole('textbox').fill('-123456789012.345126'); + await component.getByRole('textbox').blur(); + await expect(component.getByTestId('form-value')).toHaveText('"-123456789012345.126"'); + await component.locator('button[aria-haspopup="listbox"]').click(); + await page.getByRole('option', {name: 'm', exact: true}).click(); + await expect(component.getByRole('textbox')).toHaveValue('-123456789012345.126'); + await expect(component.getByTestId('form-value')).toHaveText('"-123456789012345.126"'); + } + await expectScreenshot({scale: 'css'}); + }); +}); diff --git a/src/lib/unstable/kit/form-entities/StringNumberWithScaleInput/__tests__/helpers.ts b/src/lib/unstable/kit/form-entities/StringNumberWithScaleInput/__tests__/helpers.ts new file mode 100644 index 00000000..e9cae7d4 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/StringNumberWithScaleInput/__tests__/helpers.ts @@ -0,0 +1,33 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +const settings: Partial = { + stringNumber: { + type: JsonSchemaType.Number, + minimum: '0', + maximum: '10000', + multipleOf: '0.001', + }, + nodeParameters: { + entityProps: { + scale: {m: {title: 'm', factor: '1'}, km: {title: 'km', factor: '1000'}}, + defaultType: 'm', + viewType: 'km', + }, + }, +}; + +export const schema: JsonSchemaString = { + type: JsonSchemaType.String, + title: 'StringNumberWithScaleInput', + description: 'Description for StringNumberWithScaleInput', + ...settings, + nodeParameters: { + type: NodeType.String, + entity: 'string_number_with_scale', + layout: 'row', + ...settings.nodeParameters, + flags: {open: true}, + }, +}; +export const value = '1500'; diff --git a/src/lib/unstable/kit/form-entities/SwitchInput/__snapshots__/SwitchInput.visual.test.tsx-snapshots/SchemaRenderer-SwitchInput-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/form-entities/SwitchInput/__snapshots__/SwitchInput.visual.test.tsx-snapshots/SchemaRenderer-SwitchInput-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..ad0e2e8a Binary files /dev/null and b/src/lib/unstable/kit/form-entities/SwitchInput/__snapshots__/SwitchInput.visual.test.tsx-snapshots/SchemaRenderer-SwitchInput-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/SwitchInput/__snapshots__/SwitchInput.visual.test.tsx-snapshots/SchemaRenderer-SwitchInput-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/form-entities/SwitchInput/__snapshots__/SwitchInput.visual.test.tsx-snapshots/SchemaRenderer-SwitchInput-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..7474e4e2 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/SwitchInput/__snapshots__/SwitchInput.visual.test.tsx-snapshots/SchemaRenderer-SwitchInput-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/SwitchInput/__snapshots__/SwitchInput.visual.test.tsx-snapshots/SchemaRenderer-SwitchInput-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/form-entities/SwitchInput/__snapshots__/SwitchInput.visual.test.tsx-snapshots/SchemaRenderer-SwitchInput-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..752fff34 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/SwitchInput/__snapshots__/SwitchInput.visual.test.tsx-snapshots/SchemaRenderer-SwitchInput-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/SwitchInput/__snapshots__/SwitchInput.visual.test.tsx-snapshots/SchemaRenderer-SwitchInput-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/form-entities/SwitchInput/__snapshots__/SwitchInput.visual.test.tsx-snapshots/SchemaRenderer-SwitchInput-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..139ff5cc Binary files /dev/null and b/src/lib/unstable/kit/form-entities/SwitchInput/__snapshots__/SwitchInput.visual.test.tsx-snapshots/SchemaRenderer-SwitchInput-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/SwitchInput/__tests__/SwitchInput.visual.test.tsx b/src/lib/unstable/kit/form-entities/SwitchInput/__tests__/SwitchInput.visual.test.tsx new file mode 100644 index 00000000..4a52a0c9 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/SwitchInput/__tests__/SwitchInput.visual.test.tsx @@ -0,0 +1,144 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {schema, value} from './helpers'; + +test.describe('SchemaRenderer SwitchInput', () => { + test('layouts sizes and views', async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'disabled preserves the value', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + flags: {open: true, disabled: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'keyboard toggle updates a boolean', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'unchecked state with content', + props: { + schema: schema, + value: false, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + ]; + const additionalCases = stateCases; + + const grid = await mount( + { + const label = `layout=${variant.layoutName} / size=${variant.size} / view=${variant.view}`; + return { + label, + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + layout: variant.layout, + flags: {open: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: variant.size, + view: variant.view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByRole('switch')).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'disabled preserves the value', + exact: true, + }); + await expect(component.getByRole('switch')).toBeDisabled(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'keyboard toggle updates a boolean', + exact: true, + }); + await component.getByRole('switch').press('Space'); + await expect(component.getByTestId('form-value')).toHaveText('false'); + } + { + const component = grid.getByRole('region', { + name: 'unchecked state with content', + exact: true, + }); + await expect(component.getByRole('switch')).not.toBeChecked(); + await expect(component.getByText('Notifications enabled', {exact: true})).toBeVisible(); + } + await expectScreenshot({scale: 'css'}); + }); +}); diff --git a/src/lib/unstable/kit/form-entities/SwitchInput/__tests__/helpers.ts b/src/lib/unstable/kit/form-entities/SwitchInput/__tests__/helpers.ts new file mode 100644 index 00000000..04e901be --- /dev/null +++ b/src/lib/unstable/kit/form-entities/SwitchInput/__tests__/helpers.ts @@ -0,0 +1,21 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaBoolean} from '../../../../core/types'; + +const settings: Partial = { + nodeParameters: {entityProps: {content: 'Notifications enabled'}}, +}; + +export const schema: JsonSchemaBoolean = { + type: JsonSchemaType.Boolean, + title: 'SwitchInput', + description: 'Description for SwitchInput', + ...settings, + nodeParameters: { + type: NodeType.Boolean, + entity: 'switch', + layout: 'row', + ...settings.nodeParameters, + flags: {open: true}, + }, +}; +export const value = true; diff --git a/src/lib/unstable/kit/form-entities/TextAreaInput/__snapshots__/TextAreaInput.visual.test.tsx-snapshots/SchemaRenderer-TextAreaInput-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/form-entities/TextAreaInput/__snapshots__/TextAreaInput.visual.test.tsx-snapshots/SchemaRenderer-TextAreaInput-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..95af3820 Binary files /dev/null and b/src/lib/unstable/kit/form-entities/TextAreaInput/__snapshots__/TextAreaInput.visual.test.tsx-snapshots/SchemaRenderer-TextAreaInput-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/TextAreaInput/__snapshots__/TextAreaInput.visual.test.tsx-snapshots/SchemaRenderer-TextAreaInput-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/form-entities/TextAreaInput/__snapshots__/TextAreaInput.visual.test.tsx-snapshots/SchemaRenderer-TextAreaInput-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..bea08ffa Binary files /dev/null and b/src/lib/unstable/kit/form-entities/TextAreaInput/__snapshots__/TextAreaInput.visual.test.tsx-snapshots/SchemaRenderer-TextAreaInput-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/TextAreaInput/__snapshots__/TextAreaInput.visual.test.tsx-snapshots/SchemaRenderer-TextAreaInput-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/form-entities/TextAreaInput/__snapshots__/TextAreaInput.visual.test.tsx-snapshots/SchemaRenderer-TextAreaInput-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..f2f90e9d Binary files /dev/null and b/src/lib/unstable/kit/form-entities/TextAreaInput/__snapshots__/TextAreaInput.visual.test.tsx-snapshots/SchemaRenderer-TextAreaInput-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/TextAreaInput/__snapshots__/TextAreaInput.visual.test.tsx-snapshots/SchemaRenderer-TextAreaInput-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/form-entities/TextAreaInput/__snapshots__/TextAreaInput.visual.test.tsx-snapshots/SchemaRenderer-TextAreaInput-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..7a6444ed Binary files /dev/null and b/src/lib/unstable/kit/form-entities/TextAreaInput/__snapshots__/TextAreaInput.visual.test.tsx-snapshots/SchemaRenderer-TextAreaInput-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-entities/TextAreaInput/__tests__/TextAreaInput.visual.test.tsx b/src/lib/unstable/kit/form-entities/TextAreaInput/__tests__/TextAreaInput.visual.test.tsx new file mode 100644 index 00000000..0c77fb87 --- /dev/null +++ b/src/lib/unstable/kit/form-entities/TextAreaInput/__tests__/TextAreaInput.visual.test.tsx @@ -0,0 +1,213 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {schema, value} from './helpers'; + +test.describe('SchemaRenderer TextAreaInput', () => { + test('layouts sizes and views', async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'disabled preserves the value', + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + flags: {open: true, disabled: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'edits and clears the value — edited', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'edits and clears the value — cleared', + props: { + schema: schema, + value: value, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'empty placeholder', + props: { + schema: schema, + value: undefined, + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }, + { + label: 'reports minimum length after submit', + props: { + schema: { + ...schema, + minLength: 5, + nodeParameters: { + ...schema.nodeParameters, + errorMessages: {minLength: 'At least five characters'}, + }, + }, + value: 'ab', + mode: SchemaRendererMode.Form, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: true, + width: 600, + }, + }, + ]; + const additionalCases = stateCases; + + const grid = await mount( + { + const label = `layout=${variant.layoutName} / size=${variant.size} / view=${variant.view}`; + return { + label, + props: { + schema: { + ...schema, + nodeParameters: { + ...schema.nodeParameters, + layout: variant.layout, + flags: {open: true}, + }, + }, + value: value, + mode: SchemaRendererMode.Form, + size: variant.size, + view: variant.view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByRole('textbox')).toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'disabled preserves the value', + exact: true, + }); + + await expect(component.getByRole('textbox')).toBeDisabled(); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify(value) ?? '', + ); + } + { + const component = grid.getByRole('region', { + name: 'edits and clears the value — edited', + exact: true, + }); + + await component.getByRole('textbox').fill('Updated description\nwith another line'); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify('Updated description\nwith another line'), + ); + } + { + const component = grid.getByRole('region', { + name: 'edits and clears the value — cleared', + exact: true, + }); + + await component.getByRole('textbox').fill('Updated description\nwith another line'); + await expect(component.getByTestId('form-value')).toHaveText( + JSON.stringify('Updated description\nwith another line'), + ); + await component.getByRole('button', {name: 'Clear', exact: true}).click(); + await expect(component.getByRole('textbox')).toHaveValue(''); + await expect(component.getByTestId('form-value')).toHaveText(JSON.stringify('')); + } + { + const component = grid.getByRole('region', {name: 'empty placeholder', exact: true}); + + await expect(component.getByRole('textbox')).toHaveValue(''); + await expect(component.getByRole('textbox')).toHaveAttribute( + 'placeholder', + schema.examples![0], + ); + } + { + const component = grid.getByRole('region', { + name: 'reports minimum length after submit', + exact: true, + }); + + await component.getByRole('button', {name: 'Submit', exact: true}).click(); + await expect(component.locator('[data-sr-error]')).toHaveText( + 'At least five characters', + ); + } + await expectScreenshot({scale: 'css'}); + }); +}); diff --git a/src/lib/unstable/kit/form-entities/TextAreaInput/__tests__/helpers.ts b/src/lib/unstable/kit/form-entities/TextAreaInput/__tests__/helpers.ts new file mode 100644 index 00000000..e601374b --- /dev/null +++ b/src/lib/unstable/kit/form-entities/TextAreaInput/__tests__/helpers.ts @@ -0,0 +1,22 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +const settings: Partial = { + examples: ['Description'], + nodeParameters: {entityProps: {minRows: 2, maxRows: 4, hasClear: true}}, +}; + +export const schema: JsonSchemaString = { + type: JsonSchemaType.String, + title: 'TextAreaInput', + description: 'Description for TextAreaInput', + ...settings, + nodeParameters: { + type: NodeType.String, + entity: 'textarea', + layout: 'row', + ...settings.nodeParameters, + flags: {open: true}, + }, +}; +export const value = 'First line\nSecond line'; diff --git a/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-description-tooltip-dark-chromium-linux.png b/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-description-tooltip-dark-chromium-linux.png new file mode 100644 index 00000000..11df80c1 Binary files /dev/null and b/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-description-tooltip-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-description-tooltip-dark-webkit-linux.png b/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-description-tooltip-dark-webkit-linux.png new file mode 100644 index 00000000..5025d87e Binary files /dev/null and b/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-description-tooltip-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-description-tooltip-light-chromium-linux.png b/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-description-tooltip-light-chromium-linux.png new file mode 100644 index 00000000..ac5fd724 Binary files /dev/null and b/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-description-tooltip-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-description-tooltip-light-webkit-linux.png b/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-description-tooltip-light-webkit-linux.png new file mode 100644 index 00000000..4f6979a1 Binary files /dev/null and b/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-description-tooltip-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-form-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-form-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..73db1ff0 Binary files /dev/null and b/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-form-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-form-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-form-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..111a9f22 Binary files /dev/null and b/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-form-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-form-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-form-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..f7b6db9a Binary files /dev/null and b/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-form-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-form-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-form-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..6150839a Binary files /dev/null and b/src/lib/unstable/kit/form-layouts/FormColumn/__snapshots__/FormColumn.visual.test.tsx-snapshots/FormColumn-form-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-layouts/FormColumn/__tests__/FormColumn.visual.test.tsx b/src/lib/unstable/kit/form-layouts/FormColumn/__tests__/FormColumn.visual.test.tsx new file mode 100644 index 00000000..7c815ad9 --- /dev/null +++ b/src/lib/unstable/kit/form-layouts/FormColumn/__tests__/FormColumn.visual.test.tsx @@ -0,0 +1,156 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaForm, type SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererSizes, rendererViews} from '~playwright/core/schemaRendererVariants'; + +import {JsonSchemaType, NodeType, SchemaRendererMode} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +const modes = [SchemaRendererMode.Form]; +const baseSchema: JsonSchemaString = { + type: JsonSchemaType.String, + title: 'Service name', + description: 'Name shown to other team members', + nodeParameters: {type: NodeType.String, entity: 'base', layout: 'column', flags: {open: true}}, +}; + +test.describe('FormColumn', () => { + for (const mode of modes) { + test(`${mode} sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'description bottom', + props: ((): SchemaFormProps => { + const descriptionType = 'bottom' as const; + const schema: JsonSchemaString = { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + layoutProps: {descriptionType}, + flags: {open: true, required: true}, + }, + }; + const value = 'Gravity service'; + const mode = SchemaRendererMode.Form; + const size = 'm'; + const view = 'stretch'; + return { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + const schema = baseSchema; + const value = 'Gravity service'; + const variants = rendererSizes.flatMap((size) => + rendererViews.map((view) => ({size, view})), + ); + const grid = await mount( + { + const label = `size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(variants.length + additionalCases.length); + for (let index = 0; index < variants.length; index++) { + const component = cases.nth(index); + if (mode === SchemaRendererMode.Form) { + await expect(component.getByRole('textbox')).toHaveValue(value); + } else { + await expect(component.getByText(value, {exact: true})).toBeVisible(); + } + } + if (mode === SchemaRendererMode.Form) { + const descriptionType = 'bottom' as const; + const schema: JsonSchemaString = { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + layoutProps: {descriptionType}, + flags: {open: true, required: true}, + }, + }; + const value = 'Gravity service'; + const component = grid.getByRole('region', { + name: 'description bottom', + exact: true, + }); + await expect(component.getByRole('textbox')).toHaveValue(value); + await expect(component.getByText(schema.description!, {exact: true})).toBeVisible(); + } + await expectScreenshot({scale: 'css'}); + }); + } + + test('description tooltip', async ({mount, page, expectScreenshot}) => { + const descriptionType = 'tooltip' as const; + const schema: JsonSchemaString = { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + layoutProps: {descriptionType}, + flags: {open: true, required: true}, + }, + }; + const value = 'Gravity service'; + const mode = SchemaRendererMode.Form; + const size = 'm'; + const view = 'stretch'; + const component = await mount( + , + ); + await expect(component.getByRole('textbox')).toHaveValue(value); + await component.locator('.g-help-mark').hover(); + await expect(page.getByText(schema.description!, {exact: true})).toBeVisible(); + await expectScreenshot({fullPage: true}); + }); +}); diff --git a/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-description-tooltip-dark-chromium-linux.png b/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-description-tooltip-dark-chromium-linux.png new file mode 100644 index 00000000..e3155536 Binary files /dev/null and b/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-description-tooltip-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-description-tooltip-dark-webkit-linux.png b/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-description-tooltip-dark-webkit-linux.png new file mode 100644 index 00000000..f18fac1b Binary files /dev/null and b/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-description-tooltip-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-description-tooltip-light-chromium-linux.png b/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-description-tooltip-light-chromium-linux.png new file mode 100644 index 00000000..e597ca77 Binary files /dev/null and b/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-description-tooltip-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-description-tooltip-light-webkit-linux.png b/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-description-tooltip-light-webkit-linux.png new file mode 100644 index 00000000..998c3291 Binary files /dev/null and b/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-description-tooltip-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-form-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-form-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..ab4d5d8e Binary files /dev/null and b/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-form-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-form-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-form-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..c590bb07 Binary files /dev/null and b/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-form-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-form-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-form-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..9c89ad6f Binary files /dev/null and b/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-form-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-form-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-form-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..e0c1096b Binary files /dev/null and b/src/lib/unstable/kit/form-layouts/FormRow/__snapshots__/FormRow.visual.test.tsx-snapshots/FormRow-form-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/form-layouts/FormRow/__tests__/FormRow.visual.test.tsx b/src/lib/unstable/kit/form-layouts/FormRow/__tests__/FormRow.visual.test.tsx new file mode 100644 index 00000000..675d56c8 --- /dev/null +++ b/src/lib/unstable/kit/form-layouts/FormRow/__tests__/FormRow.visual.test.tsx @@ -0,0 +1,156 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaForm, type SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererSizes, rendererViews} from '~playwright/core/schemaRendererVariants'; + +import {JsonSchemaType, NodeType, SchemaRendererMode} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +const modes = [SchemaRendererMode.Form]; +const baseSchema: JsonSchemaString = { + type: JsonSchemaType.String, + title: 'Service name', + description: 'Name shown to other team members', + nodeParameters: {type: NodeType.String, entity: 'base', layout: 'row', flags: {open: true}}, +}; + +test.describe('FormRow', () => { + for (const mode of modes) { + test(`${mode} sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'description bottom', + props: ((): SchemaFormProps => { + const descriptionType = 'bottom' as const; + const schema: JsonSchemaString = { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + layoutProps: {descriptionType}, + flags: {open: true, required: true}, + }, + }; + const value = 'Gravity service'; + const mode = SchemaRendererMode.Form; + const size = 'm'; + const view = 'stretch'; + return { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + const schema = baseSchema; + const value = 'Gravity service'; + const variants = rendererSizes.flatMap((size) => + rendererViews.map((view) => ({size, view})), + ); + const grid = await mount( + { + const label = `size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(variants.length + additionalCases.length); + for (let index = 0; index < variants.length; index++) { + const component = cases.nth(index); + if (mode === SchemaRendererMode.Form) { + await expect(component.getByRole('textbox')).toHaveValue(value); + } else { + await expect(component.getByText(value, {exact: true})).toBeVisible(); + } + } + if (mode === SchemaRendererMode.Form) { + const descriptionType = 'bottom' as const; + const schema: JsonSchemaString = { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + layoutProps: {descriptionType}, + flags: {open: true, required: true}, + }, + }; + const value = 'Gravity service'; + const component = grid.getByRole('region', { + name: 'description bottom', + exact: true, + }); + await expect(component.getByRole('textbox')).toHaveValue(value); + await expect(component.getByText(schema.description!, {exact: true})).toBeVisible(); + } + await expectScreenshot({scale: 'css'}); + }); + } + + test('description tooltip', async ({mount, page, expectScreenshot}) => { + const descriptionType = 'tooltip' as const; + const schema: JsonSchemaString = { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + layoutProps: {descriptionType}, + flags: {open: true, required: true}, + }, + }; + const value = 'Gravity service'; + const mode = SchemaRendererMode.Form; + const size = 'm'; + const view = 'stretch'; + const component = await mount( + , + ); + await expect(component.getByRole('textbox')).toHaveValue(value); + await component.locator('.g-help-mark').hover(); + await expect(page.getByText(schema.description!, {exact: true})).toBeVisible(); + await expectScreenshot({fullPage: true}); + }); +}); diff --git a/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-form-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-form-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..526b6756 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-form-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-form-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-form-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..87a9c8dd Binary files /dev/null and b/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-form-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-form-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-form-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..facc9935 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-form-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-form-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-form-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..64435eac Binary files /dev/null and b/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-form-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-overview-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-overview-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..20c4ee60 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-overview-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-overview-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-overview-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..1c078621 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-overview-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-overview-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-overview-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..181c1f18 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-overview-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-overview-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-overview-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..78d84268 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Accordeon/__snapshots__/Accordeon.visual.test.tsx-snapshots/Accordeon-overview-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Accordeon/__tests__/Accordeon.visual.test.tsx b/src/lib/unstable/kit/layouts/Accordeon/__tests__/Accordeon.visual.test.tsx new file mode 100644 index 00000000..606504a9 --- /dev/null +++ b/src/lib/unstable/kit/layouts/Accordeon/__tests__/Accordeon.visual.test.tsx @@ -0,0 +1,275 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaForm, type SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererSizes, rendererViews} from '~playwright/core/schemaRendererVariants'; + +import {JsonSchemaType, NodeType, SchemaRendererMode} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +const modes = [SchemaRendererMode.Form, SchemaRendererMode.Overview]; +const baseSchema: JsonSchemaString = { + type: JsonSchemaType.String, + title: 'Service name', + description: 'Name shown to other team members', + nodeParameters: { + type: NodeType.String, + entity: 'base', + layout: 'accordeon', + flags: {open: true}, + }, +}; + +test.describe('Accordeon', () => { + for (const mode of modes) { + test(`${mode} sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'closed layout expands and collapses without losing the value — Collapsed', + props: ((): SchemaFormProps => { + const schema: JsonSchemaString = { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + flags: {open: false}, + }, + }; + const value = 'Preserved value'; + const mode = SchemaRendererMode.Form; + const size = 'l'; + const view = 'fit'; + return { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'closed layout expands and collapses without losing the value — Expanded', + props: ((): SchemaFormProps => { + const schema: JsonSchemaString = { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + flags: {open: false}, + }, + }; + const value = 'Preserved value'; + const mode = SchemaRendererMode.Form; + const size = 'l'; + const view = 'fit'; + return { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'default summary and indent', + props: ((): SchemaFormProps => { + const schema: JsonSchemaString = { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + layoutProps: { + withDefaultSummary: true, + withIndent: true, + titleProps: {color: 'secondary'}, + togglerProps: {view: 'outlined'}, + }, + }, + }; + const value = 'Gravity service'; + const mode = SchemaRendererMode.Form; + const size = 'xl'; + const view = 'stretch'; + return { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'custom summary applies toggler props', + props: ((): SchemaFormProps => { + const schema: JsonSchemaString = { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + flags: {open: true}, + layoutProps: { + withDefaultSummary: false, + togglerProps: {view: 'outlined', disabled: true}, + }, + }, + }; + return { + schema: schema, + value: 'Visible in disabled summary', + mode: SchemaRendererMode.Form, + size: 'xl', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const schema = baseSchema; + const value = 'Gravity service'; + const variants = rendererSizes.flatMap((size) => + rendererViews.map((view) => ({size, view})), + ); + const grid = await mount( + { + const label = `size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(variants.length + additionalCases.length); + for (let index = 0; index < variants.length; index++) { + const component = cases.nth(index); + if (mode === SchemaRendererMode.Form) { + await expect(component.getByRole('textbox')).toHaveValue(value); + } else { + await expect(component.getByText(value, {exact: true})).toBeVisible(); + } + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'closed layout expands and collapses without losing the value — Collapsed', + exact: true, + }); + + await expect(component.getByRole('textbox')).not.toBeVisible(); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'closed layout expands and collapses without losing the value — Expanded', + exact: true, + }); + const value = 'Preserved value'; + await expect(component.getByRole('textbox')).not.toBeVisible(); + await component.getByRole('button').first().click(); + await expect(component.getByRole('textbox')).toHaveValue(value); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'default summary and indent', + exact: true, + }); + const value = 'Gravity service'; + await expect(component.getByRole('textbox')).toHaveValue(value); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'custom summary applies toggler props', + exact: true, + }); + + const toggler = component.getByRole('button', {name: 'Service name', exact: true}); + await expect(toggler).toBeDisabled(); + await expect(toggler).toHaveClass(/g-button_view_outlined/); + await expect(component.getByRole('textbox')).toHaveValue( + 'Visible in disabled summary', + ); + } + await expectScreenshot({scale: 'css'}); + }); + } + + test('closed layout expands and collapses without losing the value', async ({mount}) => { + const schema: JsonSchemaString = { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + flags: {open: false}, + }, + }; + const value = 'Preserved value'; + const mode = SchemaRendererMode.Form; + const size = 'l'; + const view = 'fit'; + const component = await mount( + , + ); + await expect(component.getByRole('textbox')).not.toBeVisible(); + + await component.getByRole('button').first().click(); + await expect(component.getByRole('textbox')).toHaveValue(value); + + await component.getByRole('button').first().click(); + await expect(component.getByRole('textbox')).not.toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText(JSON.stringify(value)); + }); +}); diff --git a/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-description-tooltip-dark-chromium-linux.png b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-description-tooltip-dark-chromium-linux.png new file mode 100644 index 00000000..2b86579b Binary files /dev/null and b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-description-tooltip-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-description-tooltip-dark-webkit-linux.png b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-description-tooltip-dark-webkit-linux.png new file mode 100644 index 00000000..b30fa779 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-description-tooltip-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-description-tooltip-light-chromium-linux.png b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-description-tooltip-light-chromium-linux.png new file mode 100644 index 00000000..3871fec4 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-description-tooltip-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-description-tooltip-light-webkit-linux.png b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-description-tooltip-light-webkit-linux.png new file mode 100644 index 00000000..0a4223dc Binary files /dev/null and b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-description-tooltip-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-form-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-form-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..e0d0aa4d Binary files /dev/null and b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-form-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-form-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-form-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..da3149da Binary files /dev/null and b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-form-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-form-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-form-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..b7bde6e2 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-form-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-form-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-form-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..80a85dda Binary files /dev/null and b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-form-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-overview-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-overview-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..2d30a873 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-overview-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-overview-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-overview-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..3fe0c199 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-overview-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-overview-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-overview-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..8fbdd4fc Binary files /dev/null and b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-overview-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-overview-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-overview-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..8cfeb457 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Card/__snapshots__/Card.visual.test.tsx-snapshots/Card-overview-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Card/__tests__/Card.visual.test.tsx b/src/lib/unstable/kit/layouts/Card/__tests__/Card.visual.test.tsx new file mode 100644 index 00000000..2b87ca7d --- /dev/null +++ b/src/lib/unstable/kit/layouts/Card/__tests__/Card.visual.test.tsx @@ -0,0 +1,311 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaForm, type SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererSizes, rendererViews} from '~playwright/core/schemaRendererVariants'; + +import {JsonSchemaType, NodeType, SchemaRendererMode} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +const modes = [SchemaRendererMode.Form, SchemaRendererMode.Overview]; +const baseSchema: JsonSchemaString = { + type: JsonSchemaType.String, + title: 'Service name', + description: 'Name shown to other team members', + nodeParameters: {type: NodeType.String, entity: 'base', layout: 'card', flags: {open: true}}, +}; + +test.describe('Card', () => { + for (const mode of modes) { + test(`${mode} sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'closed layout expands and collapses without losing the value — Collapsed', + props: ((): SchemaFormProps => { + const schema: JsonSchemaString = { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + flags: {open: false}, + }, + }; + const value = 'Preserved value'; + const mode = SchemaRendererMode.Form; + const size = 'l'; + const view = 'fit'; + return { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'closed layout expands and collapses without losing the value — Expanded', + props: ((): SchemaFormProps => { + const schema: JsonSchemaString = { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + flags: {open: false}, + }, + }; + const value = 'Preserved value'; + const mode = SchemaRendererMode.Form; + const size = 'l'; + const view = 'fit'; + return { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'non-collapsible outlined card', + props: ((): SchemaFormProps => { + const schema: JsonSchemaString = { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + layoutProps: { + likeAccordeon: false, + view: 'outlined', + descriptionType: 'bottom', + }, + }, + }; + const value = 'Gravity service'; + const mode = SchemaRendererMode.Form; + const size = 'xl'; + const view = 'stretch'; + return { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'description bottom', + props: ((): SchemaFormProps => { + const descriptionType = 'bottom' as const; + const schema: JsonSchemaString = { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + layoutProps: {descriptionType}, + flags: {open: true, required: true}, + }, + }; + const value = 'Gravity service'; + const mode = SchemaRendererMode.Form; + const size = 'm'; + const view = 'stretch'; + return { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const schema = baseSchema; + const value = 'Gravity service'; + const variants = rendererSizes.flatMap((size) => + rendererViews.map((view) => ({size, view})), + ); + const grid = await mount( + { + const label = `size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(variants.length + additionalCases.length); + for (let index = 0; index < variants.length; index++) { + const component = cases.nth(index); + if (mode === SchemaRendererMode.Form) { + await expect(component.getByRole('textbox')).toHaveValue(value); + } else { + await expect(component.getByText(value, {exact: true})).toBeVisible(); + } + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'closed layout expands and collapses without losing the value — Collapsed', + exact: true, + }); + + await expect(component.getByRole('textbox')).not.toBeVisible(); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'closed layout expands and collapses without losing the value — Expanded', + exact: true, + }); + const value = 'Preserved value'; + await expect(component.getByRole('textbox')).not.toBeVisible(); + await component.locator('.sr-card__header-actions').getByRole('button').click(); + await expect(component.getByRole('textbox')).toHaveValue(value); + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', { + name: 'non-collapsible outlined card', + exact: true, + }); + const value = 'Gravity service'; + await expect(component.getByRole('textbox')).toHaveValue(value); + } + if (mode === SchemaRendererMode.Form) { + const descriptionType = 'bottom' as const; + const schema: JsonSchemaString = { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + layoutProps: {descriptionType}, + flags: {open: true, required: true}, + }, + }; + const value = 'Gravity service'; + const component = grid.getByRole('region', { + name: 'description bottom', + exact: true, + }); + await expect(component.getByRole('textbox')).toHaveValue(value); + await expect(component.getByText(schema.description!, {exact: true})).toBeVisible(); + } + await expectScreenshot({scale: 'css'}); + }); + } + + test('description tooltip', async ({mount, page, expectScreenshot}) => { + const descriptionType = 'tooltip' as const; + const schema: JsonSchemaString = { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + layoutProps: {descriptionType}, + flags: {open: true, required: true}, + }, + }; + const value = 'Gravity service'; + const mode = SchemaRendererMode.Form; + const size = 'm'; + const view = 'stretch'; + const component = await mount( + , + ); + await expect(component.getByRole('textbox')).toHaveValue(value); + await component.locator('.g-help-mark').hover(); + await expect(page.getByText(schema.description!, {exact: true})).toBeVisible(); + await expectScreenshot({fullPage: true}); + }); + + test('closed layout expands and collapses without losing the value', async ({mount}) => { + const schema: JsonSchemaString = { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + flags: {open: false}, + }, + }; + const value = 'Preserved value'; + const mode = SchemaRendererMode.Form; + const size = 'l'; + const view = 'fit'; + const component = await mount( + , + ); + await expect(component.getByRole('textbox')).not.toBeVisible(); + + await component.locator('.sr-card__header-actions').getByRole('button').click(); + await expect(component.getByRole('textbox')).toHaveValue(value); + + await component.locator('.sr-card__header-actions').getByRole('button').click(); + await expect(component.getByRole('textbox')).not.toBeVisible(); + await expect(component.getByTestId('form-value')).toHaveText(JSON.stringify(value)); + }); +}); diff --git a/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-description-tooltip-dark-chromium-linux.png b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-description-tooltip-dark-chromium-linux.png new file mode 100644 index 00000000..659eec6a Binary files /dev/null and b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-description-tooltip-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-description-tooltip-dark-webkit-linux.png b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-description-tooltip-dark-webkit-linux.png new file mode 100644 index 00000000..4174f68f Binary files /dev/null and b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-description-tooltip-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-description-tooltip-light-chromium-linux.png b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-description-tooltip-light-chromium-linux.png new file mode 100644 index 00000000..7b00db52 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-description-tooltip-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-description-tooltip-light-webkit-linux.png b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-description-tooltip-light-webkit-linux.png new file mode 100644 index 00000000..b8cc4883 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-description-tooltip-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-form-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-form-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..bcb361c0 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-form-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-form-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-form-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..958a6429 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-form-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-form-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-form-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..2fcfdffe Binary files /dev/null and b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-form-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-form-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-form-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..d43653c0 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-form-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-overview-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-overview-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..c38d6b81 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-overview-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-overview-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-overview-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..4ea1ef36 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-overview-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-overview-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-overview-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..a798ac31 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-overview-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-overview-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-overview-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..07c00bb3 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Section/__snapshots__/Section.visual.test.tsx-snapshots/Section-overview-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Section/__tests__/Section.visual.test.tsx b/src/lib/unstable/kit/layouts/Section/__tests__/Section.visual.test.tsx new file mode 100644 index 00000000..2d42ac9a --- /dev/null +++ b/src/lib/unstable/kit/layouts/Section/__tests__/Section.visual.test.tsx @@ -0,0 +1,196 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaForm, type SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererSizes, rendererViews} from '~playwright/core/schemaRendererVariants'; + +import {JsonSchemaType, NodeType, SchemaRendererMode} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +const modes = [SchemaRendererMode.Form, SchemaRendererMode.Overview]; +const baseSchema: JsonSchemaString = { + type: JsonSchemaType.String, + title: 'Service name', + description: 'Name shown to other team members', + nodeParameters: {type: NodeType.String, entity: 'base', layout: 'section', flags: {open: true}}, +}; + +test.describe('Section', () => { + for (const mode of modes) { + test(`${mode} sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'indented section', + height: 200, + props: ((): SchemaFormProps => { + const schema: JsonSchemaString = { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + layoutProps: { + withIndent: true, + descriptionType: 'bottom', + color: 'secondary', + }, + }, + }; + const value = 'Gravity service'; + const mode = SchemaRendererMode.Form; + const size = 'xl'; + const view = 'stretch'; + return { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'description bottom', + props: ((): SchemaFormProps => { + const descriptionType = 'bottom' as const; + const schema: JsonSchemaString = { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + layoutProps: {descriptionType}, + flags: {open: true, required: true}, + }, + }; + const value = 'Gravity service'; + const mode = SchemaRendererMode.Form; + const size = 'm'; + const view = 'stretch'; + return { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const schema = baseSchema; + const value = 'Gravity service'; + const variants = rendererSizes.flatMap((size) => + rendererViews.map((view) => ({size, view})), + ); + const grid = await mount( + { + const label = `size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(variants.length + additionalCases.length); + for (let index = 0; index < variants.length; index++) { + const component = cases.nth(index); + if (mode === SchemaRendererMode.Form) { + await expect(component.getByRole('textbox')).toHaveValue(value); + } else { + await expect(component.getByText(value, {exact: true})).toBeVisible(); + } + } + if (mode === SchemaRendererMode.Form) { + const component = grid.getByRole('region', {name: 'indented section', exact: true}); + const value = 'Gravity service'; + await expect(component.getByRole('textbox')).toHaveValue(value); + } + if (mode === SchemaRendererMode.Form) { + const descriptionType = 'bottom' as const; + const schema: JsonSchemaString = { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + layoutProps: {descriptionType}, + flags: {open: true, required: true}, + }, + }; + const value = 'Gravity service'; + const component = grid.getByRole('region', { + name: 'description bottom', + exact: true, + }); + await expect(component.getByRole('textbox')).toHaveValue(value); + await expect(component.getByText(schema.description!, {exact: true})).toBeVisible(); + } + await expectScreenshot({scale: 'css'}); + }); + } + + test('description tooltip', async ({mount, page, expectScreenshot}) => { + const descriptionType = 'tooltip' as const; + const schema: JsonSchemaString = { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + layoutProps: {descriptionType}, + flags: {open: true, required: true}, + }, + }; + const value = 'Gravity service'; + const mode = SchemaRendererMode.Form; + const size = 'm'; + const view = 'stretch'; + const component = await mount( + , + ); + await expect(component.getByRole('textbox')).toHaveValue(value); + await component.locator('.g-help-mark').hover(); + await expect(page.getByText(schema.description!, {exact: true})).toBeVisible(); + await expectScreenshot({fullPage: true}); + }); +}); diff --git a/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-form-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-form-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..6196c594 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-form-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-form-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-form-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..b594a84f Binary files /dev/null and b/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-form-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-form-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-form-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..83c0d181 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-form-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-form-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-form-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..15de7be0 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-form-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-overview-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-overview-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..b1f346b2 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-overview-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-overview-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-overview-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..ff35a995 Binary files /dev/null and b/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-overview-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-overview-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-overview-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..bd31b79c Binary files /dev/null and b/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-overview-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-overview-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-overview-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..73ad71ac Binary files /dev/null and b/src/lib/unstable/kit/layouts/Transparent/__snapshots__/Transparent.visual.test.tsx-snapshots/Transparent-overview-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/layouts/Transparent/__tests__/Transparent.visual.test.tsx b/src/lib/unstable/kit/layouts/Transparent/__tests__/Transparent.visual.test.tsx new file mode 100644 index 00000000..c410f3f6 --- /dev/null +++ b/src/lib/unstable/kit/layouts/Transparent/__tests__/Transparent.visual.test.tsx @@ -0,0 +1,118 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererSizes, rendererViews} from '~playwright/core/schemaRendererVariants'; + +import {JsonSchemaType, NodeType, SchemaRendererMode} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +const modes = [SchemaRendererMode.Form, SchemaRendererMode.Overview]; +const baseSchema: JsonSchemaString = { + type: JsonSchemaType.String, + title: 'Service name', + description: 'Name shown to other team members', + nodeParameters: { + type: NodeType.String, + entity: 'base', + layout: 'transparent', + flags: {open: true}, + }, +}; + +test.describe('Transparent', () => { + for (const mode of modes) { + test(`${mode} sizes and views`, async ({mount, expectScreenshot}) => { + const emptyStates = [ + ['transparent', true], + ['transparent', false], + ['cell', false], + ] as const; + const additionalCases: SchemaFormGridProps['cases'] = + mode === SchemaRendererMode.Overview + ? emptyStates.map(([layout, hideEmpty]) => ({ + label: `empty ${layout} hideEmpty=${hideEmpty}`, + props: { + schema: { + ...baseSchema, + nodeParameters: { + ...baseSchema.nodeParameters, + layout, + layoutProps: layout === 'cell' ? {} : {hideEmpty}, + }, + }, + value: undefined, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + })) + : []; + + const schema = baseSchema; + const value = 'Gravity service'; + const variants = rendererSizes.flatMap((size) => + rendererViews.map((view) => ({size, view})), + ); + const grid = await mount( + { + const label = `size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(variants.length + additionalCases.length); + for (let index = 0; index < variants.length; index++) { + const component = cases.nth(index); + if (mode === SchemaRendererMode.Form) { + await expect(component.getByRole('textbox')).toHaveValue(value); + } else { + await expect(component.getByText(value, {exact: true})).toBeVisible(); + } + } + if (mode === SchemaRendererMode.Overview) { + for (const [layout, hideEmpty] of emptyStates) { + const component = grid.getByRole('region', { + name: `empty ${layout} hideEmpty=${hideEmpty}`, + exact: true, + }); + if (hideEmpty) { + await expect(component.getByText('—', {exact: true})).not.toBeVisible(); + } else { + await expect(component.getByText('—', {exact: true})).toBeVisible(); + } + } + } + await expectScreenshot({scale: 'css'}); + }); + } +}); diff --git a/src/lib/unstable/kit/overview-entities/BooleanValue/__snapshots__/BooleanValue.visual.test.tsx-snapshots/SchemaRenderer-BooleanValue-overview-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/overview-entities/BooleanValue/__snapshots__/BooleanValue.visual.test.tsx-snapshots/SchemaRenderer-BooleanValue-overview-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..46fd8ef1 Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/BooleanValue/__snapshots__/BooleanValue.visual.test.tsx-snapshots/SchemaRenderer-BooleanValue-overview-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/BooleanValue/__snapshots__/BooleanValue.visual.test.tsx-snapshots/SchemaRenderer-BooleanValue-overview-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/overview-entities/BooleanValue/__snapshots__/BooleanValue.visual.test.tsx-snapshots/SchemaRenderer-BooleanValue-overview-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..fba94c42 Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/BooleanValue/__snapshots__/BooleanValue.visual.test.tsx-snapshots/SchemaRenderer-BooleanValue-overview-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/BooleanValue/__snapshots__/BooleanValue.visual.test.tsx-snapshots/SchemaRenderer-BooleanValue-overview-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/overview-entities/BooleanValue/__snapshots__/BooleanValue.visual.test.tsx-snapshots/SchemaRenderer-BooleanValue-overview-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..d5800a86 Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/BooleanValue/__snapshots__/BooleanValue.visual.test.tsx-snapshots/SchemaRenderer-BooleanValue-overview-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/BooleanValue/__snapshots__/BooleanValue.visual.test.tsx-snapshots/SchemaRenderer-BooleanValue-overview-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/overview-entities/BooleanValue/__snapshots__/BooleanValue.visual.test.tsx-snapshots/SchemaRenderer-BooleanValue-overview-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..97be2941 Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/BooleanValue/__snapshots__/BooleanValue.visual.test.tsx-snapshots/SchemaRenderer-BooleanValue-overview-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/BooleanValue/__tests__/BooleanValue.visual.test.tsx b/src/lib/unstable/kit/overview-entities/BooleanValue/__tests__/BooleanValue.visual.test.tsx new file mode 100644 index 00000000..c202358a --- /dev/null +++ b/src/lib/unstable/kit/overview-entities/BooleanValue/__tests__/BooleanValue.visual.test.tsx @@ -0,0 +1,140 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {createSchema, value} from './helpers'; + +test.describe('SchemaRenderer BooleanValue', () => { + for (const mode of [SchemaRendererMode.Overview]) { + test(`${mode} layouts sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'empty overview uses the empty value marker', + props: ((): SchemaFormProps => { + const schema = createSchema('cell'); + return { + schema: schema, + value: undefined, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'false uses its description and color', + props: ((): SchemaFormProps => { + const schema = createSchema('row'); + return { + schema: schema, + value: false, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'raw boolean text without descriptions', + props: ((): SchemaFormProps => { + const base = createSchema('row'); + const schema = { + ...base, + nodeParameters: {...base.nodeParameters, entityProps: {}}, + }; + return { + schema: schema, + value: false, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const grid = await mount( + { + const schema = createSchema(layout); + const label = `layout=${layoutName} / size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByTestId('input')).toHaveText('Enabled'); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'empty overview uses the empty value marker', + exact: true, + }); + await expect(component.getByText('—', {exact: true})).toBeVisible(); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'false uses its description and color', + exact: true, + }); + await expect(component.getByTestId('input')).toHaveText('Disabled'); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'raw boolean text without descriptions', + exact: true, + }); + await expect(component.getByTestId('input')).toHaveText('false'); + } + await expectScreenshot({scale: 'css'}); + }); + } +}); diff --git a/src/lib/unstable/kit/overview-entities/BooleanValue/__tests__/helpers.ts b/src/lib/unstable/kit/overview-entities/BooleanValue/__tests__/helpers.ts new file mode 100644 index 00000000..2680f608 --- /dev/null +++ b/src/lib/unstable/kit/overview-entities/BooleanValue/__tests__/helpers.ts @@ -0,0 +1,22 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaBoolean} from '../../../../core/types'; + +export const createSchema = (layout: string | undefined): JsonSchemaBoolean => ({ + type: JsonSchemaType.Boolean, + title: 'BooleanValue', + description: 'Description for BooleanValue', + + nodeParameters: { + type: NodeType.Boolean, + entity: 'base', + layout, + entityProps: { + valueDescriptions: {true: 'Enabled', false: 'Disabled'}, + viewColor: {true: 'positive', false: 'danger'}, + }, + flags: {open: true}, + layoutProps: {descriptionType: 'bottom'}, + }, +}); + +export const value = true; diff --git a/src/lib/unstable/kit/overview-entities/ColorPickerValue/__snapshots__/ColorPickerValue.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerValue-overview-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/overview-entities/ColorPickerValue/__snapshots__/ColorPickerValue.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerValue-overview-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..ce9ebfc1 Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/ColorPickerValue/__snapshots__/ColorPickerValue.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerValue-overview-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/ColorPickerValue/__snapshots__/ColorPickerValue.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerValue-overview-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/overview-entities/ColorPickerValue/__snapshots__/ColorPickerValue.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerValue-overview-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..556b17ae Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/ColorPickerValue/__snapshots__/ColorPickerValue.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerValue-overview-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/ColorPickerValue/__snapshots__/ColorPickerValue.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerValue-overview-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/overview-entities/ColorPickerValue/__snapshots__/ColorPickerValue.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerValue-overview-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..090b5b3f Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/ColorPickerValue/__snapshots__/ColorPickerValue.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerValue-overview-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/ColorPickerValue/__snapshots__/ColorPickerValue.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerValue-overview-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/overview-entities/ColorPickerValue/__snapshots__/ColorPickerValue.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerValue-overview-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..64a607bb Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/ColorPickerValue/__snapshots__/ColorPickerValue.visual.test.tsx-snapshots/SchemaRenderer-ColorPickerValue-overview-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/ColorPickerValue/__tests__/ColorPickerValue.visual.test.tsx b/src/lib/unstable/kit/overview-entities/ColorPickerValue/__tests__/ColorPickerValue.visual.test.tsx new file mode 100644 index 00000000..20063f3d --- /dev/null +++ b/src/lib/unstable/kit/overview-entities/ColorPickerValue/__tests__/ColorPickerValue.visual.test.tsx @@ -0,0 +1,142 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {createSchema, value} from './helpers'; + +test.describe('SchemaRenderer ColorPickerValue', () => { + for (const mode of [SchemaRendererMode.Overview]) { + test(`${mode} layouts sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'empty overview uses the empty value marker', + props: ((): SchemaFormProps => { + const schema = createSchema('cell'); + return { + schema: schema, + value: undefined, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'invalid color retains text without a swatch', + props: ((): SchemaFormProps => { + const schema = createSchema('row'); + return { + schema: schema, + value: 'not-a-color', + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'alpha color preview', + props: ((): SchemaFormProps => { + const schema = createSchema('row'); + return { + schema: schema, + value: 'rgba(255, 87, 51, 0.5)', + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const grid = await mount( + { + const schema = createSchema(layout); + const label = `layout=${layoutName} / size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByTestId('input')).toHaveText('#ff5733'); + await expect(component.locator('.sr-color-picker-value__preview')).toBeVisible(); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'empty overview uses the empty value marker', + exact: true, + }); + await expect(component.getByText('—', {exact: true})).toBeVisible(); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'invalid color retains text without a swatch', + exact: true, + }); + await expect(component.getByTestId('input')).toHaveText('not-a-color'); + await expect(component.locator('.sr-color-picker-value__preview')).toHaveCount(0); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'alpha color preview', + exact: true, + }); + await expect(component.getByTestId('input')).toHaveText('rgba(255, 87, 51, 0.5)'); + await expect(component.locator('.sr-color-picker-value__preview')).toHaveCSS( + 'background-color', + 'rgba(255, 87, 51, 0.5)', + ); + } + await expectScreenshot({scale: 'css'}); + }); + } +}); diff --git a/src/lib/unstable/kit/overview-entities/ColorPickerValue/__tests__/helpers.ts b/src/lib/unstable/kit/overview-entities/ColorPickerValue/__tests__/helpers.ts new file mode 100644 index 00000000..176d852a --- /dev/null +++ b/src/lib/unstable/kit/overview-entities/ColorPickerValue/__tests__/helpers.ts @@ -0,0 +1,19 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +export const createSchema = (layout: string | undefined): JsonSchemaString => ({ + type: JsonSchemaType.String, + title: 'ColorPickerValue', + description: 'Description for ColorPickerValue', + + nodeParameters: { + type: NodeType.String, + entity: 'color_picker', + layout, + entityProps: {}, + flags: {open: true}, + layoutProps: {descriptionType: 'bottom'}, + }, +}); + +export const value = '#ff5733'; diff --git a/src/lib/unstable/kit/overview-entities/DateValue/__snapshots__/DateValue.visual.test.tsx-snapshots/SchemaRenderer-DateValue-overview-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/overview-entities/DateValue/__snapshots__/DateValue.visual.test.tsx-snapshots/SchemaRenderer-DateValue-overview-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..d7681519 Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/DateValue/__snapshots__/DateValue.visual.test.tsx-snapshots/SchemaRenderer-DateValue-overview-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/DateValue/__snapshots__/DateValue.visual.test.tsx-snapshots/SchemaRenderer-DateValue-overview-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/overview-entities/DateValue/__snapshots__/DateValue.visual.test.tsx-snapshots/SchemaRenderer-DateValue-overview-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..b7ac7d60 Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/DateValue/__snapshots__/DateValue.visual.test.tsx-snapshots/SchemaRenderer-DateValue-overview-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/DateValue/__snapshots__/DateValue.visual.test.tsx-snapshots/SchemaRenderer-DateValue-overview-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/overview-entities/DateValue/__snapshots__/DateValue.visual.test.tsx-snapshots/SchemaRenderer-DateValue-overview-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..118cfdb9 Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/DateValue/__snapshots__/DateValue.visual.test.tsx-snapshots/SchemaRenderer-DateValue-overview-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/DateValue/__snapshots__/DateValue.visual.test.tsx-snapshots/SchemaRenderer-DateValue-overview-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/overview-entities/DateValue/__snapshots__/DateValue.visual.test.tsx-snapshots/SchemaRenderer-DateValue-overview-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..c4f16be5 Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/DateValue/__snapshots__/DateValue.visual.test.tsx-snapshots/SchemaRenderer-DateValue-overview-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/DateValue/__tests__/DateValue.visual.test.tsx b/src/lib/unstable/kit/overview-entities/DateValue/__tests__/DateValue.visual.test.tsx new file mode 100644 index 00000000..3ad15438 --- /dev/null +++ b/src/lib/unstable/kit/overview-entities/DateValue/__tests__/DateValue.visual.test.tsx @@ -0,0 +1,209 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {createSchema, value} from './helpers'; + +test.describe('SchemaRenderer DateValue', () => { + for (const mode of [SchemaRendererMode.Overview]) { + test(`${mode} layouts sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'empty overview uses the empty value marker', + props: ((): SchemaFormProps => { + const schema = createSchema('cell'); + return { + schema: schema, + value: undefined, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'explicit timezone converts the displayed time', + props: ((): SchemaFormProps => { + const base = createSchema('row'); + const schema = { + ...base, + nodeParameters: { + ...base.nodeParameters, + entityProps: { + format: 'YYYY-MM-DD HH:mm', + timeZone: 'America/New_York', + }, + }, + }; + return { + schema: schema, + value: value, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'custom input and output formats', + props: ((): SchemaFormProps => { + const base = createSchema('row'); + const schema = { + ...base, + nodeParameters: { + ...base.nodeParameters, + entityProps: { + format: 'DD MMM YYYY', + outputFormat: 'YYYY/MM/DD', + timeZone: 'UTC', + }, + }, + }; + return { + schema: schema, + value: '2020/01/02', + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'seconds object is formatted as a date', + props: ((): SchemaFormProps => { + const schema = createSchema('row'); + return { + schema: schema, + value: {seconds: 1577934240}, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'invalid value uses the empty value marker', + props: ((): SchemaFormProps => { + const schema = createSchema('cell'); + return { + schema: schema, + value: 'not-a-valid-value', + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const grid = await mount( + { + const schema = createSchema(layout); + const label = `layout=${layoutName} / size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByTestId('input')).toHaveText('2020-01-02 03:04'); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'empty overview uses the empty value marker', + exact: true, + }); + await expect(component.getByText('—', {exact: true})).toBeVisible(); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'explicit timezone converts the displayed time', + exact: true, + }); + await expect(component.getByTestId('input')).toHaveText('2020-01-01 22:04'); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'custom input and output formats', + exact: true, + }); + await expect(component.getByTestId('input')).toHaveText('02 Jan 2020'); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'seconds object is formatted as a date', + exact: true, + }); + await expect(component.getByTestId('input')).toHaveText('2020-01-02 03:04'); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'invalid value uses the empty value marker', + exact: true, + }); + await expect(component.getByText('—', {exact: true})).toBeVisible(); + } + await expectScreenshot({scale: 'css'}); + }); + } +}); diff --git a/src/lib/unstable/kit/overview-entities/DateValue/__tests__/helpers.ts b/src/lib/unstable/kit/overview-entities/DateValue/__tests__/helpers.ts new file mode 100644 index 00000000..76bddf55 --- /dev/null +++ b/src/lib/unstable/kit/overview-entities/DateValue/__tests__/helpers.ts @@ -0,0 +1,18 @@ +import {NodeType} from '../../../../core/constants'; +import type {JsonSchemaAny} from '../../../../core/types'; + +export const createSchema = (layout: string | undefined): JsonSchemaAny => ({ + title: 'DateValue', + description: 'Description for DateValue', + + nodeParameters: { + type: NodeType.Any, + entity: 'date', + layout, + entityProps: {format: 'YYYY-MM-DD HH:mm', timeZone: 'UTC'}, + flags: {open: true}, + layoutProps: {descriptionType: 'bottom'}, + }, +}); + +export const value = '2020-01-02T03:04:00Z'; diff --git a/src/lib/unstable/kit/overview-entities/NumberValue/__snapshots__/NumberValue.visual.test.tsx-snapshots/SchemaRenderer-NumberValue-overview-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/overview-entities/NumberValue/__snapshots__/NumberValue.visual.test.tsx-snapshots/SchemaRenderer-NumberValue-overview-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..590e0444 Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/NumberValue/__snapshots__/NumberValue.visual.test.tsx-snapshots/SchemaRenderer-NumberValue-overview-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/NumberValue/__snapshots__/NumberValue.visual.test.tsx-snapshots/SchemaRenderer-NumberValue-overview-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/overview-entities/NumberValue/__snapshots__/NumberValue.visual.test.tsx-snapshots/SchemaRenderer-NumberValue-overview-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..e0070021 Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/NumberValue/__snapshots__/NumberValue.visual.test.tsx-snapshots/SchemaRenderer-NumberValue-overview-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/NumberValue/__snapshots__/NumberValue.visual.test.tsx-snapshots/SchemaRenderer-NumberValue-overview-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/overview-entities/NumberValue/__snapshots__/NumberValue.visual.test.tsx-snapshots/SchemaRenderer-NumberValue-overview-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..7d36eecf Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/NumberValue/__snapshots__/NumberValue.visual.test.tsx-snapshots/SchemaRenderer-NumberValue-overview-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/NumberValue/__snapshots__/NumberValue.visual.test.tsx-snapshots/SchemaRenderer-NumberValue-overview-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/overview-entities/NumberValue/__snapshots__/NumberValue.visual.test.tsx-snapshots/SchemaRenderer-NumberValue-overview-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..37c358cc Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/NumberValue/__snapshots__/NumberValue.visual.test.tsx-snapshots/SchemaRenderer-NumberValue-overview-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/NumberValue/__tests__/NumberValue.visual.test.tsx b/src/lib/unstable/kit/overview-entities/NumberValue/__tests__/NumberValue.visual.test.tsx new file mode 100644 index 00000000..34921988 --- /dev/null +++ b/src/lib/unstable/kit/overview-entities/NumberValue/__tests__/NumberValue.visual.test.tsx @@ -0,0 +1,143 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {createSchema, value} from './helpers'; + +test.describe('SchemaRenderer NumberValue', () => { + for (const mode of [SchemaRendererMode.Overview]) { + test(`${mode} layouts sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'empty overview uses the empty value marker', + props: ((): SchemaFormProps => { + const schema = createSchema('cell'); + return { + schema: schema, + value: undefined, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'zero remains visible', + props: ((): SchemaFormProps => { + const schema = createSchema('row'); + return { + schema: schema, + value: 0, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'negative fractional value with custom color', + props: ((): SchemaFormProps => { + const base = createSchema('row'); + const schema = { + ...base, + nodeParameters: { + ...base.nodeParameters, + entityProps: {color: 'danger'}, + }, + }; + return { + schema: schema, + value: -12.75, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const grid = await mount( + { + const schema = createSchema(layout); + const label = `layout=${layoutName} / size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByTestId('input')).toHaveText('42.5'); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'empty overview uses the empty value marker', + exact: true, + }); + await expect(component.getByText('—', {exact: true})).toBeVisible(); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'zero remains visible', + exact: true, + }); + await expect(component.getByTestId('input')).toHaveText('0'); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'negative fractional value with custom color', + exact: true, + }); + await expect(component.getByTestId('input')).toHaveText('-12.75'); + } + await expectScreenshot({scale: 'css'}); + }); + } +}); diff --git a/src/lib/unstable/kit/overview-entities/NumberValue/__tests__/helpers.ts b/src/lib/unstable/kit/overview-entities/NumberValue/__tests__/helpers.ts new file mode 100644 index 00000000..eb6c6ccb --- /dev/null +++ b/src/lib/unstable/kit/overview-entities/NumberValue/__tests__/helpers.ts @@ -0,0 +1,19 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaNumber} from '../../../../core/types'; + +export const createSchema = (layout: string | undefined): JsonSchemaNumber => ({ + type: JsonSchemaType.Number, + title: 'NumberValue', + description: 'Description for NumberValue', + + nodeParameters: { + type: NodeType.Number, + entity: 'base', + layout, + entityProps: {}, + flags: {open: true}, + layoutProps: {descriptionType: 'bottom'}, + }, +}); + +export const value = 42.5; diff --git a/src/lib/unstable/kit/overview-entities/NumberWithScaleValue/__snapshots__/NumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleValue-overview-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/overview-entities/NumberWithScaleValue/__snapshots__/NumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleValue-overview-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..7b8ea6c4 Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/NumberWithScaleValue/__snapshots__/NumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleValue-overview-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/NumberWithScaleValue/__snapshots__/NumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleValue-overview-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/overview-entities/NumberWithScaleValue/__snapshots__/NumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleValue-overview-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..b407cbab Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/NumberWithScaleValue/__snapshots__/NumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleValue-overview-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/NumberWithScaleValue/__snapshots__/NumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleValue-overview-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/overview-entities/NumberWithScaleValue/__snapshots__/NumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleValue-overview-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..2989eb18 Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/NumberWithScaleValue/__snapshots__/NumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleValue-overview-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/NumberWithScaleValue/__snapshots__/NumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleValue-overview-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/overview-entities/NumberWithScaleValue/__snapshots__/NumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleValue-overview-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..5ba38ece Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/NumberWithScaleValue/__snapshots__/NumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-NumberWithScaleValue-overview-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/NumberWithScaleValue/__tests__/NumberWithScaleValue.visual.test.tsx b/src/lib/unstable/kit/overview-entities/NumberWithScaleValue/__tests__/NumberWithScaleValue.visual.test.tsx new file mode 100644 index 00000000..0452394b --- /dev/null +++ b/src/lib/unstable/kit/overview-entities/NumberWithScaleValue/__tests__/NumberWithScaleValue.visual.test.tsx @@ -0,0 +1,198 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {createSchema, value} from './helpers'; + +test.describe('SchemaRenderer NumberWithScaleValue', () => { + for (const mode of [SchemaRendererMode.Overview]) { + test(`${mode} layouts sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'empty overview uses the empty value marker', + props: ((): SchemaFormProps => { + const schema = createSchema('cell'); + return { + schema: schema, + value: undefined, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'zero remains visible without a scale suffix', + props: ((): SchemaFormProps => { + const schema = createSchema('row'); + return { + schema: schema, + value: 0, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'negative value selects its scale using absolute magnitude', + props: ((): SchemaFormProps => { + const schema = createSchema('row'); + return { + schema: schema, + value: -1500, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'explicit view type handles a value smaller than its factor', + props: ((): SchemaFormProps => { + const base = createSchema('row'); + const schema = { + ...base, + nodeParameters: { + ...base.nodeParameters, + entityProps: {...base.nodeParameters?.entityProps, viewType: 'km'}, + }, + }; + return { + schema: schema, + value: 50, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'invalid value uses the empty value marker', + props: ((): SchemaFormProps => { + const schema = createSchema('cell'); + return { + schema: schema, + value: 'not-a-valid-value', + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const grid = await mount( + { + const schema = createSchema(layout); + const label = `layout=${layoutName} / size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByTestId('input')).toHaveText('1.5'); + await expect(component.getByText('km', {exact: true})).toBeVisible(); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'empty overview uses the empty value marker', + exact: true, + }); + await expect(component.getByText('—', {exact: true})).toBeVisible(); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'zero remains visible without a scale suffix', + exact: true, + }); + await expect(component.getByTestId('input')).toHaveText('0'); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'negative value selects its scale using absolute magnitude', + exact: true, + }); + await expect(component.getByTestId('input')).toHaveText('-1.5'); + await expect(component.getByText('km', {exact: true})).toBeVisible(); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'explicit view type handles a value smaller than its factor', + exact: true, + }); + await expect(component.getByTestId('input')).toHaveText('0.05'); + await expect(component.getByText('km', {exact: true})).toBeVisible(); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'invalid value uses the empty value marker', + exact: true, + }); + await expect(component.getByText('—', {exact: true})).toBeVisible(); + } + await expectScreenshot({scale: 'css'}); + }); + } +}); diff --git a/src/lib/unstable/kit/overview-entities/NumberWithScaleValue/__tests__/helpers.ts b/src/lib/unstable/kit/overview-entities/NumberWithScaleValue/__tests__/helpers.ts new file mode 100644 index 00000000..72325482 --- /dev/null +++ b/src/lib/unstable/kit/overview-entities/NumberWithScaleValue/__tests__/helpers.ts @@ -0,0 +1,19 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaNumber} from '../../../../core/types'; + +export const createSchema = (layout: string | undefined): JsonSchemaNumber => ({ + type: JsonSchemaType.Number, + title: 'NumberWithScaleValue', + description: 'Description for NumberWithScaleValue', + + nodeParameters: { + type: NodeType.Number, + entity: 'number_with_scale', + layout, + entityProps: {scale: {m: {title: 'm', factor: 1}, km: {title: 'km', factor: 1000}}}, + flags: {open: true}, + layoutProps: {descriptionType: 'bottom'}, + }, +}); + +export const value = 1500; diff --git a/src/lib/unstable/kit/overview-entities/PrimitiveArrayValue/__snapshots__/PrimitiveArrayValue.visual.test.tsx-snapshots/SchemaRenderer-PrimitiveArrayValue-overview-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/overview-entities/PrimitiveArrayValue/__snapshots__/PrimitiveArrayValue.visual.test.tsx-snapshots/SchemaRenderer-PrimitiveArrayValue-overview-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..cb9c062a Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/PrimitiveArrayValue/__snapshots__/PrimitiveArrayValue.visual.test.tsx-snapshots/SchemaRenderer-PrimitiveArrayValue-overview-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/PrimitiveArrayValue/__snapshots__/PrimitiveArrayValue.visual.test.tsx-snapshots/SchemaRenderer-PrimitiveArrayValue-overview-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/overview-entities/PrimitiveArrayValue/__snapshots__/PrimitiveArrayValue.visual.test.tsx-snapshots/SchemaRenderer-PrimitiveArrayValue-overview-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..dbb69bd6 Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/PrimitiveArrayValue/__snapshots__/PrimitiveArrayValue.visual.test.tsx-snapshots/SchemaRenderer-PrimitiveArrayValue-overview-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/PrimitiveArrayValue/__snapshots__/PrimitiveArrayValue.visual.test.tsx-snapshots/SchemaRenderer-PrimitiveArrayValue-overview-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/overview-entities/PrimitiveArrayValue/__snapshots__/PrimitiveArrayValue.visual.test.tsx-snapshots/SchemaRenderer-PrimitiveArrayValue-overview-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..45053b5d Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/PrimitiveArrayValue/__snapshots__/PrimitiveArrayValue.visual.test.tsx-snapshots/SchemaRenderer-PrimitiveArrayValue-overview-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/PrimitiveArrayValue/__snapshots__/PrimitiveArrayValue.visual.test.tsx-snapshots/SchemaRenderer-PrimitiveArrayValue-overview-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/overview-entities/PrimitiveArrayValue/__snapshots__/PrimitiveArrayValue.visual.test.tsx-snapshots/SchemaRenderer-PrimitiveArrayValue-overview-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..6d1e4690 Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/PrimitiveArrayValue/__snapshots__/PrimitiveArrayValue.visual.test.tsx-snapshots/SchemaRenderer-PrimitiveArrayValue-overview-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/PrimitiveArrayValue/__tests__/PrimitiveArrayValue.visual.test.tsx b/src/lib/unstable/kit/overview-entities/PrimitiveArrayValue/__tests__/PrimitiveArrayValue.visual.test.tsx new file mode 100644 index 00000000..f5b32037 --- /dev/null +++ b/src/lib/unstable/kit/overview-entities/PrimitiveArrayValue/__tests__/PrimitiveArrayValue.visual.test.tsx @@ -0,0 +1,147 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {createSchema, value} from './helpers'; + +test.describe('SchemaRenderer PrimitiveArrayValue', () => { + for (const mode of [SchemaRendererMode.Overview]) { + test(`${mode} layouts sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'empty overview uses the empty value marker', + props: ((): SchemaFormProps => { + const schema = createSchema('cell'); + return { + schema: schema, + value: undefined, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'column direction renders each described item separately', + props: ((): SchemaFormProps => { + const base = createSchema('row'); + const schema = { + ...base, + nodeParameters: { + ...base.nodeParameters, + entityProps: { + ...base.nodeParameters?.entityProps, + direction: 'column', + }, + }, + }; + return { + schema: schema, + value: value, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'unknown enum values remain visible', + props: ((): SchemaFormProps => { + const schema = createSchema('row'); + return { + schema: schema, + value: ['draft', 'unknown'], + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const grid = await mount( + { + const schema = createSchema(layout); + const label = `layout=${layoutName} / size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByTestId('input')).toHaveText('Draft, Published'); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'empty overview uses the empty value marker', + exact: true, + }); + await expect(component.getByText('—', {exact: true})).toBeVisible(); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'column direction renders each described item separately', + exact: true, + }); + await expect(component.getByTestId('input[0]')).toHaveText('Draft'); + await expect(component.getByTestId('input[1]')).toHaveText('Published'); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'unknown enum values remain visible', + exact: true, + }); + await expect(component.getByTestId('input')).toHaveText('Draft, unknown'); + } + await expectScreenshot({scale: 'css'}); + }); + } +}); diff --git a/src/lib/unstable/kit/overview-entities/PrimitiveArrayValue/__tests__/helpers.ts b/src/lib/unstable/kit/overview-entities/PrimitiveArrayValue/__tests__/helpers.ts new file mode 100644 index 00000000..28fedd37 --- /dev/null +++ b/src/lib/unstable/kit/overview-entities/PrimitiveArrayValue/__tests__/helpers.ts @@ -0,0 +1,22 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaArray} from '../../../../core/types'; + +export const createSchema = (layout: string | undefined): JsonSchemaArray => ({ + type: JsonSchemaType.Array, + title: 'PrimitiveArrayValue', + description: 'Description for PrimitiveArrayValue', + items: {type: JsonSchemaType.String, enum: ['draft', 'published', 'archived']}, + nodeParameters: { + type: NodeType.Array, + entity: 'select', + layout, + entityProps: { + direction: 'row', + enumDescriptions: {draft: 'Draft', published: 'Published', archived: 'Archived'}, + }, + flags: {open: true}, + layoutProps: {descriptionType: 'bottom'}, + }, +}); + +export const value = ['draft', 'published']; diff --git a/src/lib/unstable/kit/overview-entities/RangeValue/__snapshots__/RangeValue.visual.test.tsx-snapshots/SchemaRenderer-RangeValue-overview-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/overview-entities/RangeValue/__snapshots__/RangeValue.visual.test.tsx-snapshots/SchemaRenderer-RangeValue-overview-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..0b4d180b Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/RangeValue/__snapshots__/RangeValue.visual.test.tsx-snapshots/SchemaRenderer-RangeValue-overview-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/RangeValue/__snapshots__/RangeValue.visual.test.tsx-snapshots/SchemaRenderer-RangeValue-overview-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/overview-entities/RangeValue/__snapshots__/RangeValue.visual.test.tsx-snapshots/SchemaRenderer-RangeValue-overview-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..3b0147fb Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/RangeValue/__snapshots__/RangeValue.visual.test.tsx-snapshots/SchemaRenderer-RangeValue-overview-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/RangeValue/__snapshots__/RangeValue.visual.test.tsx-snapshots/SchemaRenderer-RangeValue-overview-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/overview-entities/RangeValue/__snapshots__/RangeValue.visual.test.tsx-snapshots/SchemaRenderer-RangeValue-overview-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..fc121e5b Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/RangeValue/__snapshots__/RangeValue.visual.test.tsx-snapshots/SchemaRenderer-RangeValue-overview-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/RangeValue/__snapshots__/RangeValue.visual.test.tsx-snapshots/SchemaRenderer-RangeValue-overview-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/overview-entities/RangeValue/__snapshots__/RangeValue.visual.test.tsx-snapshots/SchemaRenderer-RangeValue-overview-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..bec8b001 Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/RangeValue/__snapshots__/RangeValue.visual.test.tsx-snapshots/SchemaRenderer-RangeValue-overview-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/RangeValue/__tests__/RangeValue.visual.test.tsx b/src/lib/unstable/kit/overview-entities/RangeValue/__tests__/RangeValue.visual.test.tsx new file mode 100644 index 00000000..d37ff4ab --- /dev/null +++ b/src/lib/unstable/kit/overview-entities/RangeValue/__tests__/RangeValue.visual.test.tsx @@ -0,0 +1,150 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {JsonSchemaType, SchemaRendererMode} from '../../../../core/constants'; + +import {createSchema, value} from './helpers'; + +test.describe('SchemaRenderer RangeValue', () => { + for (const mode of [SchemaRendererMode.Overview]) { + test(`${mode} layouts sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'empty overview uses the empty value marker', + props: ((): SchemaFormProps => { + const schema = createSchema('cell'); + return { + schema: schema, + value: undefined, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'custom property keys and separator preserve zero', + props: ((): SchemaFormProps => { + const base = createSchema('row'); + const schema = { + ...base, + properties: { + start: {type: JsonSchemaType.Number}, + end: {type: JsonSchemaType.Number}, + }, + nodeParameters: { + ...base.nodeParameters, + entityProps: {propertyKeys: ['start', 'end'], separator: 'until'}, + }, + }; + return { + schema: schema, + value: {start: 0, end: 100}, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'missing range endpoint uses the empty value marker', + props: ((): SchemaFormProps => { + const schema = createSchema('cell'); + return { + schema: schema, + value: {from: 20}, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const grid = await mount( + { + const schema = createSchema(layout); + const label = `layout=${layoutName} / size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByTestId('input.from')).toHaveText('20'); + await expect(component.getByTestId('input.to')).toHaveText('80'); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'empty overview uses the empty value marker', + exact: true, + }); + await expect(component.getByText('—', {exact: true})).toBeVisible(); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'custom property keys and separator preserve zero', + exact: true, + }); + await expect(component.getByTestId('input.start')).toHaveText('0'); + await expect(component.getByTestId('input.end')).toHaveText('100'); + await expect(component.getByText('until', {exact: true})).toBeVisible(); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'missing range endpoint uses the empty value marker', + exact: true, + }); + await expect(component.getByText('—', {exact: true})).toBeVisible(); + } + await expectScreenshot({scale: 'css'}); + }); + } +}); diff --git a/src/lib/unstable/kit/overview-entities/RangeValue/__tests__/helpers.ts b/src/lib/unstable/kit/overview-entities/RangeValue/__tests__/helpers.ts new file mode 100644 index 00000000..e9ab3e03 --- /dev/null +++ b/src/lib/unstable/kit/overview-entities/RangeValue/__tests__/helpers.ts @@ -0,0 +1,19 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaObject} from '../../../../core/types'; + +export const createSchema = (layout: string | undefined): JsonSchemaObject => ({ + type: JsonSchemaType.Object, + title: 'RangeValue', + description: 'Description for RangeValue', + properties: {from: {type: JsonSchemaType.Number}, to: {type: JsonSchemaType.Number}}, + nodeParameters: { + type: NodeType.Object, + entity: 'range_input', + layout, + entityProps: {propertyKeys: ['from', 'to'], separator: 'to'}, + flags: {open: true}, + layoutProps: {descriptionType: 'bottom'}, + }, +}); + +export const value = {from: 20, to: 80}; diff --git a/src/lib/unstable/kit/overview-entities/StringNumberWithScaleValue/__snapshots__/StringNumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleValue-overview-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/overview-entities/StringNumberWithScaleValue/__snapshots__/StringNumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleValue-overview-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..863c99fc Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/StringNumberWithScaleValue/__snapshots__/StringNumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleValue-overview-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/StringNumberWithScaleValue/__snapshots__/StringNumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleValue-overview-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/overview-entities/StringNumberWithScaleValue/__snapshots__/StringNumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleValue-overview-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..f27e65ca Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/StringNumberWithScaleValue/__snapshots__/StringNumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleValue-overview-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/StringNumberWithScaleValue/__snapshots__/StringNumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleValue-overview-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/overview-entities/StringNumberWithScaleValue/__snapshots__/StringNumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleValue-overview-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..b066f6ee Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/StringNumberWithScaleValue/__snapshots__/StringNumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleValue-overview-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/StringNumberWithScaleValue/__snapshots__/StringNumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleValue-overview-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/overview-entities/StringNumberWithScaleValue/__snapshots__/StringNumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleValue-overview-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..383d49e7 Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/StringNumberWithScaleValue/__snapshots__/StringNumberWithScaleValue.visual.test.tsx-snapshots/SchemaRenderer-StringNumberWithScaleValue-overview-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/StringNumberWithScaleValue/__tests__/StringNumberWithScaleValue.visual.test.tsx b/src/lib/unstable/kit/overview-entities/StringNumberWithScaleValue/__tests__/StringNumberWithScaleValue.visual.test.tsx new file mode 100644 index 00000000..50bb207d --- /dev/null +++ b/src/lib/unstable/kit/overview-entities/StringNumberWithScaleValue/__tests__/StringNumberWithScaleValue.visual.test.tsx @@ -0,0 +1,224 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {createSchema, value} from './helpers'; + +test.describe('SchemaRenderer StringNumberWithScaleValue', () => { + for (const mode of [SchemaRendererMode.Overview]) { + test(`${mode} layouts sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'empty overview uses the empty value marker', + props: ((): SchemaFormProps => { + const schema = createSchema('cell'); + return { + schema: schema, + value: undefined, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'zero remains visible without a scale suffix', + props: ((): SchemaFormProps => { + const schema = createSchema('row'); + return { + schema: schema, + value: '0', + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'negative value selects its scale using absolute magnitude', + props: ((): SchemaFormProps => { + const schema = createSchema('row'); + return { + schema: schema, + value: '-1500', + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'explicit view type handles a value smaller than its factor', + props: ((): SchemaFormProps => { + const base = createSchema('row'); + const schema = { + ...base, + nodeParameters: { + ...base.nodeParameters, + entityProps: {...base.nodeParameters?.entityProps, viewType: 'km'}, + }, + }; + return { + schema: schema, + value: '50', + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'large decimal strings retain precision', + props: ((): SchemaFormProps => { + const schema = createSchema('row'); + return { + schema: schema, + value: '9007199254740993000', + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'invalid value uses the empty value marker', + props: ((): SchemaFormProps => { + const schema = createSchema('cell'); + return { + schema: schema, + value: 'not-a-valid-value', + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const grid = await mount( + { + const schema = createSchema(layout); + const label = `layout=${layoutName} / size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByTestId('input')).toHaveText('1.5'); + await expect(component.getByText('km', {exact: true})).toBeVisible(); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'empty overview uses the empty value marker', + exact: true, + }); + await expect(component.getByText('—', {exact: true})).toBeVisible(); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'zero remains visible without a scale suffix', + exact: true, + }); + await expect(component.getByTestId('input')).toHaveText('0'); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'negative value selects its scale using absolute magnitude', + exact: true, + }); + await expect(component.getByTestId('input')).toHaveText('-1.5'); + await expect(component.getByText('km', {exact: true})).toBeVisible(); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'explicit view type handles a value smaller than its factor', + exact: true, + }); + await expect(component.getByTestId('input')).toHaveText('0.05'); + await expect(component.getByText('km', {exact: true})).toBeVisible(); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'large decimal strings retain precision', + exact: true, + }); + await expect(component.getByTestId('input')).toHaveText('9007199254740993'); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'invalid value uses the empty value marker', + exact: true, + }); + await expect(component.getByText('—', {exact: true})).toBeVisible(); + } + await expectScreenshot({scale: 'css'}); + }); + } +}); diff --git a/src/lib/unstable/kit/overview-entities/StringNumberWithScaleValue/__tests__/helpers.ts b/src/lib/unstable/kit/overview-entities/StringNumberWithScaleValue/__tests__/helpers.ts new file mode 100644 index 00000000..e1061873 --- /dev/null +++ b/src/lib/unstable/kit/overview-entities/StringNumberWithScaleValue/__tests__/helpers.ts @@ -0,0 +1,19 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +export const createSchema = (layout: string | undefined): JsonSchemaString => ({ + type: JsonSchemaType.String, + title: 'StringNumberWithScaleValue', + description: 'Description for StringNumberWithScaleValue', + stringNumber: {type: JsonSchemaType.Number}, + nodeParameters: { + type: NodeType.String, + entity: 'string_number_with_scale', + layout, + entityProps: {scale: {m: {title: 'm', factor: '1'}, km: {title: 'km', factor: '1000'}}}, + flags: {open: true}, + layoutProps: {descriptionType: 'bottom'}, + }, +}); + +export const value = '1500'; diff --git a/src/lib/unstable/kit/overview-entities/StringValue/__snapshots__/StringValue.visual.test.tsx-snapshots/SchemaRenderer-StringValue-overview-layouts-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/overview-entities/StringValue/__snapshots__/StringValue.visual.test.tsx-snapshots/SchemaRenderer-StringValue-overview-layouts-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..150cfff1 Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/StringValue/__snapshots__/StringValue.visual.test.tsx-snapshots/SchemaRenderer-StringValue-overview-layouts-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/StringValue/__snapshots__/StringValue.visual.test.tsx-snapshots/SchemaRenderer-StringValue-overview-layouts-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/overview-entities/StringValue/__snapshots__/StringValue.visual.test.tsx-snapshots/SchemaRenderer-StringValue-overview-layouts-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..b601e179 Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/StringValue/__snapshots__/StringValue.visual.test.tsx-snapshots/SchemaRenderer-StringValue-overview-layouts-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/StringValue/__snapshots__/StringValue.visual.test.tsx-snapshots/SchemaRenderer-StringValue-overview-layouts-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/overview-entities/StringValue/__snapshots__/StringValue.visual.test.tsx-snapshots/SchemaRenderer-StringValue-overview-layouts-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..d883793c Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/StringValue/__snapshots__/StringValue.visual.test.tsx-snapshots/SchemaRenderer-StringValue-overview-layouts-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/StringValue/__snapshots__/StringValue.visual.test.tsx-snapshots/SchemaRenderer-StringValue-overview-layouts-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/overview-entities/StringValue/__snapshots__/StringValue.visual.test.tsx-snapshots/SchemaRenderer-StringValue-overview-layouts-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..a9a2f90e Binary files /dev/null and b/src/lib/unstable/kit/overview-entities/StringValue/__snapshots__/StringValue.visual.test.tsx-snapshots/SchemaRenderer-StringValue-overview-layouts-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/overview-entities/StringValue/__tests__/StringValue.visual.test.tsx b/src/lib/unstable/kit/overview-entities/StringValue/__tests__/StringValue.visual.test.tsx new file mode 100644 index 00000000..2c781120 --- /dev/null +++ b/src/lib/unstable/kit/overview-entities/StringValue/__tests__/StringValue.visual.test.tsx @@ -0,0 +1,124 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererVariants} from '~playwright/core/schemaRendererVariants'; + +import {SchemaRendererMode} from '../../../../core/constants'; + +import {createSchema, value} from './helpers'; + +test.describe('SchemaRenderer StringValue', () => { + for (const mode of [SchemaRendererMode.Overview]) { + test(`${mode} layouts sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'empty overview uses the empty value marker', + props: ((): SchemaFormProps => { + const schema = createSchema('cell'); + return { + schema: schema, + value: undefined, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + { + label: 'long text expands on click', + height: 620, + props: ((): SchemaFormProps => { + const schema = createSchema('row'); + const longValue = + 'A long account description that must not be cut off when expanded. '.repeat( + 20, + ); + return { + schema: schema, + value: longValue, + mode: SchemaRendererMode.Overview, + size: 'm', + view: 'stretch', + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const grid = await mount( + { + const schema = createSchema(layout); + const label = `layout=${layoutName} / size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(rendererVariants.length + additionalCases.length); + for (let index = 0; index < rendererVariants.length; index++) { + const component = cases.nth(index); + await expect(component.getByTestId('input')).toHaveText(value); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'empty overview uses the empty value marker', + exact: true, + }); + + await expect(component.getByText('—', {exact: true})).toBeVisible(); + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'long text expands on click', + exact: true, + }); + const longValue = + 'A long account description that must not be cut off when expanded. '.repeat( + 20, + ); + const text = component.getByTestId('input'); + await expect(text).toHaveClass(/sr-long-value_long/); + await text.click(); + await expect(text).toHaveClass(/sr-long-value_open/); + await expect(text).toHaveText(longValue.trim()); + } + await expectScreenshot({scale: 'css'}); + }); + } +}); diff --git a/src/lib/unstable/kit/overview-entities/StringValue/__tests__/helpers.ts b/src/lib/unstable/kit/overview-entities/StringValue/__tests__/helpers.ts new file mode 100644 index 00000000..f577c46f --- /dev/null +++ b/src/lib/unstable/kit/overview-entities/StringValue/__tests__/helpers.ts @@ -0,0 +1,19 @@ +import {JsonSchemaType, NodeType} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +export const createSchema = (layout: string | undefined): JsonSchemaString => ({ + type: JsonSchemaType.String, + title: 'StringValue', + description: 'Description for StringValue', + + nodeParameters: { + type: NodeType.String, + entity: 'base', + layout, + entityProps: {}, + flags: {open: true}, + layoutProps: {descriptionType: 'bottom'}, + }, +}); + +export const value = 'A detailed account description'; diff --git a/src/lib/unstable/kit/overview-layouts/OverviewColumn/__snapshots__/OverviewColumn.visual.test.tsx-snapshots/OverviewColumn-overview-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/overview-layouts/OverviewColumn/__snapshots__/OverviewColumn.visual.test.tsx-snapshots/OverviewColumn-overview-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..356515d2 Binary files /dev/null and b/src/lib/unstable/kit/overview-layouts/OverviewColumn/__snapshots__/OverviewColumn.visual.test.tsx-snapshots/OverviewColumn-overview-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/overview-layouts/OverviewColumn/__snapshots__/OverviewColumn.visual.test.tsx-snapshots/OverviewColumn-overview-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/overview-layouts/OverviewColumn/__snapshots__/OverviewColumn.visual.test.tsx-snapshots/OverviewColumn-overview-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..6ad6cdb5 Binary files /dev/null and b/src/lib/unstable/kit/overview-layouts/OverviewColumn/__snapshots__/OverviewColumn.visual.test.tsx-snapshots/OverviewColumn-overview-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/overview-layouts/OverviewColumn/__snapshots__/OverviewColumn.visual.test.tsx-snapshots/OverviewColumn-overview-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/overview-layouts/OverviewColumn/__snapshots__/OverviewColumn.visual.test.tsx-snapshots/OverviewColumn-overview-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..ed6a3f0e Binary files /dev/null and b/src/lib/unstable/kit/overview-layouts/OverviewColumn/__snapshots__/OverviewColumn.visual.test.tsx-snapshots/OverviewColumn-overview-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/overview-layouts/OverviewColumn/__snapshots__/OverviewColumn.visual.test.tsx-snapshots/OverviewColumn-overview-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/overview-layouts/OverviewColumn/__snapshots__/OverviewColumn.visual.test.tsx-snapshots/OverviewColumn-overview-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..d76dffd8 Binary files /dev/null and b/src/lib/unstable/kit/overview-layouts/OverviewColumn/__snapshots__/OverviewColumn.visual.test.tsx-snapshots/OverviewColumn-overview-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/overview-layouts/OverviewColumn/__tests__/OverviewColumn.visual.test.tsx b/src/lib/unstable/kit/overview-layouts/OverviewColumn/__tests__/OverviewColumn.visual.test.tsx new file mode 100644 index 00000000..dfff1302 --- /dev/null +++ b/src/lib/unstable/kit/overview-layouts/OverviewColumn/__tests__/OverviewColumn.visual.test.tsx @@ -0,0 +1,110 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererSizes, rendererViews} from '~playwright/core/schemaRendererVariants'; + +import {JsonSchemaType, NodeType, SchemaRendererMode} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +const modes = [SchemaRendererMode.Overview]; +const baseSchema: JsonSchemaString = { + type: JsonSchemaType.String, + title: 'Service name', + description: 'Name shown to other team members', + nodeParameters: {type: NodeType.String, entity: 'base', layout: 'column', flags: {open: true}}, +}; + +test.describe('OverviewColumn', () => { + for (const mode of modes) { + test(`${mode} sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'long title and value with copy action', + props: ((): SchemaFormProps => { + const schema: JsonSchemaString = { + ...baseSchema, + title: 'A long service identifier that should wrap instead of covering the value', + nodeParameters: {...baseSchema.nodeParameters, flags: {copy: true}}, + }; + const value = + 'service-with-a-long-display-name-for-overview-layout-wrapping'; + const mode = SchemaRendererMode.Overview; + const size = 'xl'; + const view = 'fit'; + return { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const schema = baseSchema; + const value = 'Gravity service'; + const variants = rendererSizes.flatMap((size) => + rendererViews.map((view) => ({size, view})), + ); + const grid = await mount( + { + const label = `size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(variants.length + additionalCases.length); + for (let index = 0; index < variants.length; index++) { + const component = cases.nth(index); + if (mode === SchemaRendererMode.Form) { + await expect(component.getByRole('textbox')).toHaveValue(value); + } else { + await expect(component.getByText(value, {exact: true})).toBeVisible(); + } + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'long title and value with copy action', + exact: true, + }); + const value = 'service-with-a-long-display-name-for-overview-layout-wrapping'; + await expect(component.getByText(value, {exact: true})).toBeVisible(); + await expect(component.getByRole('button')).toBeVisible(); + } + await expectScreenshot({scale: 'css'}); + }); + } +}); diff --git a/src/lib/unstable/kit/overview-layouts/OverviewRow/__snapshots__/OverviewRow.visual.test.tsx-snapshots/OverviewRow-overview-sizes-and-views-dark-chromium-linux.png b/src/lib/unstable/kit/overview-layouts/OverviewRow/__snapshots__/OverviewRow.visual.test.tsx-snapshots/OverviewRow-overview-sizes-and-views-dark-chromium-linux.png new file mode 100644 index 00000000..16a28036 Binary files /dev/null and b/src/lib/unstable/kit/overview-layouts/OverviewRow/__snapshots__/OverviewRow.visual.test.tsx-snapshots/OverviewRow-overview-sizes-and-views-dark-chromium-linux.png differ diff --git a/src/lib/unstable/kit/overview-layouts/OverviewRow/__snapshots__/OverviewRow.visual.test.tsx-snapshots/OverviewRow-overview-sizes-and-views-dark-webkit-linux.png b/src/lib/unstable/kit/overview-layouts/OverviewRow/__snapshots__/OverviewRow.visual.test.tsx-snapshots/OverviewRow-overview-sizes-and-views-dark-webkit-linux.png new file mode 100644 index 00000000..fdfd3c13 Binary files /dev/null and b/src/lib/unstable/kit/overview-layouts/OverviewRow/__snapshots__/OverviewRow.visual.test.tsx-snapshots/OverviewRow-overview-sizes-and-views-dark-webkit-linux.png differ diff --git a/src/lib/unstable/kit/overview-layouts/OverviewRow/__snapshots__/OverviewRow.visual.test.tsx-snapshots/OverviewRow-overview-sizes-and-views-light-chromium-linux.png b/src/lib/unstable/kit/overview-layouts/OverviewRow/__snapshots__/OverviewRow.visual.test.tsx-snapshots/OverviewRow-overview-sizes-and-views-light-chromium-linux.png new file mode 100644 index 00000000..14beda0e Binary files /dev/null and b/src/lib/unstable/kit/overview-layouts/OverviewRow/__snapshots__/OverviewRow.visual.test.tsx-snapshots/OverviewRow-overview-sizes-and-views-light-chromium-linux.png differ diff --git a/src/lib/unstable/kit/overview-layouts/OverviewRow/__snapshots__/OverviewRow.visual.test.tsx-snapshots/OverviewRow-overview-sizes-and-views-light-webkit-linux.png b/src/lib/unstable/kit/overview-layouts/OverviewRow/__snapshots__/OverviewRow.visual.test.tsx-snapshots/OverviewRow-overview-sizes-and-views-light-webkit-linux.png new file mode 100644 index 00000000..7958a6d8 Binary files /dev/null and b/src/lib/unstable/kit/overview-layouts/OverviewRow/__snapshots__/OverviewRow.visual.test.tsx-snapshots/OverviewRow-overview-sizes-and-views-light-webkit-linux.png differ diff --git a/src/lib/unstable/kit/overview-layouts/OverviewRow/__tests__/OverviewRow.visual.test.tsx b/src/lib/unstable/kit/overview-layouts/OverviewRow/__tests__/OverviewRow.visual.test.tsx new file mode 100644 index 00000000..cd024d97 --- /dev/null +++ b/src/lib/unstable/kit/overview-layouts/OverviewRow/__tests__/OverviewRow.visual.test.tsx @@ -0,0 +1,111 @@ +import React from 'react'; + +import {expect} from '@playwright/experimental-ct-react'; + +import {schemaTest as test} from '~playwright/core'; +import type {SchemaFormProps} from '~playwright/core/SchemaForm'; +import {SchemaFormGrid, type SchemaFormGridProps} from '~playwright/core/SchemaFormGrid'; +import {rendererSizes, rendererViews} from '~playwright/core/schemaRendererVariants'; + +import {JsonSchemaType, NodeType, SchemaRendererMode} from '../../../../core/constants'; +import type {JsonSchemaString} from '../../../../core/types'; + +const modes = [SchemaRendererMode.Overview]; +const baseSchema: JsonSchemaString = { + type: JsonSchemaType.String, + title: 'Service name', + description: 'Name shown to other team members', + nodeParameters: {type: NodeType.String, entity: 'base', layout: 'row', flags: {open: true}}, +}; + +test.describe('OverviewRow', () => { + for (const mode of modes) { + test(`${mode} sizes and views`, async ({mount, expectScreenshot}) => { + const stateCases: SchemaFormGridProps['cases'] = [ + { + label: 'long title and value with copy action', + height: 140, + props: ((): SchemaFormProps => { + const schema: JsonSchemaString = { + ...baseSchema, + title: 'A long service identifier that should wrap instead of covering the value', + nodeParameters: {...baseSchema.nodeParameters, flags: {copy: true}}, + }; + const value = + 'service-with-a-long-display-name-for-overview-layout-wrapping'; + const mode = SchemaRendererMode.Overview; + const size = 'xl'; + const view = 'fit'; + return { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }; + })(), + }, + ]; + const additionalCases = stateCases.filter(({props}) => props.mode === mode); + + const schema = baseSchema; + const value = 'Gravity service'; + const variants = rendererSizes.flatMap((size) => + rendererViews.map((view) => ({size, view})), + ); + const grid = await mount( + { + const label = `size=${size} / view=${view}`; + return { + label, + props: { + schema: schema, + value: value, + mode: mode, + size: size, + view: view, + coerceInitialValues: true, + jsonDefaultValues: false, + validateOnBlur: false, + userContext: {}, + showSubmit: false, + width: 600, + }, + }; + }), + ...additionalCases, + ]} + />, + ); + const cases = grid.getByTestId('schema-variant'); + await expect(cases).toHaveCount(variants.length + additionalCases.length); + for (let index = 0; index < variants.length; index++) { + const component = cases.nth(index); + if (mode === SchemaRendererMode.Form) { + await expect(component.getByRole('textbox')).toHaveValue(value); + } else { + await expect(component.getByText(value, {exact: true})).toBeVisible(); + } + } + if (mode === SchemaRendererMode.Overview) { + const component = grid.getByRole('region', { + name: 'long title and value with copy action', + exact: true, + }); + const value = 'service-with-a-long-display-name-for-overview-layout-wrapping'; + await expect(component.getByText(value, {exact: true})).toBeVisible(); + await expect(component.getByRole('button')).toBeVisible(); + } + await expectScreenshot({scale: 'css'}); + }); + } +});