diff --git a/coral/package.json b/coral/package.json index ba3686a261..a1a8f6b169 100644 --- a/coral/package.json +++ b/coral/package.json @@ -79,7 +79,7 @@ "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", "@vitejs/plugin-react": "^4.1.0", - "eslint": "^8.51.0", + "eslint": "^8.54.0", "eslint-config-prettier": "^9.0.0", "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-import": "^2.28.1", @@ -90,7 +90,7 @@ "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-transform-stub": "^2.0.0", - "lint-staged": "^13.0.3", + "lint-staged": "^15.1.0", "lodash": "^4.17.21", "msw": "^2.0.1", "openapi-typescript": "^6.7.0", @@ -102,7 +102,7 @@ "ts-node": "^10.9.1", "typescript": "^5.0.4", "vite": "^4.5.0", - "vite-plugin-svgr": "^3.2.0", + "vite-plugin-svgr": "^4.2.0", "whatwg-fetch": "^3.6.19" }, "peerDependencies": { diff --git a/coral/pnpm-lock.yaml b/coral/pnpm-lock.yaml index 06b00b7e04..2a2e69d11c 100644 --- a/coral/pnpm-lock.yaml +++ b/coral/pnpm-lock.yaml @@ -111,34 +111,34 @@ devDependencies: version: 18.2.7 '@typescript-eslint/eslint-plugin': specifier: ^5.62.0 - version: 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.51.0)(typescript@5.0.4) + version: 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.54.0)(typescript@5.0.4) '@typescript-eslint/parser': specifier: ^5.62.0 - version: 5.62.0(eslint@8.51.0)(typescript@5.0.4) + version: 5.62.0(eslint@8.54.0)(typescript@5.0.4) '@vitejs/plugin-react': specifier: ^4.1.0 version: 4.1.0(vite@4.5.0) eslint: - specifier: ^8.51.0 - version: 8.51.0 + specifier: ^8.54.0 + version: 8.54.0 eslint-config-prettier: specifier: ^9.0.0 - version: 9.0.0(eslint@8.51.0) + version: 9.0.0(eslint@8.54.0) eslint-import-resolver-typescript: specifier: ^3.6.1 - version: 3.6.1(@typescript-eslint/parser@5.62.0)(eslint-plugin-import@2.28.1)(eslint@8.51.0) + version: 3.6.1(@typescript-eslint/parser@5.62.0)(eslint-plugin-import@2.28.1)(eslint@8.54.0) eslint-plugin-import: specifier: ^2.28.1 - version: 2.28.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.51.0) + version: 2.28.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.54.0) eslint-plugin-lodash: specifier: ^7.4.0 - version: 7.4.0(eslint@8.51.0) + version: 7.4.0(eslint@8.54.0) eslint-plugin-no-relative-import-paths: specifier: ^1.5.2 version: 1.5.2 eslint-plugin-react: specifier: ^7.33.2 - version: 7.33.2(eslint@8.51.0) + version: 7.33.2(eslint@8.54.0) identity-obj-proxy: specifier: ^3.0.0 version: 3.0.0 @@ -152,8 +152,8 @@ devDependencies: specifier: ^2.0.0 version: 2.0.0 lint-staged: - specifier: ^13.0.3 - version: 13.0.3 + specifier: ^15.1.0 + version: 15.1.0 lodash: specifier: ^4.17.21 version: 4.17.21 @@ -177,7 +177,7 @@ devDependencies: version: 5.9.2 ts-jest: specifier: ^29.1.1 - version: 29.1.1(@babel/core@7.22.15)(esbuild@0.18.17)(jest@29.7.0)(typescript@5.0.4) + version: 29.1.1(@babel/core@7.22.20)(esbuild@0.18.17)(jest@29.7.0)(typescript@5.0.4) ts-node: specifier: ^10.9.1 version: 10.9.1(@types/node@18.17.14)(typescript@5.0.4) @@ -188,8 +188,8 @@ devDependencies: specifier: ^4.5.0 version: 4.5.0(@types/node@18.17.14)(less@4.2.0) vite-plugin-svgr: - specifier: ^3.2.0 - version: 3.2.0(vite@4.5.0) + specifier: ^4.2.0 + version: 4.2.0(typescript@5.0.4)(vite@4.5.0) whatwg-fetch: specifier: ^3.6.19 version: 3.6.19 @@ -268,29 +268,6 @@ packages: engines: {node: '>=6.9.0'} dev: true - /@babel/core@7.22.15: - resolution: {integrity: sha512-PtZqMmgRrvj8ruoEOIwVA3yoF91O+Hgw9o7DAUTNBA6Mo2jpu31clx9a7Nz/9JznqetTR6zwfC4L3LAjKQXUwA==} - engines: {node: '>=6.9.0'} - dependencies: - '@ampproject/remapping': 2.2.1 - '@babel/code-frame': 7.22.13 - '@babel/generator': 7.22.15 - '@babel/helper-compilation-targets': 7.22.15 - '@babel/helper-module-transforms': 7.22.15(@babel/core@7.22.15) - '@babel/helpers': 7.22.15 - '@babel/parser': 7.22.16 - '@babel/template': 7.22.15 - '@babel/traverse': 7.23.2 - '@babel/types': 7.22.19 - convert-source-map: 1.9.0 - debug: 4.3.4 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - dev: true - /@babel/core@7.22.20: resolution: {integrity: sha512-Y6jd1ahLubuYweD/zJH+vvOY141v4f9igNQAQ+MBgq9JlHS2iTsZKn1aMsb3vGccZsXI16VzTBw52Xx0DWmtnA==} engines: {node: '>=6.9.0'} @@ -318,7 +295,7 @@ packages: resolution: {integrity: sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.19 + '@babel/types': 7.23.0 '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.19 jsesc: 2.5.2 @@ -369,21 +346,7 @@ packages: resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.19 - dev: true - - /@babel/helper-module-transforms@7.22.15(@babel/core@7.22.15): - resolution: {integrity: sha512-l1UiX4UyHSFsYt17iQ3Se5pQQZZHa22zyIXURmvkmLCD4t/aU+dvNWHatKac/D9Vm9UES7nvIqHs4jZqKviUmQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.22.15 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-module-imports': 7.22.15 - '@babel/helper-simple-access': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - '@babel/helper-validator-identifier': 7.22.20 + '@babel/types': 7.23.0 dev: true /@babel/helper-module-transforms@7.22.20(@babel/core@7.22.20): @@ -409,7 +372,7 @@ packages: resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.22.19 + '@babel/types': 7.23.0 dev: true /@babel/helper-split-export-declaration@7.22.6: @@ -438,7 +401,7 @@ packages: dependencies: '@babel/template': 7.22.15 '@babel/traverse': 7.23.2 - '@babel/types': 7.22.19 + '@babel/types': 7.23.0 transitivePeerDependencies: - supports-color dev: true @@ -456,7 +419,7 @@ packages: engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.22.19 + '@babel/types': 7.23.0 /@babel/parser@7.23.0: resolution: {integrity: sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==} @@ -465,132 +428,132 @@ packages: dependencies: '@babel/types': 7.23.0 - /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.22.15): + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.22.20): resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.22.15): + /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.22.20): resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.22.15): + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.22.20): resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.22.15): + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.22.20): resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.22.15): + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.22.20): resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-jsx@7.22.5(@babel/core@7.22.15): + /@babel/plugin-syntax-jsx@7.22.5(@babel/core@7.22.20): resolution: {integrity: sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.22.15): + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.22.20): resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.22.15): + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.22.20): resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.22.15): + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.22.20): resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.22.15): + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.22.20): resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.22.15): + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.22.20): resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.22.15): + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.22.20): resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.22.15): + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.22.20): resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-typescript@7.22.5(@babel/core@7.22.15): + /@babel/plugin-syntax-typescript@7.22.5(@babel/core@7.22.20): resolution: {integrity: sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 '@babel/helper-plugin-utils': 7.22.5 dev: true @@ -875,13 +838,13 @@ packages: requiresBuild: true optional: true - /@eslint-community/eslint-utils@4.2.0(eslint@8.51.0): + /@eslint-community/eslint-utils@4.2.0(eslint@8.54.0): resolution: {integrity: sha512-gB8T4H4DEfX2IV9zGDJPOBgP1e/DbfCPDTtEqUMckpvzS1OYtva8JdFYBqMwYk7xAQ429WGF/UPqn8uQ//h2vQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.51.0 + eslint: 8.54.0 eslint-visitor-keys: 3.4.3 dev: true @@ -890,8 +853,8 @@ packages: engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} dev: true - /@eslint/eslintrc@2.1.2: - resolution: {integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==} + /@eslint/eslintrc@2.1.3: + resolution: {integrity: sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 @@ -907,8 +870,8 @@ packages: - supports-color dev: true - /@eslint/js@8.51.0: - resolution: {integrity: sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==} + /@eslint/js@8.54.0: + resolution: {integrity: sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true @@ -987,11 +950,11 @@ packages: react-hook-form: 7.43.9(react@18.2.0) dev: false - /@humanwhocodes/config-array@0.11.11: - resolution: {integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==} + /@humanwhocodes/config-array@0.11.13: + resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==} engines: {node: '>=10.10.0'} dependencies: - '@humanwhocodes/object-schema': 1.2.1 + '@humanwhocodes/object-schema': 2.0.1 debug: 4.3.4 minimatch: 3.1.2 transitivePeerDependencies: @@ -1003,8 +966,8 @@ packages: engines: {node: '>=12.22'} dev: true - /@humanwhocodes/object-schema@1.2.1: - resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + /@humanwhocodes/object-schema@2.0.1: + resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} dev: true /@iconify/react@3.2.2(react@18.2.0): @@ -1255,7 +1218,7 @@ packages: resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.19 babel-plugin-istanbul: 6.1.1 @@ -3252,11 +3215,11 @@ packages: engines: {node: '>=14'} dev: false - /@rollup/pluginutils@5.0.2: - resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==} + /@rollup/pluginutils@5.0.5: + resolution: {integrity: sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0 + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 peerDependenciesMeta: rollup: optional: true @@ -3526,122 +3489,127 @@ packages: file-system-cache: 2.3.0 dev: false - /@svgr/babel-plugin-add-jsx-attribute@7.0.0(@babel/core@7.22.15): - resolution: {integrity: sha512-khWbXesWIP9v8HuKCl2NU2HNAyqpSQ/vkIl36Nbn4HIwEYSRWL0H7Gs6idJdha2DkpFDWlsqMELvoCE8lfFY6Q==} + /@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.22.20): + resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} engines: {node: '>=14'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 dev: true - /@svgr/babel-plugin-remove-jsx-attribute@7.0.0(@babel/core@7.22.15): - resolution: {integrity: sha512-iiZaIvb3H/c7d3TH2HBeK91uI2rMhZNwnsIrvd7ZwGLkFw6mmunOCoVnjdYua662MqGFxlN9xTq4fv9hgR4VXQ==} + /@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.22.20): + resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==} engines: {node: '>=14'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 dev: true - /@svgr/babel-plugin-remove-jsx-empty-expression@7.0.0(@babel/core@7.22.15): - resolution: {integrity: sha512-sQQmyo+qegBx8DfFc04PFmIO1FP1MHI1/QEpzcIcclo5OAISsOJPW76ZIs0bDyO/DBSJEa/tDa1W26pVtt0FRw==} + /@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.22.20): + resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==} engines: {node: '>=14'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 dev: true - /@svgr/babel-plugin-replace-jsx-attribute-value@7.0.0(@babel/core@7.22.15): - resolution: {integrity: sha512-i6MaAqIZXDOJeikJuzocByBf8zO+meLwfQ/qMHIjCcvpnfvWf82PFvredEZElErB5glQFJa2KVKk8N2xV6tRRA==} + /@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.22.20): + resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==} engines: {node: '>=14'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 dev: true - /@svgr/babel-plugin-svg-dynamic-title@7.0.0(@babel/core@7.22.15): - resolution: {integrity: sha512-BoVSh6ge3SLLpKC0pmmN9DFlqgFy4NxNgdZNLPNJWBUU7TQpDWeBuyVuDW88iXydb5Cv0ReC+ffa5h3VrKfk1w==} + /@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.22.20): + resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==} engines: {node: '>=14'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 dev: true - /@svgr/babel-plugin-svg-em-dimensions@7.0.0(@babel/core@7.22.15): - resolution: {integrity: sha512-tNDcBa+hYn0gO+GkP/AuNKdVtMufVhU9fdzu+vUQsR18RIJ9RWe7h/pSBY338RO08wArntwbDk5WhQBmhf2PaA==} + /@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.22.20): + resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==} engines: {node: '>=14'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 dev: true - /@svgr/babel-plugin-transform-react-native-svg@7.0.0(@babel/core@7.22.15): - resolution: {integrity: sha512-qw54u8ljCJYL2KtBOjI5z7Nzg8LnSvQOP5hPKj77H4VQL4+HdKbAT5pnkkZLmHKYwzsIHSYKXxHouD8zZamCFQ==} + /@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.22.20): + resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==} engines: {node: '>=14'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 dev: true - /@svgr/babel-plugin-transform-svg-component@7.0.0(@babel/core@7.22.15): - resolution: {integrity: sha512-CcFECkDj98daOg9jE3Bh3uyD9kzevCAnZ+UtzG6+BQG/jOQ2OA3jHnX6iG4G1MCJkUQFnUvEv33NvQfqrb/F3A==} + /@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.22.20): + resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==} engines: {node: '>=12'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 dev: true - /@svgr/babel-preset@7.0.0(@babel/core@7.22.15): - resolution: {integrity: sha512-EX/NHeFa30j5UjldQGVQikuuQNHUdGmbh9kEpBKofGUtF0GUPJ4T4rhoYiqDAOmBOxojyot36JIFiDUHUK1ilQ==} + /@svgr/babel-preset@8.1.0(@babel/core@7.22.20): + resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==} engines: {node: '>=14'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.15 - '@svgr/babel-plugin-add-jsx-attribute': 7.0.0(@babel/core@7.22.15) - '@svgr/babel-plugin-remove-jsx-attribute': 7.0.0(@babel/core@7.22.15) - '@svgr/babel-plugin-remove-jsx-empty-expression': 7.0.0(@babel/core@7.22.15) - '@svgr/babel-plugin-replace-jsx-attribute-value': 7.0.0(@babel/core@7.22.15) - '@svgr/babel-plugin-svg-dynamic-title': 7.0.0(@babel/core@7.22.15) - '@svgr/babel-plugin-svg-em-dimensions': 7.0.0(@babel/core@7.22.15) - '@svgr/babel-plugin-transform-react-native-svg': 7.0.0(@babel/core@7.22.15) - '@svgr/babel-plugin-transform-svg-component': 7.0.0(@babel/core@7.22.15) - dev: true - - /@svgr/core@7.0.0: - resolution: {integrity: sha512-ztAoxkaKhRVloa3XydohgQQCb0/8x9T63yXovpmHzKMkHO6pkjdsIAWKOS4bE95P/2quVh1NtjSKlMRNzSBffw==} + '@babel/core': 7.22.20 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.22.20) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.22.20) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.22.20) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.22.20) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.22.20) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.22.20) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.22.20) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.22.20) + dev: true + + /@svgr/core@8.1.0(typescript@5.0.4): + resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==} engines: {node: '>=14'} dependencies: - '@babel/core': 7.22.15 - '@svgr/babel-preset': 7.0.0(@babel/core@7.22.15) + '@babel/core': 7.22.20 + '@svgr/babel-preset': 8.1.0(@babel/core@7.22.20) camelcase: 6.3.0 - cosmiconfig: 8.1.3 + cosmiconfig: 8.3.6(typescript@5.0.4) + snake-case: 3.0.4 transitivePeerDependencies: - supports-color + - typescript dev: true - /@svgr/hast-util-to-babel-ast@7.0.0: - resolution: {integrity: sha512-42Ej9sDDEmsJKjrfQ1PHmiDiHagh/u9AHO9QWbeNx4KmD9yS5d1XHmXUNINfUcykAU+4431Cn+k6Vn5mWBYimQ==} + /@svgr/hast-util-to-babel-ast@8.0.0: + resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==} engines: {node: '>=14'} dependencies: - '@babel/types': 7.22.19 + '@babel/types': 7.23.0 entities: 4.4.0 dev: true - /@svgr/plugin-jsx@7.0.0: - resolution: {integrity: sha512-SWlTpPQmBUtLKxXWgpv8syzqIU8XgFRvyhfkam2So8b3BE0OS0HPe5UfmlJ2KIC+a7dpuuYovPR2WAQuSyMoPw==} + /@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0): + resolution: {integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==} engines: {node: '>=14'} + peerDependencies: + '@svgr/core': '*' dependencies: - '@babel/core': 7.22.15 - '@svgr/babel-preset': 7.0.0(@babel/core@7.22.15) - '@svgr/hast-util-to-babel-ast': 7.0.0 + '@babel/core': 7.22.20 + '@svgr/babel-preset': 8.1.0(@babel/core@7.22.20) + '@svgr/core': 8.1.0(typescript@5.0.4) + '@svgr/hast-util-to-babel-ast': 8.0.0 svg-parser: 2.0.4 transitivePeerDependencies: - supports-color @@ -3815,7 +3783,7 @@ packages: resolution: {integrity: sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==} dependencies: '@babel/parser': 7.22.16 - '@babel/types': 7.22.19 + '@babel/types': 7.23.0 '@types/babel__generator': 7.6.4 '@types/babel__template': 7.4.1 '@types/babel__traverse': 7.20.1 @@ -3833,18 +3801,18 @@ packages: /@types/babel__generator@7.6.4: resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} dependencies: - '@babel/types': 7.22.19 + '@babel/types': 7.23.0 /@types/babel__template@7.4.1: resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} dependencies: '@babel/parser': 7.22.16 - '@babel/types': 7.22.19 + '@babel/types': 7.23.0 /@types/babel__traverse@7.20.1: resolution: {integrity: sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==} dependencies: - '@babel/types': 7.22.19 + '@babel/types': 7.23.0 /@types/body-parser@1.19.4: resolution: {integrity: sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==} @@ -4139,7 +4107,7 @@ packages: '@types/yargs-parser': 21.0.0 dev: true - /@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.51.0)(typescript@5.0.4): + /@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.54.0)(typescript@5.0.4): resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -4151,12 +4119,12 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.8.1 - '@typescript-eslint/parser': 5.62.0(eslint@8.51.0)(typescript@5.0.4) + '@typescript-eslint/parser': 5.62.0(eslint@8.54.0)(typescript@5.0.4) '@typescript-eslint/scope-manager': 5.62.0 - '@typescript-eslint/type-utils': 5.62.0(eslint@8.51.0)(typescript@5.0.4) - '@typescript-eslint/utils': 5.62.0(eslint@8.51.0)(typescript@5.0.4) + '@typescript-eslint/type-utils': 5.62.0(eslint@8.54.0)(typescript@5.0.4) + '@typescript-eslint/utils': 5.62.0(eslint@8.54.0)(typescript@5.0.4) debug: 4.3.4 - eslint: 8.51.0 + eslint: 8.54.0 graphemer: 1.4.0 ignore: 5.2.0 natural-compare-lite: 1.4.0 @@ -4167,7 +4135,7 @@ packages: - supports-color dev: true - /@typescript-eslint/parser@5.62.0(eslint@8.51.0)(typescript@5.0.4): + /@typescript-eslint/parser@5.62.0(eslint@8.54.0)(typescript@5.0.4): resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -4181,7 +4149,7 @@ packages: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.0.4) debug: 4.3.4 - eslint: 8.51.0 + eslint: 8.54.0 typescript: 5.0.4 transitivePeerDependencies: - supports-color @@ -4195,7 +4163,7 @@ packages: '@typescript-eslint/visitor-keys': 5.62.0 dev: true - /@typescript-eslint/type-utils@5.62.0(eslint@8.51.0)(typescript@5.0.4): + /@typescript-eslint/type-utils@5.62.0(eslint@8.54.0)(typescript@5.0.4): resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -4206,9 +4174,9 @@ packages: optional: true dependencies: '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.0.4) - '@typescript-eslint/utils': 5.62.0(eslint@8.51.0)(typescript@5.0.4) + '@typescript-eslint/utils': 5.62.0(eslint@8.54.0)(typescript@5.0.4) debug: 4.3.4 - eslint: 8.51.0 + eslint: 8.54.0 tsutils: 3.21.0(typescript@5.0.4) typescript: 5.0.4 transitivePeerDependencies: @@ -4241,19 +4209,19 @@ packages: - supports-color dev: true - /@typescript-eslint/utils@5.62.0(eslint@8.51.0)(typescript@5.0.4): + /@typescript-eslint/utils@5.62.0(eslint@8.54.0)(typescript@5.0.4): resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.2.0(eslint@8.51.0) + '@eslint-community/eslint-utils': 4.2.0(eslint@8.54.0) '@types/json-schema': 7.0.11 '@types/semver': 7.3.12 '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.0.4) - eslint: 8.51.0 + eslint: 8.54.0 eslint-scope: 5.1.1 semver: 7.5.4 transitivePeerDependencies: @@ -4271,7 +4239,6 @@ packages: /@ungap/structured-clone@1.2.0: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - dev: false /@vitejs/plugin-react@4.1.0(vite@4.5.0): resolution: {integrity: sha512-rM0SqazU9iqPUraQ2JlIvReeaxOoRj6n+PzB1C0cBzIbd8qP336nC39/R9yPi3wVcah7E7j/kdU1uCUqMEU4OQ==} @@ -4462,14 +4429,6 @@ packages: - supports-color dev: true - /aggregate-error@3.1.0: - resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} - engines: {node: '>=8'} - dependencies: - clean-stack: 2.2.0 - indent-string: 4.0.0 - dev: true - /ajv-keywords@3.5.2(ajv@6.12.6): resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} peerDependencies: @@ -4498,6 +4457,13 @@ packages: type-fest: 0.21.3 dev: true + /ansi-escapes@5.0.0: + resolution: {integrity: sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==} + engines: {node: '>=12'} + dependencies: + type-fest: 1.4.0 + dev: true + /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -4642,11 +4608,6 @@ packages: is-shared-array-buffer: 1.0.2 dev: true - /astral-regex@2.0.0: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} - engines: {node: '>=8'} - dev: true - /asynciterator.prototype@1.0.0: resolution: {integrity: sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==} dependencies: @@ -4661,17 +4622,17 @@ packages: engines: {node: '>= 0.4'} dev: true - /babel-jest@29.7.0(@babel/core@7.22.15): + /babel-jest@29.7.0(@babel/core@7.22.20): resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@babel/core': ^7.8.0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 '@jest/transform': 29.7.0 '@types/babel__core': 7.20.1 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.22.15) + babel-preset-jest: 29.6.3(@babel/core@7.22.20) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -4697,40 +4658,40 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/template': 7.22.15 - '@babel/types': 7.22.19 + '@babel/types': 7.23.0 '@types/babel__core': 7.20.1 '@types/babel__traverse': 7.20.1 dev: true - /babel-preset-current-node-syntax@1.0.1(@babel/core@7.22.15): + /babel-preset-current-node-syntax@1.0.1(@babel/core@7.22.20): resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.22.15 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.22.15) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.22.15) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.22.15) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.22.15) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.22.15) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.22.15) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.22.15) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.22.15) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.22.15) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.22.15) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.22.15) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.22.15) - dev: true - - /babel-preset-jest@29.6.3(@babel/core@7.22.15): + '@babel/core': 7.22.20 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.22.20) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.22.20) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.22.20) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.22.20) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.22.20) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.22.20) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.22.20) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.22.20) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.22.20) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.22.20) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.22.20) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.22.20) + dev: true + + /babel-preset-jest@29.6.3(@babel/core@7.22.20): resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.22.15) + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.22.20) dev: true /bail@2.0.2: @@ -4864,6 +4825,11 @@ packages: ansi-styles: 4.3.0 supports-color: 7.2.0 + /chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: true + /char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} @@ -4930,11 +4896,6 @@ packages: resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==} dev: false - /clean-stack@2.2.0: - resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} - engines: {node: '>=6'} - dev: true - /cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -4942,19 +4903,18 @@ packages: restore-cursor: 3.1.0 dev: true + /cli-cursor@4.0.0: + resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + restore-cursor: 4.0.0 + dev: true + /cli-spinners@2.7.0: resolution: {integrity: sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==} engines: {node: '>=6'} dev: true - /cli-truncate@2.1.0: - resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} - engines: {node: '>=8'} - dependencies: - slice-ansi: 3.0.0 - string-width: 4.2.3 - dev: true - /cli-truncate@3.1.0: resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -5013,8 +4973,8 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - /colorette@2.0.19: - resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} + /colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} dev: true /combined-stream@1.0.8: @@ -5031,15 +4991,15 @@ packages: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} dev: false + /commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + dev: true + /commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} dev: false - /commander@9.4.1: - resolution: {integrity: sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==} - engines: {node: ^12.20.0 || >=14} - dev: true - /commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} dev: false @@ -5076,16 +5036,6 @@ packages: is-what: 4.1.7 dev: true - /cosmiconfig@8.1.3: - resolution: {integrity: sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==} - engines: {node: '>=14'} - dependencies: - import-fresh: 3.3.0 - js-yaml: 4.1.0 - parse-json: 5.2.0 - path-type: 4.0.0 - dev: true - /cosmiconfig@8.3.6(typescript@5.0.4): resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} engines: {node: '>=14'} @@ -5100,7 +5050,6 @@ packages: parse-json: 5.2.0 path-type: 4.0.0 typescript: 5.0.4 - dev: false /create-jest@29.7.0(@types/node@18.17.14)(ts-node@10.9.1): resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} @@ -5442,6 +5391,13 @@ packages: webidl-conversions: 7.0.0 dev: true + /dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + dependencies: + no-case: 3.0.4 + tslib: 2.5.0 + dev: true + /dotenv-expand@10.0.0: resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} engines: {node: '>=12'} @@ -5689,13 +5645,13 @@ packages: source-map: 0.6.1 dev: true - /eslint-config-prettier@9.0.0(eslint@8.51.0): + /eslint-config-prettier@9.0.0(eslint@8.54.0): resolution: {integrity: sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==} hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 8.51.0 + eslint: 8.54.0 dev: true /eslint-import-resolver-node@0.3.7: @@ -5708,7 +5664,7 @@ packages: - supports-color dev: true - /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0)(eslint-plugin-import@2.28.1)(eslint@8.51.0): + /eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@5.62.0)(eslint-plugin-import@2.28.1)(eslint@8.54.0): resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -5717,9 +5673,9 @@ packages: dependencies: debug: 4.3.4 enhanced-resolve: 5.15.0 - eslint: 8.51.0 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.6.1)(eslint@8.51.0) - eslint-plugin-import: 2.28.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.51.0) + eslint: 8.54.0 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.6.1)(eslint@8.54.0) + eslint-plugin-import: 2.28.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.54.0) fast-glob: 3.3.1 get-tsconfig: 4.5.0 is-core-module: 2.13.0 @@ -5731,7 +5687,7 @@ packages: - supports-color dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.6.1)(eslint@8.51.0): + /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.6.1)(eslint@8.54.0): resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} engines: {node: '>=4'} peerDependencies: @@ -5752,16 +5708,16 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.62.0(eslint@8.51.0)(typescript@5.0.4) + '@typescript-eslint/parser': 5.62.0(eslint@8.54.0)(typescript@5.0.4) debug: 3.2.7 - eslint: 8.51.0 + eslint: 8.54.0 eslint-import-resolver-node: 0.3.7 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0)(eslint-plugin-import@2.28.1)(eslint@8.51.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@5.62.0)(eslint-plugin-import@2.28.1)(eslint@8.54.0) transitivePeerDependencies: - supports-color dev: true - /eslint-plugin-import@2.28.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.51.0): + /eslint-plugin-import@2.28.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.54.0): resolution: {integrity: sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==} engines: {node: '>=4'} peerDependencies: @@ -5771,16 +5727,16 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.62.0(eslint@8.51.0)(typescript@5.0.4) + '@typescript-eslint/parser': 5.62.0(eslint@8.54.0)(typescript@5.0.4) array-includes: 3.1.6 array.prototype.findlastindex: 1.2.3 array.prototype.flat: 1.3.1 array.prototype.flatmap: 1.3.1 debug: 3.2.7 doctrine: 2.1.0 - eslint: 8.51.0 + eslint: 8.54.0 eslint-import-resolver-node: 0.3.7 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.6.1)(eslint@8.51.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.6.1)(eslint@8.54.0) has: 1.0.3 is-core-module: 2.13.0 is-glob: 4.0.3 @@ -5796,13 +5752,13 @@ packages: - supports-color dev: true - /eslint-plugin-lodash@7.4.0(eslint@8.51.0): + /eslint-plugin-lodash@7.4.0(eslint@8.54.0): resolution: {integrity: sha512-Tl83UwVXqe1OVeBRKUeWcfg6/pCW1GTRObbdnbEJgYwjxp5Q92MEWQaH9+dmzbRt6kvYU1Mp893E79nJiCSM8A==} engines: {node: '>=10'} peerDependencies: eslint: '>=2' dependencies: - eslint: 8.51.0 + eslint: 8.54.0 lodash: 4.17.21 dev: true @@ -5810,7 +5766,7 @@ packages: resolution: {integrity: sha512-wMlL+TVuDhKk1plP+w3L4Hc7+u89vUkrOYq6/0ARjcYqwc9/YaS9uEXNzaqAk+WLoEgakzNL5JgJJw6m4qd5zw==} dev: true - /eslint-plugin-react@7.33.2(eslint@8.51.0): + /eslint-plugin-react@7.33.2(eslint@8.54.0): resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==} engines: {node: '>=4'} peerDependencies: @@ -5821,7 +5777,7 @@ packages: array.prototype.tosorted: 1.1.1 doctrine: 2.1.0 es-iterator-helpers: 1.0.15 - eslint: 8.51.0 + eslint: 8.54.0 estraverse: 5.3.0 jsx-ast-utils: 3.3.3 minimatch: 3.1.2 @@ -5855,18 +5811,19 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint@8.51.0: - resolution: {integrity: sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==} + /eslint@8.54.0: + resolution: {integrity: sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.2.0(eslint@8.51.0) + '@eslint-community/eslint-utils': 4.2.0(eslint@8.54.0) '@eslint-community/regexpp': 4.8.1 - '@eslint/eslintrc': 2.1.2 - '@eslint/js': 8.51.0 - '@humanwhocodes/config-array': 0.11.11 + '@eslint/eslintrc': 2.1.3 + '@eslint/js': 8.54.0 + '@humanwhocodes/config-array': 0.11.13 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.2.0 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 @@ -5950,6 +5907,10 @@ packages: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} dev: false + /eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + dev: true + /events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -5970,18 +5931,18 @@ packages: strip-final-newline: 2.0.0 dev: true - /execa@6.1.0: - resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + /execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} dependencies: cross-spawn: 7.0.3 - get-stream: 6.0.1 - human-signals: 3.0.1 + 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: 3.0.7 + signal-exit: 4.1.0 strip-final-newline: 3.0.0 dev: true @@ -6227,6 +6188,11 @@ packages: engines: {node: '>=10'} dev: true + /get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + dev: true + /get-symbol-description@1.0.0: resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} engines: {node: '>= 0.4'} @@ -6595,9 +6561,9 @@ packages: engines: {node: '>=10.17.0'} dev: true - /human-signals@3.0.1: - resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==} - engines: {node: '>=12.20.0'} + /human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} dev: true /iconv-lite@0.4.24: @@ -7016,7 +6982,7 @@ packages: resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} engines: {node: '>=8'} dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 '@babel/parser': 7.22.16 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 @@ -7029,7 +6995,7 @@ packages: resolution: {integrity: sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw==} engines: {node: '>=10'} dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 '@babel/parser': 7.22.16 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 @@ -7163,11 +7129,11 @@ packages: ts-node: optional: true dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 '@types/node': 18.17.14 - babel-jest: 29.7.0(@babel/core@7.22.15) + babel-jest: 29.7.0(@babel/core@7.22.20) chalk: 4.1.2 ci-info: 3.8.0 deepmerge: 4.3.1 @@ -7426,15 +7392,15 @@ packages: resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 '@babel/generator': 7.22.15 - '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.22.15) - '@babel/plugin-syntax-typescript': 7.22.5(@babel/core@7.22.15) - '@babel/types': 7.22.19 + '@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.22.20) + '@babel/plugin-syntax-typescript': 7.22.5(@babel/core@7.22.20) + '@babel/types': 7.23.0 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.22.15) + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.22.20) chalk: 4.1.2 expect: 29.7.0 graceful-fs: 4.2.11 @@ -7714,53 +7680,43 @@ packages: type-check: 0.4.0 dev: true - /lilconfig@2.0.5: - resolution: {integrity: sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==} + /lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} dev: true /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - /lint-staged@13.0.3: - resolution: {integrity: sha512-9hmrwSCFroTSYLjflGI8Uk+GWAwMB4OlpU4bMJEAT5d/llQwtYKoim4bLOyLCuWFAhWEupE0vkIFqtw/WIsPug==} - engines: {node: ^14.13.1 || >=16.0.0} + /lint-staged@15.1.0: + resolution: {integrity: sha512-ZPKXWHVlL7uwVpy8OZ7YQjYDAuO5X4kMh0XgZvPNxLcCCngd0PO5jKQyy3+s4TL2EnHoIXIzP1422f/l3nZKMw==} + engines: {node: '>=18.12.0'} + hasBin: true dependencies: - cli-truncate: 3.1.0 - colorette: 2.0.19 - commander: 9.4.1 + chalk: 5.3.0 + commander: 11.1.0 debug: 4.3.4 - execa: 6.1.0 - lilconfig: 2.0.5 - listr2: 4.0.5 + execa: 8.0.1 + lilconfig: 2.1.0 + listr2: 7.0.2 micromatch: 4.0.5 - normalize-path: 3.0.0 - object-inspect: 1.12.2 pidtree: 0.6.0 - string-argv: 0.3.1 - yaml: 2.2.2 + string-argv: 0.3.2 + yaml: 2.3.4 transitivePeerDependencies: - - enquirer - supports-color dev: true - /listr2@4.0.5: - resolution: {integrity: sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==} - engines: {node: '>=12'} - peerDependencies: - enquirer: '>= 2.3.0 < 3' - peerDependenciesMeta: - enquirer: - optional: true + /listr2@7.0.2: + resolution: {integrity: sha512-rJysbR9GKIalhTbVL2tYbF2hVyDnrf7pFUZBwjPaMIdadYHmeT+EVi/Bu3qd7ETQPahTotg2WRCatXwRBW554g==} + engines: {node: '>=16.0.0'} dependencies: - cli-truncate: 2.1.0 - colorette: 2.0.19 - log-update: 4.0.0 - p-map: 4.0.0 + cli-truncate: 3.1.0 + colorette: 2.0.20 + eventemitter3: 5.0.1 + log-update: 5.0.1 rfdc: 1.3.0 - rxjs: 7.5.7 - through: 2.3.8 - wrap-ansi: 7.0.0 + wrap-ansi: 8.1.0 dev: true /loader-runner@4.3.0: @@ -7808,14 +7764,15 @@ packages: is-unicode-supported: 0.1.0 dev: true - /log-update@4.0.0: - resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} - engines: {node: '>=10'} + /log-update@5.0.1: + resolution: {integrity: sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: - ansi-escapes: 4.3.2 - cli-cursor: 3.1.0 - slice-ansi: 4.0.0 - wrap-ansi: 6.2.0 + ansi-escapes: 5.0.0 + cli-cursor: 4.0.0 + slice-ansi: 5.0.0 + strip-ansi: 7.0.1 + wrap-ansi: 8.1.0 dev: true /longest-streak@3.1.0: @@ -7828,6 +7785,12 @@ packages: dependencies: js-tokens: 4.0.0 + /lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + dependencies: + tslib: 2.5.0 + dev: true + /lowlight@1.20.0: resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} dependencies: @@ -8302,6 +8265,13 @@ packages: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} dev: false + /no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + dependencies: + lower-case: 2.0.2 + tslib: 2.5.0 + dev: true + /node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} @@ -8352,10 +8322,6 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - /object-inspect@1.12.2: - resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==} - dev: true - /object-inspect@1.13.0: resolution: {integrity: sha512-HQ4J+ic8hKrgIt3mqk6cVOVrW2ozL4KdvHlqpBv9vDYWx9ysAgENAdvy4FoGF+KFdhR7nQTNm5J0ctAeOwn+3g==} @@ -8538,13 +8504,6 @@ packages: dependencies: p-limit: 3.1.0 - /p-map@4.0.0: - resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} - engines: {node: '>=10'} - dependencies: - aggregate-error: 3.1.0 - dev: true - /p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -8637,6 +8596,7 @@ packages: /pidtree@0.6.0: resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} engines: {node: '>=0.10'} + hasBin: true dev: true /pify@4.0.1: @@ -9393,6 +9353,14 @@ packages: signal-exit: 3.0.7 dev: true + /restore-cursor@4.0.0: + resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + dev: true + /reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -9586,7 +9554,6 @@ packages: /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - dev: false /sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -9597,24 +9564,6 @@ packages: engines: {node: '>=8'} dev: true - /slice-ansi@3.0.0: - resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} - engines: {node: '>=8'} - dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 - dev: true - - /slice-ansi@4.0.0: - resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 - dev: true - /slice-ansi@5.0.0: resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} engines: {node: '>=12'} @@ -9623,6 +9572,13 @@ packages: is-fullwidth-code-point: 4.0.0 dev: true + /snake-case@3.0.4: + resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + dependencies: + dot-case: 3.0.4 + tslib: 2.5.0 + dev: true + /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} @@ -9693,8 +9649,8 @@ packages: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} dev: true - /string-argv@0.3.1: - resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} + /string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} dev: true @@ -10005,7 +9961,7 @@ packages: engines: {node: '>=6.10'} dev: false - /ts-jest@29.1.1(@babel/core@7.22.15)(esbuild@0.18.17)(jest@29.7.0)(typescript@5.0.4): + /ts-jest@29.1.1(@babel/core@7.22.20)(esbuild@0.18.17)(jest@29.7.0)(typescript@5.0.4): resolution: {integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -10026,7 +9982,7 @@ packages: esbuild: optional: true dependencies: - '@babel/core': 7.22.15 + '@babel/core': 7.22.20 bs-logger: 0.2.6 esbuild: 0.18.17 fast-json-stable-stringify: 2.1.0 @@ -10126,6 +10082,11 @@ packages: engines: {node: '>=10'} dev: true + /type-fest@1.4.0: + resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} + engines: {node: '>=10'} + dev: true + /type-fest@2.19.0: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} @@ -10409,18 +10370,19 @@ packages: d3-timer: 3.0.1 dev: false - /vite-plugin-svgr@3.2.0(vite@4.5.0): - resolution: {integrity: sha512-Uvq6niTvhqJU6ga78qLKBFJSDvxWhOnyfQSoKpDPMAGxJPo5S3+9hyjExE5YDj6Lpa4uaLkGc1cBgxXov+LjSw==} + /vite-plugin-svgr@4.2.0(typescript@5.0.4)(vite@4.5.0): + resolution: {integrity: sha512-SC7+FfVtNQk7So0XMjrrtLAbEC8qjFPifyD7+fs/E6aaNdVde6umlVVh0QuwDLdOMu7vp5RiGFsB70nj5yo0XA==} peerDependencies: - vite: ^2.6.0 || 3 || 4 + vite: ^2.6.0 || 3 || 4 || 5 dependencies: - '@rollup/pluginutils': 5.0.2 - '@svgr/core': 7.0.0 - '@svgr/plugin-jsx': 7.0.0 + '@rollup/pluginutils': 5.0.5 + '@svgr/core': 8.1.0(typescript@5.0.4) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0) vite: 4.5.0(@types/node@18.17.14)(less@4.2.0) transitivePeerDependencies: - rollup - supports-color + - typescript dev: true /vite@4.5.0(@types/node@18.17.14)(less@4.2.0): @@ -10643,15 +10605,6 @@ packages: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} dev: false - /wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - dev: true - /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -10667,7 +10620,6 @@ packages: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.0.1 - dev: false /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -10723,6 +10675,12 @@ packages: /yaml@2.2.2: resolution: {integrity: sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==} engines: {node: '>= 14'} + dev: false + + /yaml@2.3.4: + resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==} + engines: {node: '>= 14'} + dev: true /yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} diff --git a/coral/src/app/features/components/filters/TeamFilter.test.tsx b/coral/src/app/features/components/filters/TeamFilter.test.tsx index 77c62fba14..440cfb6a9e 100644 --- a/coral/src/app/features/components/filters/TeamFilter.test.tsx +++ b/coral/src/app/features/components/filters/TeamFilter.test.tsx @@ -41,156 +41,315 @@ const WrappedTeamFilter = withFiltersContext({ element: , }); +const WrappedTeamFilterForTeamName = withFiltersContext({ + element: , +}); + const filterLabel = "Filter by team"; describe("TeamFilter.tsx", () => { - describe("renders default view when no query is set", () => { - beforeAll(async () => { - mockGetTeams.mockResolvedValue(mockedTeamsResponse); + describe("handles the teamId per default", () => { + describe("renders default view when no query is set", () => { + beforeAll(async () => { + mockGetTeams.mockResolvedValue(mockedTeamsResponse); + + customRender(, { + memoryRouter: true, + queryClient: true, + }); + await waitForElementToBeRemoved( + screen.getByTestId("select-team-loading") + ); + }); + + afterAll(cleanup); - customRender(, { - memoryRouter: true, - queryClient: true, + it("shows a select element for Team", () => { + const select = screen.getByRole("combobox", { + name: filterLabel, + }); + + expect(select).toBeEnabled(); }); - await waitForElementToBeRemoved( - screen.getByTestId("select-team-loading") - ); - }); - afterAll(cleanup); + it("renders a list of given options for teams plus a option for `All teams`", () => { + mockedTeamsResponse.forEach((team) => { + const option = screen.getByRole("option", { + name: team.teamname, + }); - it("shows a select element for Team", () => { - const select = screen.getByRole("combobox", { - name: filterLabel, + expect(option).toBeEnabled(); + }); + expect(screen.getAllByRole("option")).toHaveLength( + mockedTeamsResponse.length + 1 + ); }); - expect(select).toBeEnabled(); + it("shows `All teams` as the active option one", () => { + const option = screen.getByRole("option", { + selected: true, + }); + expect(option).toHaveAccessibleName("All teams"); + }); }); - it("renders a list of given options for teams plus a option for `All teams`", () => { - mockedTeamsResponse.forEach((team) => { - const option = screen.getByRole("option", { - name: team.teamname, + describe("sets the active team based on a query param", () => { + const optionName = "Ospo"; + const optionId = "1003"; + + beforeEach(async () => { + const routePath = `/topics?teamId=${optionId}`; + + mockGetTeams.mockResolvedValue(mockedTeamsResponse); + + customRender(, { + memoryRouter: true, + queryClient: true, + customRoutePath: routePath, }); + await waitForElementToBeRemoved( + screen.getByTestId("select-team-loading") + ); + }); - expect(option).toBeEnabled(); + afterEach(() => { + jest.resetAllMocks(); + cleanup(); }); - expect(screen.getAllByRole("option")).toHaveLength( - mockedTeamsResponse.length + 1 - ); - }); - it("shows `All teams` as the active option one", () => { - const option = screen.getByRole("option", { - selected: true, + it("shows `Ospo` as the active option one", async () => { + const option = await screen.findByRole("option", { + name: optionName, + selected: true, + }); + expect(option).toBeVisible(); }); - expect(option).toHaveAccessibleName("All teams"); }); - }); - describe("sets the active team based on a query param", () => { - const optionName = "Ospo"; - const optionId = "1003"; + describe("handles user selecting a team", () => { + const optionToSelect = "Ospo"; + const optionId = "1003"; - beforeEach(async () => { - const routePath = `/topics?teamId=${optionId}`; + beforeEach(async () => { + mockGetTeams.mockResolvedValue(mockedTeamsResponse); - mockGetTeams.mockResolvedValue(mockedTeamsResponse); + customRender(, { + queryClient: true, + memoryRouter: true, + }); + await waitForElementToBeRemoved( + screen.getByTestId("select-team-loading") + ); + }); - customRender(, { - memoryRouter: true, - queryClient: true, - customRoutePath: routePath, + afterEach(() => { + jest.resetAllMocks(); + cleanup(); }); - await waitForElementToBeRemoved( - screen.getByTestId("select-team-loading") - ); - }); - afterEach(() => { - jest.resetAllMocks(); - cleanup(); + it("sets the team the user choose as active option", async () => { + const select = screen.getByRole("combobox", { + name: filterLabel, + }); + const option = screen.getByRole("option", { name: optionToSelect }); + + await userEvent.selectOptions(select, option); + + expect(select).toHaveValue(optionId); + expect(select).toHaveDisplayValue(optionToSelect); + }); }); - it("shows `Ospo` as the active option one", async () => { - const option = await screen.findByRole("option", { - name: optionName, - selected: true, + describe("updates the search param to preserve team in url", () => { + const optionToSelect = "DevRel"; + const optionId = "1004"; + + beforeEach(async () => { + mockGetTeams.mockResolvedValue(mockedTeamsResponse); + customRender(, { + queryClient: true, + browserRouter: true, + }); + await waitFor(() => { + expect(screen.getByRole("combobox")).toBeVisible(); + }); + }); + + afterEach(() => { + // resets url to get to clean state again + window.history.pushState({}, "No page title", "/"); + jest.resetAllMocks(); + cleanup(); + }); + + it("shows no search param by default", async () => { + expect(window.location.search).toEqual(""); + }); + + it("sets `teamId=1` and `page=1` as search param when user selected it", async () => { + const select = screen.getByRole("combobox", { + name: filterLabel, + }); + + const option = screen.getByRole("option", { name: optionToSelect }); + + await userEvent.selectOptions(select, option); + + await waitFor(() => { + expect(window.location.search).toEqual(`?teamId=${optionId}&page=1`); + }); }); - expect(option).toBeVisible(); }); }); - describe("handles user selecting a team", () => { - const optionToSelect = "Ospo"; - const optionId = "1003"; + describe("handles the teamName optional instead of id", () => { + describe("renders default view when no query is set", () => { + beforeAll(async () => { + mockGetTeams.mockResolvedValue(mockedTeamsResponse); + + customRender(, { + memoryRouter: true, + queryClient: true, + }); + await waitForElementToBeRemoved( + screen.getByTestId("select-team-loading") + ); + }); + + afterAll(cleanup); - beforeEach(async () => { - mockGetTeams.mockResolvedValue(mockedTeamsResponse); + it("shows a select element for Team", () => { + const select = screen.getByRole("combobox", { + name: filterLabel, + }); - customRender(, { - queryClient: true, - memoryRouter: true, + expect(select).toBeEnabled(); + }); + + it("renders a list of given options for teams plus a option for `All teams`", () => { + mockedTeamsResponse.forEach((team) => { + const option = screen.getByRole("option", { + name: team.teamname, + }); + + expect(option).toBeEnabled(); + }); + expect(screen.getAllByRole("option")).toHaveLength( + mockedTeamsResponse.length + 1 + ); }); - await waitForElementToBeRemoved( - screen.getByTestId("select-team-loading") - ); - }); - afterEach(() => { - jest.resetAllMocks(); - cleanup(); + it("shows `All teams` as the active option one", () => { + const option = screen.getByRole("option", { + selected: true, + }); + expect(option).toHaveAccessibleName("All teams"); + }); }); - it("sets the team the user choose as active option", async () => { - const select = screen.getByRole("combobox", { - name: filterLabel, + describe("sets the active team based on a query param", () => { + const optionName = "Ospo"; + + beforeEach(async () => { + const routePath = `/topics?teamName=${optionName}`; + + mockGetTeams.mockResolvedValue(mockedTeamsResponse); + + customRender(, { + memoryRouter: true, + queryClient: true, + customRoutePath: routePath, + }); + await waitForElementToBeRemoved( + screen.getByTestId("select-team-loading") + ); }); - const option = screen.getByRole("option", { name: optionToSelect }); - await userEvent.selectOptions(select, option); + afterEach(() => { + jest.resetAllMocks(); + cleanup(); + }); - expect(select).toHaveValue(optionId); - expect(select).toHaveDisplayValue(optionToSelect); + it("shows `Ospo` as the active option one", async () => { + const option = await screen.findByRole("option", { + name: optionName, + selected: true, + }); + expect(option).toBeVisible(); + }); }); - }); - describe("updates the search param to preserve team in url", () => { - const optionToSelect = "DevRel"; - const optionId = "1004"; + describe("handles user selecting a team", () => { + const optionToSelect = "Ospo"; + + beforeEach(async () => { + mockGetTeams.mockResolvedValue(mockedTeamsResponse); - beforeEach(async () => { - mockGetTeams.mockResolvedValue(mockedTeamsResponse); - customRender(, { - queryClient: true, - browserRouter: true, + customRender(, { + queryClient: true, + memoryRouter: true, + }); + await waitForElementToBeRemoved( + screen.getByTestId("select-team-loading") + ); }); - await waitFor(() => { - expect(screen.getByRole("combobox")).toBeVisible(); + + afterEach(() => { + jest.resetAllMocks(); + cleanup(); }); - }); - afterEach(() => { - // resets url to get to clean state again - window.history.pushState({}, "No page title", "/"); - jest.resetAllMocks(); - cleanup(); - }); + it("sets the team the user choose as active option", async () => { + const select = screen.getByRole("combobox", { + name: filterLabel, + }); + const option = screen.getByRole("option", { name: optionToSelect }); - it("shows no search param by default", async () => { - expect(window.location.search).toEqual(""); + await userEvent.selectOptions(select, option); + + expect(select).toHaveValue(optionToSelect); + expect(select).toHaveDisplayValue(optionToSelect); + }); }); - it("sets `teamId=1` and `page=1` as search param when user selected it", async () => { - const select = screen.getByRole("combobox", { - name: filterLabel, + describe("updates the search param to preserve team name in url", () => { + const optionToSelect = "DevRel"; + + beforeEach(async () => { + mockGetTeams.mockResolvedValue(mockedTeamsResponse); + customRender(, { + queryClient: true, + browserRouter: true, + }); + await waitFor(() => { + expect(screen.getByRole("combobox")).toBeVisible(); + }); + }); + + afterEach(() => { + // resets url to get to clean state again + window.history.pushState({}, "No page title", "/"); + jest.resetAllMocks(); + cleanup(); }); - const option = screen.getByRole("option", { name: optionToSelect }); + it("shows no search param by default", async () => { + expect(window.location.search).toEqual(""); + }); + + it("sets `teamName=DevRel` and `page=1` as search param when user selected it", async () => { + const select = screen.getByRole("combobox", { + name: filterLabel, + }); - await userEvent.selectOptions(select, option); + const option = screen.getByRole("option", { name: optionToSelect }); - await waitFor(() => { - expect(window.location.search).toEqual(`?teamId=${optionId}&page=1`); + await userEvent.selectOptions(select, option); + + await waitFor(() => { + expect(window.location.search).toEqual( + `?teamName=${optionToSelect}&page=1` + ); + }); }); }); }); diff --git a/coral/src/app/features/components/filters/TeamFilter.tsx b/coral/src/app/features/components/filters/TeamFilter.tsx index 0d55e8151e..a8abfee9e6 100644 --- a/coral/src/app/features/components/filters/TeamFilter.tsx +++ b/coral/src/app/features/components/filters/TeamFilter.tsx @@ -3,14 +3,17 @@ import { useQuery } from "@tanstack/react-query"; import { useFiltersContext } from "src/app/features/components/filters/useFiltersContext"; import { getTeams } from "src/domain/team/team-api"; -function TeamFilter() { - const { data: topicTeams } = useQuery(["get-teams"], { +type TeamFilterProps = { + useTeamName?: boolean; +}; +function TeamFilter({ useTeamName }: TeamFilterProps) { + const { data: teams } = useQuery(["get-teams"], { queryFn: () => getTeams(), }); - const { teamId, setFilterValue } = useFiltersContext(); + const { teamId, teamName, setFilterValue } = useFiltersContext(); - if (!topicTeams) { + if (!teams) { return (
@@ -20,16 +23,22 @@ function TeamFilter() { return ( - setFilterValue({ name: "teamId", value: event.target.value }) - } + value={useTeamName ? teamName : teamId} + onChange={(event) => { + return setFilterValue({ + name: useTeamName ? "teamName" : "teamId", + value: event.target.value, + }); + }} > - {topicTeams.map((team) => ( - ))} diff --git a/coral/src/app/features/components/filters/useFiltersContext.tsx b/coral/src/app/features/components/filters/useFiltersContext.tsx index da0a29a86f..710b17a8d6 100644 --- a/coral/src/app/features/components/filters/useFiltersContext.tsx +++ b/coral/src/app/features/components/filters/useFiltersContext.tsx @@ -13,7 +13,8 @@ type SetFiltersParams = | { name: "teamId"; value: string } | { name: "showOnlyMyRequests"; value: boolean } | { name: "requestType"; value: RequestOperationType } - | { name: "search"; value: string }; + | { name: "search"; value: string } + | { name: "teamName"; value: string }; interface UseFiltersDefaultValues { environment: string; @@ -24,6 +25,7 @@ interface UseFiltersDefaultValues { requestType: RequestOperationType; search: string; paginated: boolean; + teamName: string; } interface UseFiltersReturnedValues @@ -36,6 +38,7 @@ const emptyValues: UseFiltersDefaultValues = { aclType: "ALL", status: "ALL", teamId: "ALL", + teamName: "ALL", showOnlyMyRequests: false, requestType: "ALL", search: "", @@ -73,6 +76,7 @@ const FiltersProvider = ({ initialValues.requestType; const search = searchParams.get("search") ?? initialValues.search; const paginated = initialValues.paginated; + const teamName = searchParams.get("teamName") ?? initialValues.teamName; const setFilterValue = ({ name, value }: SetFiltersParams) => { const parsedValue = typeof value === "boolean" ? String(value) : value; @@ -94,6 +98,7 @@ const FiltersProvider = ({ aclType, status, teamId, + teamName, showOnlyMyRequests, requestType, search, diff --git a/coral/src/app/features/configuration/users/Users.test.tsx b/coral/src/app/features/configuration/users/Users.test.tsx new file mode 100644 index 0000000000..1ddc3ac056 --- /dev/null +++ b/coral/src/app/features/configuration/users/Users.test.tsx @@ -0,0 +1,461 @@ +import { + cleanup, + screen, + waitFor, + waitForElementToBeRemoved, + within, +} from "@testing-library/react"; +import { customRender } from "src/services/test-utils/render-with-wrappers"; +import { KlawApiError } from "src/services/api"; +import { mockIntersectionObserver } from "src/services/test-utils/mock-intersection-observer"; +import { getUserList, User } from "src/domain/user"; +import { Users } from "src/app/features/configuration/users/Users"; +import { userEvent } from "@testing-library/user-event"; +import { getTeams, Team } from "src/domain/team"; + +jest.mock("src/domain/user/user-api"); +jest.mock("src/domain/team/team-api.ts"); + +const mockGetUsers = getUserList as jest.MockedFunction; +const mockGetTeams = getTeams as jest.MockedFunction; + +const userOne: User = { + fullname: "Jean-Luc Picard", + mailid: "jlpicard@ufp.org", + role: "CAPTAIN", + switchTeams: false, + team: "Enterprise", + teamId: 1, + tenantId: 0, + username: "js-picard", +}; + +const userTwo: User = { + fullname: "Chris Pike", + mailid: "cpike@ufp.org", + role: "SUPERADMIN", + switchAllowedTeamIds: [1, 2], + switchAllowedTeamNames: ["Enterprise", "Discovery"], + switchTeams: true, + team: "Discovery", + teamId: 2, + tenantId: 0, + username: "c-pike", +}; + +const mockUsers = [userOne, userTwo]; + +const teamOne: Team = { + app: "", + contactperson: "Picard", + envList: [], + serviceAccounts: {}, + showDeleteTeam: false, + teamId: 1111, + teammail: "", + teamname: "NCC-1701-D", + teamphone: "12345", + tenantId: 0, + tenantName: "UFP", +}; +const teamTwo: Team = { + contactperson: "Pike", + showDeleteTeam: false, + teamId: 2222, + teamname: "NCC-1701", + teamphone: "67890", + tenantId: 0, + tenantName: "default", +}; + +const mockedTeams = [teamOne, teamTwo]; + +describe("Users.tsx", () => { + const user = userEvent.setup(); + + describe("handles loading state", () => { + beforeAll(() => { + mockGetTeams.mockResolvedValue([]); + mockGetUsers.mockResolvedValue({ + currentPage: 1, + totalPages: 1, + entries: [], + }); + customRender(, { queryClient: true, memoryRouter: true }); + }); + + afterAll(() => { + cleanup(); + jest.resetAllMocks(); + }); + + it("shows a loading information", () => { + const loadingAnimation = screen.getByTestId("skeleton-table"); + expect(loadingAnimation).toBeVisible(); + }); + }); + + describe("handles empty state", () => { + beforeAll(async () => { + mockGetTeams.mockResolvedValue([]); + mockGetUsers.mockResolvedValue({ + currentPage: 1, + totalPages: 1, + entries: [], + }); + customRender(, { queryClient: true, memoryRouter: true }); + await waitForElementToBeRemoved(screen.getByTestId("skeleton-table")); + }); + + afterAll(() => { + cleanup(); + jest.resetAllMocks(); + }); + + it("does not render the table", () => { + const table = screen.queryByRole("table"); + expect(table).not.toBeInTheDocument(); + }); + + it("shows information about the empty state", () => { + const heading = screen.getByRole("heading", { name: "No users" }); + expect(heading).toBeVisible(); + }); + }); + + describe("handles error state", () => { + const testError: KlawApiError = { + message: "OH NO 😭", + success: false, + }; + + const originalConsoleError = console.error; + + beforeAll(async () => { + console.error = jest.fn(); + mockGetTeams.mockResolvedValue([]); + mockGetUsers.mockRejectedValue(testError); + customRender(, { queryClient: true, memoryRouter: true }); + await waitForElementToBeRemoved(screen.getByTestId("skeleton-table")); + }); + + afterAll(() => { + cleanup(); + jest.resetAllMocks(); + console.error = originalConsoleError; + }); + + it("does not render the table", () => { + const table = screen.queryByRole("table"); + expect(table).not.toBeInTheDocument(); + + expect(console.error).toHaveBeenCalled(); + }); + + it("shows an error alert", () => { + const error = screen.getByRole("alert"); + + expect(error).toBeVisible(); + expect(error).toHaveTextContent(testError.message); + expect(console.error).toHaveBeenCalledWith(testError); + }); + }); + + describe("handles successful response with one page", () => { + beforeAll(async () => { + mockIntersectionObserver(); + mockGetTeams.mockResolvedValue([]); + mockGetUsers.mockResolvedValue({ + currentPage: 1, + totalPages: 1, + entries: mockUsers, + }); + customRender(, { queryClient: true, memoryRouter: true }); + await waitForElementToBeRemoved(screen.getByTestId("skeleton-table")); + }); + + afterAll(() => { + cleanup(); + jest.resetAllMocks(); + }); + + it("shows a table with user overview", () => { + const table = screen.getByRole("table", { + name: "Users overview, page 1 of 1", + }); + + expect(table).toBeVisible(); + }); + + it("does not render the pagination", () => { + const pagination = screen.queryByRole("navigation", { + name: /Pagination/, + }); + + expect(pagination).not.toBeInTheDocument(); + }); + + it("shows select element for teams with All teams as default", () => { + const select = screen.getByRole("combobox", { name: "Filter by team" }); + + expect(select).toBeEnabled(); + expect(select).toHaveDisplayValue("All teams"); + }); + + mockUsers.forEach((user) => { + it(`renders the user name "${user.username}"`, () => { + const table = screen.getByRole("table", { + name: /Users overview/, + }); + const cell = within(table).getByRole("cell", { + name: user.username, + }); + + expect(cell).toBeVisible(); + }); + }); + }); + + describe("handles successful response with 3 pages", () => { + beforeAll(async () => { + mockIntersectionObserver(); + mockGetTeams.mockResolvedValue([]); + mockGetUsers.mockResolvedValue({ + currentPage: 2, + totalPages: 4, + entries: mockUsers, + }); + customRender(, { queryClient: true, memoryRouter: true }); + await waitForElementToBeRemoved(screen.getByTestId("skeleton-table")); + }); + + afterAll(() => { + cleanup(); + jest.resetAllMocks(); + }); + + it("shows a table with user overview", () => { + const table = screen.getByRole("table", { + name: "Users overview, page 2 of 4", + }); + + expect(table).toBeVisible(); + }); + + it("does the pagination with current and all pages", () => { + const pagination = screen.getByRole("navigation", { + name: "Pagination navigation, you're on page 2 of 4", + }); + + expect(pagination).toBeVisible(); + }); + + mockUsers.forEach((user) => { + it(`renders the user name "${user.username}"`, () => { + const table = screen.getByRole("table", { + name: /Users overview/, + }); + const cell = within(table).getByRole("cell", { + name: user.username, + }); + + expect(cell).toBeVisible(); + }); + }); + }); + + describe("handles user stepping through pagination", () => { + beforeEach(async () => { + mockIntersectionObserver(); + mockGetTeams.mockResolvedValue([]); + mockGetUsers.mockResolvedValue({ + currentPage: 3, + totalPages: 5, + entries: mockUsers, + }); + customRender(, { queryClient: true, memoryRouter: true }); + await waitForElementToBeRemoved(screen.getByTestId("skeleton-table")); + }); + + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + + it("shows page 3 as currently active page and the total page number", () => { + const pagination = screen.getByRole("navigation", { + name: /Pagination/, + }); + + expect(pagination).toHaveAccessibleName( + "Pagination navigation, you're on page 3 of 5" + ); + }); + + it("fetches new data when user clicks on next page", async () => { + const pageTwoButton = screen.getByRole("button", { + name: "Go to next page, page 4", + }); + + await user.click(pageTwoButton); + + expect(mockGetUsers).toHaveBeenNthCalledWith(2, { + pageNo: "4", + }); + }); + }); + + describe("enables user to filter by team", () => { + beforeEach(async () => { + mockIntersectionObserver(); + mockGetTeams.mockResolvedValue(mockedTeams); + mockGetUsers.mockResolvedValue({ + currentPage: 3, + totalPages: 5, + entries: mockUsers, + }); + customRender(, { queryClient: true, memoryRouter: true }); + await waitForElementToBeRemoved(screen.getByTestId("skeleton-table")); + }); + + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + + it("renders a select with all teams fetched from the endpoint", () => { + const select = screen.getByRole("combobox", { + name: "Filter by team", + }); + + const options = within(select).getAllByRole("option"); + + // one option for "All teams" + expect(options).toHaveLength(mockedTeams.length + 1); + expect(options[0]).toHaveValue("ALL"); + expect(options[1]).toHaveValue(mockedTeams[0].teamname); + expect(options[2]).toHaveValue(mockedTeams[1].teamname); + }); + + it("fetches new data when user filters by a team", async () => { + const teamToSelect = mockedTeams[0].teamname; + + const select = screen.getByRole("combobox", { + name: "Filter by team", + }); + + const option = within(select).getByRole("option", { name: teamToSelect }); + + await user.selectOptions(select, option); + + //first call on load + expect(mockGetUsers).toHaveBeenNthCalledWith(2, { + pageNo: "1", + teamName: "NCC-1701-D", + }); + }); + + it("removes teamName as url param when user chooses All teams", async () => { + const teamToSelect = mockedTeams[0].teamname; + const select = screen.getByRole("combobox", { + name: "Filter by team", + }); + const optionOne = within(select).getByRole("option", { + name: teamToSelect, + }); + + await user.selectOptions(select, optionOne); + + //first call on load + expect(mockGetUsers).toHaveBeenNthCalledWith(2, { + pageNo: "1", + teamName: "NCC-1701-D", + }); + + const optionTwo = within(select).getByRole("option", { + name: "All teams", + }); + + await user.selectOptions(select, optionTwo); + + //first call on load, second for the first filter + expect(mockGetUsers).toHaveBeenNthCalledWith(3, { + pageNo: "1", + }); + }); + }); + + describe("enables user to search for user name", () => { + beforeEach(async () => { + mockIntersectionObserver(); + mockGetTeams.mockResolvedValue(mockedTeams); + mockGetUsers.mockResolvedValue({ + currentPage: 3, + totalPages: 5, + entries: mockUsers, + }); + customRender(, { queryClient: true, memoryRouter: true }); + await waitForElementToBeRemoved(screen.getByTestId("skeleton-table")); + }); + + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + + it("renders a search field for username", () => { + const search = screen.getByRole("search", { + name: "Search username", + }); + + expect(search).toBeEnabled(); + expect(search).toHaveAccessibleDescription( + 'Partial match for user name Searching starts automatically with a little delay while typing. Press "Escape" to delete all your input.' + ); + }); + + it("fetches new data when user searches for username", async () => { + const usernameSearch = "myname"; + + const search = screen.getByRole("search", { + name: "Search username", + }); + + await user.type(search, usernameSearch); + + await waitFor(() => { + expect(mockGetUsers).toHaveBeenCalledWith({ + pageNo: "1", + searchUserParam: usernameSearch, + }); + }); + }); + + it("removes username search as url param when user deletes input", async () => { + const usernameSearch = "myname"; + + const search = screen.getByRole("search", { + name: "Search username", + }); + + await user.type(search, usernameSearch); + + await waitFor(() => { + // first call is on load + expect(mockGetUsers).toHaveBeenNthCalledWith(2, { + pageNo: "1", + searchUserParam: usernameSearch, + }); + }); + + await user.clear(search); + + await waitFor(() => { + // first call is on load, second for search + expect(mockGetUsers).toHaveBeenNthCalledWith(3, { + pageNo: "1", + }); + }); + }); + }); +}); diff --git a/coral/src/app/features/configuration/users/Users.tsx b/coral/src/app/features/configuration/users/Users.tsx new file mode 100644 index 0000000000..0862a3e9ae --- /dev/null +++ b/coral/src/app/features/configuration/users/Users.tsx @@ -0,0 +1,85 @@ +import { TableLayout } from "src/app/features/components/layouts/TableLayout"; +import { useQuery } from "@tanstack/react-query"; +import { getUserList } from "src/domain/user"; +import { UsersTable } from "src/app/features/configuration/users/components/UsersTable"; +import { Pagination } from "src/app/components/Pagination"; +import { useSearchParams } from "react-router-dom"; +import { + useFiltersContext, + withFiltersContext, +} from "src/app/features/components/filters/useFiltersContext"; +import TeamFilter from "src/app/features/components/filters/TeamFilter"; +import { SearchFilter } from "src/app/features/components/filters/SearchFilter"; + +function UsersWithoutFilterContext() { + const [searchParams, setSearchParams] = useSearchParams(); + const { teamName, search } = useFiltersContext(); + + const currentPage = searchParams.get("page") + ? Number(searchParams.get("page")) + : 1; + + const { + data: users, + isLoading, + isError, + error, + } = useQuery(["getUserList", currentPage, teamName, search], { + queryFn: () => + getUserList({ + pageNo: String(currentPage), + teamName: teamName === "ALL" ? undefined : teamName, + searchUserParam: search.length === 0 ? undefined : search, + }), + keepPreviousData: true, + }); + + function handleChangePage(page: number) { + searchParams.set("page", page.toString()); + setSearchParams(searchParams); + } + + const pagination = + users && users.totalPages > 1 ? ( + + ) : undefined; + + const table = ( + + ); + + return ( + , + , + ]} + table={table} + pagination={pagination} + isLoading={isLoading} + isErrorLoading={isError} + errorMessage={error} + /> + ); +} + +const Users = withFiltersContext({ + element: , +}); + +export { Users }; diff --git a/coral/src/app/features/configuration/users/components/UsersTable.test.tsx b/coral/src/app/features/configuration/users/components/UsersTable.test.tsx new file mode 100644 index 0000000000..61d0f790b1 --- /dev/null +++ b/coral/src/app/features/configuration/users/components/UsersTable.test.tsx @@ -0,0 +1,173 @@ +import { cleanup, screen, render, within } from "@testing-library/react"; +import { UsersTable } from "src/app/features/configuration/users/components/UsersTable"; +import { mockIntersectionObserver } from "src/services/test-utils/mock-intersection-observer"; +import { User } from "src/domain/user"; + +const userOne: User = { + fullname: "Jean-Luc Picard", + mailid: "jlpicard@ufp.org", + role: "CAPTAIN", + switchTeams: false, + team: "Enterprise", + teamId: 1, + tenantId: 0, + username: "js-picard", +}; + +const userTwo: User = { + fullname: "Chris Pike", + mailid: "cpike@ufp.org", + role: "SUPERADMIN", + switchAllowedTeamIds: [1, 2], + switchAllowedTeamNames: ["Enterprise", "Discovery"], + switchTeams: true, + team: "Discovery", + teamId: 2, + tenantId: 0, + username: "c-pike", +}; + +const mockUsers = [userOne, userTwo]; + +const tableRowHeader = [ + "Username", + "Name", + "Role", + "Team", + "Email ID", + "Switch teams", + "Switch between teams", +]; + +const testLabel = "Users overview, page 1 of 1"; +describe("UsersTable.tsx", () => { + describe("handles empty state", () => { + beforeAll(() => { + render(); + }); + afterAll(cleanup); + + it("show empty state when there is no data", () => { + const heading = screen.getByRole("heading", { name: "No users" }); + expect(heading).toBeVisible(); + }); + + it("show additional information when there is no data", () => { + const text = screen.getByText("There are no users data available."); + expect(text).toBeVisible(); + }); + }); + + describe("shows all users as a table", () => { + beforeAll(() => { + mockIntersectionObserver(); + render(); + }); + + afterAll(cleanup); + + it("renders a users table", async () => { + const table = screen.getByRole("table", { + name: testLabel, + }); + + expect(table).toBeVisible(); + }); + + tableRowHeader.forEach((header) => { + it(`renders a column header for ${header}`, () => { + const table = screen.getByRole("table", { + name: testLabel, + }); + const colHeader = within(table).getByRole("columnheader", { + name: header, + }); + + expect(colHeader).toBeVisible(); + }); + }); + + mockUsers.forEach((user) => { + it(`renders the user name "${user.username}"`, () => { + const table = screen.getByRole("table", { + name: testLabel, + }); + const cell = within(table).getByRole("cell", { + name: user.username, + }); + + expect(cell).toBeVisible(); + }); + + it(`renders the users full name "${user.fullname}"`, () => { + const table = screen.getByRole("table", { + name: testLabel, + }); + const cell = within(table).getByRole("cell", { + name: user.fullname, + }); + + expect(cell).toBeVisible(); + }); + + it(`renders the users team "${user.team}"`, () => { + const table = screen.getByRole("table", { + name: testLabel, + }); + const cell = within(table).getByRole("cell", { + name: user.team, + }); + + expect(cell).toBeVisible(); + }); + + it(`renders the users email ID "${user.mailid}"`, () => { + const table = screen.getByRole("table", { + name: testLabel, + }); + const cell = within(table).getByRole("cell", { + name: user.mailid, + }); + + expect(cell).toBeVisible(); + }); + + it(`renders indicator that user can switch team`, () => { + const table = screen.getByRole("table", { + name: testLabel, + }); + + const row = within(table).getByRole("row", { + name: new RegExp(`${user.username}`, "i"), + }); + const cell = within(row).getAllByRole("cell"); + + const positionOfSwitchTeamRow = tableRowHeader.indexOf("Switch teams"); + expect(cell[positionOfSwitchTeamRow]).toHaveTextContent( + user.switchTeams ? "enabled" : "false" + ); + }); + + if (user.switchAllowedTeamNames) { + it(`renders all teams a user can switch between if its enabled for them`, () => { + const table = screen.getByRole("table", { + name: testLabel, + }); + + const row = within(table).getByRole("row", { + name: new RegExp(`${user.username}`, "i"), + }); + const cell = within(row).getAllByRole("cell"); + + const positionOfTeamsToSwitch = tableRowHeader.indexOf( + "Switch between teams" + ); + + user.switchAllowedTeamNames?.forEach((name) => { + expect(cell[positionOfTeamsToSwitch]).toHaveTextContent(name); + }); + }); + } + }); + }); +}); diff --git a/coral/src/app/features/configuration/users/components/UsersTable.tsx b/coral/src/app/features/configuration/users/components/UsersTable.tsx new file mode 100644 index 0000000000..30295474d1 --- /dev/null +++ b/coral/src/app/features/configuration/users/components/UsersTable.tsx @@ -0,0 +1,116 @@ +import { Box, DataTable, DataTableColumn, EmptyState } from "@aivenio/aquarium"; +import { User } from "src/domain/user"; +import uniqueId from "lodash/uniqueId"; + +type UsersProps = { + users: User[]; + ariaLabel: string; +}; + +interface UsersRow { + id: string; + username: User["username"]; + fullname: User["fullname"]; + role: User["role"]; + team: User["team"]; + mailid: User["mailid"]; + switchTeams: User["switchTeams"]; + switchAllowedTeamNames: User["switchAllowedTeamNames"]; +} + +const UsersTable = ({ users, ariaLabel }: UsersProps) => { + const columns: Array> = [ + { + type: "text", + field: "username", + headerName: "Username", + }, + { + type: "text", + field: "fullname", + headerName: "Name", + }, + { + type: "status", + headerName: "Role", + status: ({ role }) => ({ + status: role === "SUPERADMIN" ? "info" : "neutral", + text: role, + }), + }, + { + type: "text", + field: "team", + headerName: "Team", + }, + { + type: "text", + field: "mailid", + headerName: "Email ID", + }, + { + type: "status", + headerName: "Switch teams", + status: ({ switchTeams }) => ({ + status: switchTeams ? "success" : "neutral", + text: switchTeams ? "enabled" : "false", + }), + }, + { + type: "custom", + width: 200, + headerName: "Switch between teams", + UNSAFE_render: ({ switchAllowedTeamNames }) => { + if (switchAllowedTeamNames) { + return ( + + {switchAllowedTeamNames.map((team, index) => ( +
  • + {team} + {index === switchAllowedTeamNames.length - 1 ? "" : ","} +
  • + ))} +
    + ); + } + }, + }, + ]; + + const rows: UsersRow[] = users.map((user) => { + return { + id: uniqueId(), + username: user.username, + fullname: user.fullname, + role: user.role, + team: user.team, + mailid: user.mailid, + switchTeams: user.switchTeams, + switchAllowedTeamNames: user.switchAllowedTeamNames, + }; + }); + + if (rows.length === 0) { + return ( + + There are no users data available. + + ); + } + + return ( + + ); +}; + +export { UsersTable }; diff --git a/coral/src/app/layout/main-navigation/MainNavigation.test.tsx b/coral/src/app/layout/main-navigation/MainNavigation.test.tsx index 4beeb5149c..a1934b7fc7 100644 --- a/coral/src/app/layout/main-navigation/MainNavigation.test.tsx +++ b/coral/src/app/layout/main-navigation/MainNavigation.test.tsx @@ -189,6 +189,53 @@ describe("MainNavigation.tsx", () => { }); }); + describe("renders links to users behind feature flag", () => { + afterEach(() => { + cleanup(); + jest.resetAllMocks(); + }); + + it("renders a link to the old UI when feature flag is false", async () => { + isFeatureFlagActiveMock.mockReturnValueOnce(false); + customRender(, { + memoryRouter: true, + queryClient: true, + }); + + const button = screen.getByRole("button", { + name: new RegExp("Configuration", "i"), + }); + await userEvent.click(button); + const list = screen.getByRole("list", { + name: `Configuration submenu`, + }); + + const link = within(list).getByRole("link", { name: "Users" }); + expect(link).toBeVisible(); + expect(link).toHaveAttribute("href", "/users"); + }); + + it("renders a link to the users page when feature flag is true", async () => { + isFeatureFlagActiveMock.mockReturnValueOnce(true); + customRender(, { + memoryRouter: true, + queryClient: true, + }); + + const button = screen.getByRole("button", { + name: new RegExp("Configuration", "i"), + }); + await userEvent.click(button); + const list = screen.getByRole("list", { + name: `Configuration submenu`, + }); + + const link = within(list).getByRole("link", { name: "Users" }); + expect(link).toBeVisible(); + expect(link).toHaveAttribute("href", "/configuration/users"); + }); + }); + describe("user can open submenus and see more links", () => { beforeEach(() => { customRender(, { diff --git a/coral/src/app/layout/main-navigation/MainNavigation.tsx b/coral/src/app/layout/main-navigation/MainNavigation.tsx index 2361813ddb..3e014e3016 100644 --- a/coral/src/app/layout/main-navigation/MainNavigation.tsx +++ b/coral/src/app/layout/main-navigation/MainNavigation.tsx @@ -97,7 +97,11 @@ function MainNavigation() { text={"Configuration"} defaultExpanded={pathname.startsWith(Routes.CONFIGURATION)} > - + { + return ( + <> + + + + ); +}; + +export { UsersPage }; diff --git a/coral/src/app/router.tsx b/coral/src/app/router.tsx index ccaf94ca4a..4a8e167958 100644 --- a/coral/src/app/router.tsx +++ b/coral/src/app/router.tsx @@ -56,6 +56,7 @@ import { getRouterBasename } from "src/config"; import { createRouteBehindFeatureFlag } from "src/services/feature-flags/route-utils"; import { FeatureFlag } from "src/services/feature-flags/types"; import { TeamsPage } from "src/app/pages/configuration/teams"; +import { UsersPage } from "src/app/pages/configuration/users"; const routes: Array = [ { @@ -239,6 +240,12 @@ const routes: Array = [ redirectRouteWithoutFeatureFlag: Routes.TOPICS, element: , }), + createRouteBehindFeatureFlag({ + path: Routes.USERS, + featureFlag: FeatureFlag.FEATURE_FLAG_USER_TEAMS, + redirectRouteWithoutFeatureFlag: Routes.TOPICS, + element: , + }), ], }, ], diff --git a/coral/src/app/router_utils.ts b/coral/src/app/router_utils.ts index 4f9888d1a7..f5d5868d18 100644 --- a/coral/src/app/router_utils.ts +++ b/coral/src/app/router_utils.ts @@ -20,6 +20,7 @@ enum Routes { CONFIGURATION = "/configuration", ENVIRONMENTS = "/configuration/environments", TEAMS = "/configuration/teams", + USERS = "/configuration/users", } enum EnvironmentsTabEnum { diff --git a/coral/src/domain/user/index.ts b/coral/src/domain/user/index.ts new file mode 100644 index 0000000000..0a1d3f4617 --- /dev/null +++ b/coral/src/domain/user/index.ts @@ -0,0 +1,5 @@ +import { getUserList } from "src/domain/user/user-api"; +import { User } from "src/domain/user/user-types"; + +export { getUserList }; +export type { User }; diff --git a/coral/src/domain/user/user-api.ts b/coral/src/domain/user/user-api.ts new file mode 100644 index 0000000000..83f3fbf9e5 --- /dev/null +++ b/coral/src/domain/user/user-api.ts @@ -0,0 +1,29 @@ +import { KlawApiRequestQueryParameters, KlawApiResponse } from "types/utils"; +import api, { API_PATHS } from "src/services/api"; +import { transformUserListApiResponse } from "src/domain/user/user-transformer"; +import { UserListApiResponse } from "src/domain/user/user-types"; +import { convertQueryValuesToString } from "src/services/api-helper"; + +async function getUserList( + params: KlawApiRequestQueryParameters<"showUsers"> +): Promise { + const queryParams = convertQueryValuesToString({ + ...params, + ...(params?.teamName && { teamName: params.teamName }), + ...(params?.searchUserParam && { searchUserParam: params.searchUserParam }), + }); + + return api + .get>( + API_PATHS.showUsers, + new URLSearchParams(queryParams) + ) + .then((response) => + transformUserListApiResponse({ + apiResponse: response, + currentPage: Number(params?.pageNo) || 1, + }) + ); +} + +export { getUserList }; diff --git a/coral/src/domain/user/user-transformer.ts b/coral/src/domain/user/user-transformer.ts new file mode 100644 index 0000000000..3e8bcc8c33 --- /dev/null +++ b/coral/src/domain/user/user-transformer.ts @@ -0,0 +1,26 @@ +import { KlawApiResponse } from "types/utils"; +import { UserListApiResponse } from "src/domain/user/user-types"; + +function transformUserListApiResponse({ + apiResponse, + currentPage, +}: { + apiResponse: KlawApiResponse<"showUsers">; + currentPage: number; +}): UserListApiResponse { + if (apiResponse.length === 0) { + return { + totalPages: 0, + currentPage, + entries: [], + }; + } + + return { + totalPages: Number(apiResponse[0].totalNoPages), + currentPage, + entries: apiResponse, + }; +} + +export { transformUserListApiResponse }; diff --git a/coral/src/domain/user/user-types.ts b/coral/src/domain/user/user-types.ts new file mode 100644 index 0000000000..f88b5a402e --- /dev/null +++ b/coral/src/domain/user/user-types.ts @@ -0,0 +1,7 @@ +import { KlawApiModel, Paginated, ResolveIntersectionTypes } from "types/utils"; + +type UserListApiResponse = ResolveIntersectionTypes>; + +type User = KlawApiModel<"UserInfoModelResponse">; + +export type { User, UserListApiResponse }; diff --git a/coral/vite.config.ts b/coral/vite.config.ts index 491060fe1c..141d48c556 100644 --- a/coral/vite.config.ts +++ b/coral/vite.config.ts @@ -125,6 +125,7 @@ export default defineConfig(({ mode }) => { const environment = loadEnv(mode, process.cwd(), ""); const usesNodeProxy = mode === "local-api"; + console.log("mode", mode); return { plugins: getPlugins(environment), define: {