refactor: replace old modals with new dialog component
This commit is contained in:
parent
67b9c43ae1
commit
e9590f8806
19 changed files with 470 additions and 277 deletions
|
@ -1,5 +1,5 @@
|
|||
module.exports = {
|
||||
plugins: ['@typescript-eslint', 'import', 'react', 'jest', 'jsdoc', 'import'],
|
||||
plugins: ['@typescript-eslint', 'import', 'react', 'jest', 'jsdoc'],
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'next/core-web-vitals',
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
"dependencies": {
|
||||
"@hookform/resolvers": "^2.9.10",
|
||||
"@prisma/client": "^4.11.0",
|
||||
"@radix-ui/react-dialog": "^1.0.3",
|
||||
"@radix-ui/react-switch": "^1.0.2",
|
||||
"@radix-ui/react-tabs": "^1.0.3",
|
||||
"@runtipi/postgres-migrations": "^5.3.0",
|
||||
"@tabler/core": "1.0.0-beta17",
|
||||
|
|
236
pnpm-lock.yaml
generated
236
pnpm-lock.yaml
generated
|
@ -7,6 +7,12 @@ dependencies:
|
|||
'@prisma/client':
|
||||
specifier: ^4.11.0
|
||||
version: 4.11.0(prisma@4.11.0)
|
||||
'@radix-ui/react-dialog':
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-switch':
|
||||
specifier: ^1.0.2
|
||||
version: 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-tabs':
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3(react-dom@18.2.0)(react@18.2.0)
|
||||
|
@ -1558,6 +1564,33 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-dialog@1.0.3(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-owNhq36kNPqC2/a+zJRioPg6HHnTn5B/sh/NjTY8r4W9g1L5VJlrzZIVcBr7R9Mg8iLjVmh6MGgMlfoVf/WO/A==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
'@radix-ui/primitive': 1.0.0
|
||||
'@radix-ui/react-compose-refs': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-dismissable-layer': 1.0.3(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-focus-guards': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-focus-scope': 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-id': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-portal': 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-presence': 1.0.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-slot': 1.0.1(react@18.2.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.0.0(react@18.2.0)
|
||||
aria-hidden: 1.2.3
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
react-remove-scroll: 2.5.5(@types/react@18.0.28)(react@18.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-direction@1.0.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==}
|
||||
peerDependencies:
|
||||
|
@ -1567,6 +1600,45 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-dismissable-layer@1.0.3(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-nXZOvFjOuHS1ovumntGV7NNoLaEp9JEvTht3MBjP44NSW5hUKj/8OnfN3+8WmB+CEhN44XaGhpHoSsUIEl5P7Q==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
'@radix-ui/primitive': 1.0.0
|
||||
'@radix-ui/react-compose-refs': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-use-escape-keydown': 1.0.2(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-focus-guards@1.0.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-focus-scope@1.0.2(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-spwXlNTfeIprt+kaEWE/qYuYT3ZAqJiAGjN/JgdvgVDTu8yc+HuX+WOWXrKliKnLnwck0F6JDkqIERncnih+4A==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
'@radix-ui/react-compose-refs': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-id@1.0.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==}
|
||||
peerDependencies:
|
||||
|
@ -1577,6 +1649,18 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-portal@1.0.2(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-swu32idoCW7KA2VEiUZGBSu9nB6qwGdV6k6HYhUoOo3M1FFpD+VgLzUqtt3mwL1ssz7r2x8MggpLSQach2Xy/Q==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
'@radix-ui/react-primitive': 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-presence@1.0.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==}
|
||||
peerDependencies:
|
||||
|
@ -1632,6 +1716,24 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-switch@1.0.2(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-BcG/LKehxt36NXG0wPnoCitIfSMtU9Xo7BmythYA1PAMLtsMvW7kALfBzmduQoHTWcKr0AVcFyh0gChBUp9TiQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
'@radix-ui/primitive': 1.0.0
|
||||
'@radix-ui/react-compose-refs': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-context': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-primitive': 1.0.2(react-dom@18.2.0)(react@18.2.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-use-previous': 1.0.0(react@18.2.0)
|
||||
'@radix-ui/react-use-size': 1.0.0(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-tabs@1.0.3(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-4CkF/Rx1GcrusI/JZ1Rvyx4okGUs6wEenWA0RG/N+CwkRhTy7t54y7BLsWUXrAz/GRbBfHQg/Odfs/RoW0CiRA==}
|
||||
peerDependencies:
|
||||
|
@ -1670,6 +1772,16 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-use-escape-keydown@1.0.2(react@18.2.0):
|
||||
resolution: {integrity: sha512-DXGim3x74WgUv+iMNCF+cAo8xUHHeqvjx8zs7trKf+FkQKPQXLk2sX7Gx1ysH7Q76xCpZuxIJE7HLPxRE+Q+GA==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
'@radix-ui/react-use-callback-ref': 1.0.0(react@18.2.0)
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-use-layout-effect@1.0.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==}
|
||||
peerDependencies:
|
||||
|
@ -1679,6 +1791,25 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-use-previous@1.0.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-RG2K8z/K7InnOKpq6YLDmT49HGjNmrK+fr82UCVKT2sW0GYfVnYp4wZWBooT/EYfQ5faA9uIjvsuMMhH61rheg==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@radix-ui/react-use-size@1.0.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-imZ3aYcoYCKhhgNpkNDh/aTiU05qw9hX+HHI1QDBTyIlcFjgeFlKKySNGMwTp7nYFLQg/j0VA2FmCY4WPDDHMg==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.13
|
||||
'@radix-ui/react-use-layout-effect': 1.0.0(react@18.2.0)
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@redis/bloom@1.2.0(@redis/client@1.5.6):
|
||||
resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==}
|
||||
peerDependencies:
|
||||
|
@ -2616,6 +2747,13 @@ packages:
|
|||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
dev: true
|
||||
|
||||
/aria-hidden@1.2.3:
|
||||
resolution: {integrity: sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/aria-query@5.1.3:
|
||||
resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==}
|
||||
dependencies:
|
||||
|
@ -3370,6 +3508,10 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/detect-node-es@1.1.0:
|
||||
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
|
||||
dev: false
|
||||
|
||||
/diff-sequences@29.4.3:
|
||||
resolution: {integrity: sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
|
@ -4401,6 +4543,11 @@ packages:
|
|||
has: 1.0.3
|
||||
has-symbols: 1.0.3
|
||||
|
||||
/get-nonce@1.0.1:
|
||||
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/get-package-type@0.1.0:
|
||||
resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
@ -4735,6 +4882,12 @@ packages:
|
|||
side-channel: 1.0.4
|
||||
dev: true
|
||||
|
||||
/invariant@2.2.4:
|
||||
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
dev: false
|
||||
|
||||
/ipaddr.js@1.9.1:
|
||||
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
@ -7059,6 +7212,41 @@ packages:
|
|||
- supports-color
|
||||
dev: false
|
||||
|
||||
/react-remove-scroll-bar@2.3.4(@types/react@18.0.28)(react@18.2.0):
|
||||
resolution: {integrity: sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.0.28
|
||||
react: 18.2.0
|
||||
react-style-singleton: 2.2.1(@types/react@18.0.28)(react@18.2.0)
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/react-remove-scroll@2.5.5(@types/react@18.0.28)(react@18.2.0):
|
||||
resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.0.28
|
||||
react: 18.2.0
|
||||
react-remove-scroll-bar: 2.3.4(@types/react@18.0.28)(react@18.2.0)
|
||||
react-style-singleton: 2.2.1(@types/react@18.0.28)(react@18.2.0)
|
||||
tslib: 2.5.0
|
||||
use-callback-ref: 1.3.0(@types/react@18.0.28)(react@18.2.0)
|
||||
use-sidecar: 1.1.2(@types/react@18.0.28)(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/react-select@5.7.0(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-lJGiMxCa3cqnUr2Jjtg9YHsaytiZqeNOKeibv6WF5zbK/fPegZ1hg3y/9P1RZVLhqBTs0PfqQLKuAACednYGhQ==}
|
||||
peerDependencies:
|
||||
|
@ -7088,6 +7276,23 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/react-style-singleton@2.2.1(@types/react@18.0.28)(react@18.2.0):
|
||||
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.0.28
|
||||
get-nonce: 1.0.1
|
||||
invariant: 2.2.4
|
||||
react: 18.2.0
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/react-tooltip@4.5.1(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Zo+CSFUGXar1uV+bgXFFDe7VeS2iByeIp5rTgTcc2HqtuOS5D76QapejNNfx320MCY91TlhTQat36KGFTqgcvw==}
|
||||
engines: {npm: '>=6.13'}
|
||||
|
@ -8107,6 +8312,21 @@ packages:
|
|||
requires-port: 1.0.0
|
||||
dev: true
|
||||
|
||||
/use-callback-ref@1.3.0(@types/react@18.0.28)(react@18.2.0):
|
||||
resolution: {integrity: sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.0.28
|
||||
react: 18.2.0
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/use-isomorphic-layout-effect@1.1.2(@types/react@18.0.28)(react@18.2.0):
|
||||
resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==}
|
||||
peerDependencies:
|
||||
|
@ -8120,6 +8340,22 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/use-sidecar@1.1.2(@types/react@18.0.28)(react@18.2.0):
|
||||
resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/react': 18.0.28
|
||||
detect-node-es: 1.1.0
|
||||
react: 18.2.0
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/use-sync-external-store@1.2.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
|
||||
peerDependencies:
|
||||
|
|
|
@ -13,70 +13,91 @@ describe('Test: AppActions', () => {
|
|||
exposable: [],
|
||||
} as unknown as AppInfo;
|
||||
|
||||
it('should render the correct buttons when app status is stopped', () => {
|
||||
// Arrange
|
||||
it('should call the callbacks when buttons are clicked', () => {
|
||||
// arrange
|
||||
const onStart = jest.fn();
|
||||
const onRemove = jest.fn();
|
||||
// @ts-expect-error
|
||||
const { getByText } = render(<AppActions status="stopped" info={app} onStart={onStart} onUninstall={onRemove} />);
|
||||
render(<AppActions status="stopped" info={app} onStart={onStart} onUninstall={onRemove} />);
|
||||
|
||||
// Act
|
||||
fireEvent.click(getByText('Start'));
|
||||
fireEvent.click(getByText('Remove'));
|
||||
// act
|
||||
const startButton = screen.getByRole('button', { name: 'Start' });
|
||||
fireEvent.click(startButton);
|
||||
const removeButton = screen.getByText('Remove');
|
||||
fireEvent.click(removeButton);
|
||||
|
||||
// Assert
|
||||
expect(getByText('Start')).toBeInTheDocument();
|
||||
expect(getByText('Remove')).toBeInTheDocument();
|
||||
// assert
|
||||
expect(onStart).toHaveBeenCalled();
|
||||
expect(onRemove).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should render the correct buttons when app status is running', () => {
|
||||
// arrange
|
||||
// @ts-expect-error
|
||||
const { getByText } = render(<AppActions status="running" info={app} />);
|
||||
expect(getByText('Stop')).toBeInTheDocument();
|
||||
expect(getByText('Open')).toBeInTheDocument();
|
||||
expect(getByText('Settings')).toBeInTheDocument();
|
||||
render(<AppActions status="running" info={app} />);
|
||||
|
||||
// assert
|
||||
expect(screen.getByRole('button', { name: 'Stop' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Open' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Settings' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the correct buttons when app status is starting', () => {
|
||||
// arrange
|
||||
// @ts-expect-error
|
||||
render(<AppActions status="starting" info={app} />);
|
||||
expect(screen.getByText('Cancel')).toBeInTheDocument();
|
||||
|
||||
// assert
|
||||
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
|
||||
expect(screen.getByTestId('action-button-loading')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the correct buttons when app status is stopping', () => {
|
||||
// arrange
|
||||
// @ts-expect-error
|
||||
render(<AppActions status="stopping" info={app} />);
|
||||
expect(screen.getByText('Cancel')).toBeInTheDocument();
|
||||
|
||||
// assert
|
||||
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
|
||||
expect(screen.getByTestId('action-button-loading')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the correct buttons when app status is removing', () => {
|
||||
// arrange
|
||||
// @ts-expect-error
|
||||
render(<AppActions status="uninstalling" info={app} />);
|
||||
expect(screen.getByText('Cancel')).toBeInTheDocument();
|
||||
|
||||
// assert
|
||||
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
|
||||
expect(screen.getByTestId('action-button-loading')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the correct buttons when app status is installing', () => {
|
||||
// arrange
|
||||
// @ts-ignore
|
||||
render(<AppActions status="installing" info={app} />);
|
||||
expect(screen.getByText('Cancel')).toBeInTheDocument();
|
||||
|
||||
// assert
|
||||
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
|
||||
expect(screen.getByTestId('action-button-loading')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the correct buttons when app status is updating', () => {
|
||||
// arrange
|
||||
// @ts-expect-error
|
||||
render(<AppActions status="updating" info={app} />);
|
||||
expect(screen.getByText('Cancel')).toBeInTheDocument();
|
||||
|
||||
// assert
|
||||
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
|
||||
expect(screen.getByTestId('action-button-loading')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the correct buttons when app status is missing', () => {
|
||||
// arrange
|
||||
// @ts-expect-error
|
||||
render(<AppActions status="missing" info={app} />);
|
||||
expect(screen.getByText('Install')).toBeInTheDocument();
|
||||
|
||||
// assert
|
||||
expect(screen.getByRole('button', { name: 'Install' })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -32,8 +32,10 @@ interface BtnProps {
|
|||
const ActionButton: React.FC<BtnProps> = (props) => {
|
||||
const { IconComponent, onClick, title, loading, color, width = 140 } = props;
|
||||
|
||||
const testId = loading ? 'action-button-loading' : undefined;
|
||||
|
||||
return (
|
||||
<Button loading={loading} data-testid={`action-button-${title?.toLowerCase()}`} onClick={onClick} width={width} className={clsx('me-2 px-4 mt-2', [`btn-${color}`])}>
|
||||
<Button loading={loading} data-testid={testId} onClick={onClick} width={width} className={clsx('me-2 px-4 mt-2', [`btn-${color}`])}>
|
||||
{title}
|
||||
{IconComponent && <IconComponent className="ms-1" size={14} />}
|
||||
</Button>
|
||||
|
|
|
@ -83,10 +83,8 @@ export const InstallForm: React.FC<IProps> = ({ formFields, onSubmit, initalValu
|
|||
}
|
||||
};
|
||||
|
||||
const name = initalValues ? 'update' : 'install';
|
||||
|
||||
return (
|
||||
<form data-testid={`${name}-form`} className="flex flex-col" onSubmit={handleSubmit(validate)}>
|
||||
<form className="flex flex-col" onSubmit={handleSubmit(validate)}>
|
||||
{formFields.filter(typeFilter).map(renderField)}
|
||||
{exposable && renderExposeForm()}
|
||||
<Button loading={loading} type="submit" className="btn-success">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader } from '@/components/ui/Dialog';
|
||||
import { InstallForm } from '../InstallForm';
|
||||
import { Modal, ModalBody, ModalHeader } from '../../../../components/ui/Modal';
|
||||
import { AppInfo } from '../../../../core/types';
|
||||
import { FormValues } from '../InstallForm/InstallForm';
|
||||
|
||||
|
@ -12,12 +12,14 @@ interface IProps {
|
|||
}
|
||||
|
||||
export const InstallModal: React.FC<IProps> = ({ info, isOpen, onClose, onSubmit }) => (
|
||||
<Modal onClose={onClose} isOpen={isOpen}>
|
||||
<ModalHeader>
|
||||
<h5 className="modal-title">Install {info.name}</h5>
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<InstallForm onSubmit={onSubmit} formFields={info.form_fields} exposable={info.exposable} />
|
||||
</ModalBody>
|
||||
</Modal>
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<h5 className="modal-title">Install {info.name}</h5>
|
||||
</DialogHeader>
|
||||
<DialogDescription>
|
||||
<InstallForm onSubmit={onSubmit} formFields={info.form_fields} exposable={info.exposable} />
|
||||
</DialogDescription>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader } from '@/components/ui/Dialog';
|
||||
import { Button } from '../../../components/ui/Button';
|
||||
import { Modal, ModalBody, ModalFooter, ModalHeader } from '../../../components/ui/Modal';
|
||||
import { AppInfo } from '../../../core/types';
|
||||
|
||||
interface IProps {
|
||||
|
@ -11,17 +11,19 @@ interface IProps {
|
|||
}
|
||||
|
||||
export const StopModal: React.FC<IProps> = ({ info, isOpen, onClose, onConfirm }) => (
|
||||
<Modal size="sm" onClose={onClose} isOpen={isOpen}>
|
||||
<ModalHeader>
|
||||
<h5 className="modal-title">Stop {info.name} ?</h5>
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="text-muted">All data will be retained</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button data-testid="modal-stop-button" onClick={onConfirm} className="btn-danger">
|
||||
Stop
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent size="sm">
|
||||
<DialogHeader>
|
||||
<h5 className="modal-title">Stop {info.name} ?</h5>
|
||||
</DialogHeader>
|
||||
<DialogDescription>
|
||||
<div className="text-muted">All data will be retained</div>
|
||||
</DialogDescription>
|
||||
<DialogFooter>
|
||||
<Button onClick={onConfirm} className="btn-danger">
|
||||
Stop
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { IconAlertTriangle } from '@tabler/icons-react';
|
||||
import React from 'react';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader } from '@/components/ui/Dialog';
|
||||
import { Button } from '../../../components/ui/Button';
|
||||
import { Modal, ModalBody, ModalFooter, ModalHeader } from '../../../components/ui/Modal';
|
||||
import { AppInfo } from '../../../core/types';
|
||||
|
||||
interface IProps {
|
||||
|
@ -12,19 +12,21 @@ interface IProps {
|
|||
}
|
||||
|
||||
export const UninstallModal: React.FC<IProps> = ({ info, isOpen, onClose, onConfirm }) => (
|
||||
<Modal size="sm" type="danger" onClose={onClose} isOpen={isOpen}>
|
||||
<ModalHeader>
|
||||
<h5 className="modal-title">Uninstall {info.name} ?</h5>
|
||||
</ModalHeader>
|
||||
<ModalBody className="text-center py-4">
|
||||
<IconAlertTriangle className="icon mb-2 text-danger icon-lg" />
|
||||
<h3>Are you sure?</h3>
|
||||
<div className="text-muted">All data for this app will be lost.</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button onClick={onConfirm} className="btn-danger">
|
||||
Uninstall
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent type="danger" size="sm">
|
||||
<DialogHeader>
|
||||
<h5 className="modal-title">Uninstall {info.name} ?</h5>
|
||||
</DialogHeader>
|
||||
<DialogDescription className="text-center py-4">
|
||||
<IconAlertTriangle className="icon mb-2 text-danger icon-lg" />
|
||||
<h3>Are you sure?</h3>
|
||||
<div className="text-muted">All data for this app will be lost.</div>
|
||||
</DialogDescription>
|
||||
<DialogFooter>
|
||||
<Button onClick={onConfirm} className="btn-danger">
|
||||
Uninstall
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
|
|
@ -7,40 +7,40 @@ describe('UpdateModal', () => {
|
|||
const newVersion = '1.2.3';
|
||||
|
||||
it('renders with the correct title and version number', () => {
|
||||
// Arrange
|
||||
// arrange
|
||||
render(<UpdateModal info={app} newVersion={newVersion} isOpen onClose={jest.fn()} onConfirm={jest.fn()} />);
|
||||
|
||||
// Assert
|
||||
// assert
|
||||
expect(screen.getByText(`Update ${app.name} ?`)).toBeInTheDocument();
|
||||
expect(screen.getByText(`${newVersion}`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render when isOpen is false', () => {
|
||||
// Arrange
|
||||
// arrange
|
||||
render(<UpdateModal info={app} newVersion={newVersion} isOpen={false} onClose={jest.fn()} onConfirm={jest.fn()} />);
|
||||
const modal = screen.queryByTestId('modal');
|
||||
|
||||
// Assert (modal should have style display: none)
|
||||
expect(modal).toHaveStyle('display: none');
|
||||
// assert
|
||||
expect(modal).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onClose when the close button is clicked', () => {
|
||||
// Arrange
|
||||
// arrange
|
||||
const onClose = jest.fn();
|
||||
render(<UpdateModal info={app} newVersion={newVersion} isOpen onClose={onClose} onConfirm={jest.fn()} />);
|
||||
|
||||
// Act
|
||||
// act
|
||||
const closeButton = screen.getByTestId('modal-close-button');
|
||||
fireEvent.click(closeButton);
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls onConfirm when the update button is clicked', () => {
|
||||
// Arrange
|
||||
// arrange
|
||||
const onConfirm = jest.fn();
|
||||
render(<UpdateModal info={app} newVersion={newVersion} isOpen onClose={jest.fn()} onConfirm={onConfirm} />);
|
||||
|
||||
// Act
|
||||
// act
|
||||
const updateButton = screen.getByText('Update');
|
||||
fireEvent.click(updateButton);
|
||||
expect(onConfirm).toHaveBeenCalled();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader } from '@/components/ui/Dialog';
|
||||
import { Button } from '../../../../components/ui/Button';
|
||||
import { Modal, ModalBody, ModalFooter, ModalHeader } from '../../../../components/ui/Modal';
|
||||
import { AppInfo } from '../../../../core/types';
|
||||
|
||||
interface IProps {
|
||||
|
@ -12,20 +12,22 @@ interface IProps {
|
|||
}
|
||||
|
||||
export const UpdateModal: React.FC<IProps> = ({ info, newVersion, isOpen, onClose, onConfirm }) => (
|
||||
<Modal size="sm" onClose={onClose} isOpen={isOpen}>
|
||||
<ModalHeader>
|
||||
<h5 className="modal-title">Update {info.name} ?</h5>
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="text-muted">
|
||||
Update app to latest verion : <b>{newVersion}</b> ?<br />
|
||||
This will reset your custom configuration (e.g. changes in docker-compose.yml)
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button data-testid="modal-update-button" onClick={onConfirm} className="btn-success">
|
||||
Update
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent size="sm">
|
||||
<DialogHeader>
|
||||
<h5 className="modal-title">Update {info.name} ?</h5>
|
||||
</DialogHeader>
|
||||
<DialogDescription>
|
||||
<div className="text-muted">
|
||||
Update app to latest verion : <b>{newVersion}</b> ?<br />
|
||||
This will reset your custom configuration (e.g. changes in docker-compose.yml)
|
||||
</div>
|
||||
</DialogDescription>
|
||||
<DialogFooter>
|
||||
<Button onClick={onConfirm} className="btn-success">
|
||||
Update
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader } from '@/components/ui/Dialog';
|
||||
import { InstallForm } from './InstallForm';
|
||||
import { Modal, ModalBody, ModalHeader } from '../../../components/ui/Modal';
|
||||
import { AppInfo } from '../../../core/types';
|
||||
import { FormValues } from './InstallForm/InstallForm';
|
||||
|
||||
|
@ -15,12 +15,14 @@ interface IProps {
|
|||
}
|
||||
|
||||
export const UpdateSettingsModal: React.FC<IProps> = ({ info, config, isOpen, onClose, onSubmit, exposed, domain }) => (
|
||||
<Modal onClose={onClose} isOpen={isOpen}>
|
||||
<ModalHeader>
|
||||
<h5 className="modal-title">Update {info.name} config</h5>
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<InstallForm onSubmit={onSubmit} formFields={info.form_fields} exposable={info.exposable} initalValues={{ ...config, exposed, domain }} />
|
||||
</ModalBody>
|
||||
</Modal>
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<h5 className="modal-title">Update {info.name} config</h5>
|
||||
</DialogHeader>
|
||||
<DialogDescription>
|
||||
<InstallForm onSubmit={onSubmit} formFields={info.form_fields} exposable={info.exposable} initalValues={{ ...config, exposed, domain }} />
|
||||
</DialogDescription>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
|
|
@ -23,7 +23,7 @@ describe('Test: AppDetailsContainer', () => {
|
|||
render(<AppDetailsContainer app={app} />);
|
||||
|
||||
// Assert
|
||||
expect(screen.getByTestId('action-button-update')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Update' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display install button when app is not installed', async () => {
|
||||
|
@ -33,7 +33,7 @@ describe('Test: AppDetailsContainer', () => {
|
|||
render(<AppDetailsContainer app={app} />);
|
||||
|
||||
// Assert
|
||||
expect(screen.getByTestId('action-button-install')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Install' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display uninstall and start button when app is stopped', async () => {
|
||||
|
@ -43,8 +43,8 @@ describe('Test: AppDetailsContainer', () => {
|
|||
render(<AppDetailsContainer app={app} />);
|
||||
|
||||
// Assert
|
||||
expect(screen.getByTestId('action-button-remove')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('action-button-start')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Remove' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Start' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display stop, open and settings buttons when app is running', async () => {
|
||||
|
@ -53,9 +53,9 @@ describe('Test: AppDetailsContainer', () => {
|
|||
render(<AppDetailsContainer app={app} />);
|
||||
|
||||
// Assert
|
||||
expect(screen.getByTestId('action-button-stop')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('action-button-open')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('action-button-settings')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Stop' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Open' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Settings' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not display update button when update is not available', async () => {
|
||||
|
@ -64,7 +64,7 @@ describe('Test: AppDetailsContainer', () => {
|
|||
render(<AppDetailsContainer app={app} />);
|
||||
|
||||
// Assert
|
||||
expect(screen.queryByTestId('action-button-update')).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: 'Update' })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not display open button when app has no_gui set to true', async () => {
|
||||
|
@ -73,7 +73,7 @@ describe('Test: AppDetailsContainer', () => {
|
|||
render(<AppDetailsContainer app={app} />);
|
||||
|
||||
// Assert
|
||||
expect(screen.queryByTestId('action-button-open')).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: 'Open' })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -85,7 +85,7 @@ describe('Test: AppDetailsContainer', () => {
|
|||
render(<AppDetailsContainer app={app} />);
|
||||
|
||||
// Act
|
||||
const openButton = screen.getByTestId('action-button-open');
|
||||
const openButton = screen.getByRole('button', { name: 'Open' });
|
||||
openButton.click();
|
||||
|
||||
// Assert
|
||||
|
@ -99,7 +99,7 @@ describe('Test: AppDetailsContainer', () => {
|
|||
render(<AppDetailsContainer app={app} />);
|
||||
|
||||
// Act
|
||||
const openButton = screen.getByTestId('action-button-open');
|
||||
const openButton = screen.getByRole('button', { name: 'Open' });
|
||||
openButton.click();
|
||||
|
||||
// Assert
|
||||
|
@ -114,10 +114,12 @@ describe('Test: AppDetailsContainer', () => {
|
|||
server.use(getTRPCMock({ path: ['app', 'installApp'], type: 'mutation', response: app }));
|
||||
const { result } = renderHook(() => useToastStore());
|
||||
render(<AppDetailsContainer app={app} />);
|
||||
const openModalButton = screen.getByRole('button', { name: 'Install' });
|
||||
fireEvent.click(openModalButton);
|
||||
|
||||
// Act
|
||||
const installForm = screen.getByTestId('install-form');
|
||||
fireEvent.submit(installForm);
|
||||
const installButton = screen.getByRole('button', { name: 'Install' });
|
||||
fireEvent.click(installButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.toasts).toHaveLength(1);
|
||||
|
@ -139,10 +141,12 @@ describe('Test: AppDetailsContainer', () => {
|
|||
|
||||
const app = createAppEntity({ overrides: { status: 'missing' } });
|
||||
render(<AppDetailsContainer app={app} />);
|
||||
const openModalButton = screen.getByRole('button', { name: 'Install' });
|
||||
fireEvent.click(openModalButton);
|
||||
|
||||
// Act
|
||||
const installForm = screen.getByTestId('install-form');
|
||||
fireEvent.submit(installForm);
|
||||
const installButton = screen.getByRole('button', { name: 'Install' });
|
||||
fireEvent.click(installButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.toasts).toHaveLength(1);
|
||||
|
@ -150,24 +154,6 @@ describe('Test: AppDetailsContainer', () => {
|
|||
expect(result.current.toasts[0].status).toEqual('error');
|
||||
});
|
||||
});
|
||||
|
||||
// Skipping because trpc.useContext is not working in tests
|
||||
it.skip('should put the app in installing state when install mutation is called', async () => {
|
||||
// Arrange
|
||||
const { result } = renderHook(() => useToastStore());
|
||||
const app = createAppEntity({ overrides: { status: 'missing' } });
|
||||
server.use(getTRPCMock({ path: ['app', 'installApp'], type: 'mutation', response: app, delay: 100 }));
|
||||
render(<AppDetailsContainer app={app} />);
|
||||
|
||||
// Act
|
||||
const installForm = screen.getByTestId('install-form');
|
||||
fireEvent.submit(installForm);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('installing')).toBeInTheDocument();
|
||||
expect(result.current.toasts).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test: Update app', () => {
|
||||
|
@ -177,11 +163,11 @@ describe('Test: AppDetailsContainer', () => {
|
|||
server.use(getTRPCMock({ path: ['app', 'updateApp'], type: 'mutation', response: app }));
|
||||
const { result } = renderHook(() => useToastStore());
|
||||
render(<AppDetailsContainer app={app} />);
|
||||
const openModalButton = screen.getByRole('button', { name: 'Update' });
|
||||
fireEvent.click(openModalButton);
|
||||
|
||||
// Act
|
||||
const updateButton = screen.getByTestId('action-button-update');
|
||||
updateButton.click();
|
||||
const modalUpdateButton = screen.getByTestId('modal-update-button');
|
||||
const modalUpdateButton = screen.getByRole('button', { name: 'Update' });
|
||||
modalUpdateButton.click();
|
||||
|
||||
await waitFor(() => {
|
||||
|
@ -197,11 +183,11 @@ describe('Test: AppDetailsContainer', () => {
|
|||
server.use(getTRPCMockError({ path: ['app', 'updateApp'], type: 'mutation', message: 'my big error' }));
|
||||
const app = createAppEntity({ overrides: { version: 2, latestVersion: 3 }, overridesInfo: { tipi_version: 3 } });
|
||||
render(<AppDetailsContainer app={app} />);
|
||||
const openModalButton = screen.getByRole('button', { name: 'Update' });
|
||||
fireEvent.click(openModalButton);
|
||||
|
||||
// Act
|
||||
const updateButton = screen.getByTestId('action-button-update');
|
||||
updateButton.click();
|
||||
const modalUpdateButton = screen.getByTestId('modal-update-button');
|
||||
const modalUpdateButton = screen.getByRole('button', { name: 'Update' });
|
||||
modalUpdateButton.click();
|
||||
|
||||
// Assert
|
||||
|
@ -211,26 +197,6 @@ describe('Test: AppDetailsContainer', () => {
|
|||
expect(result.current.toasts[0].status).toEqual('error');
|
||||
});
|
||||
});
|
||||
|
||||
// Skipping because trpc.useContext is not working in tests
|
||||
it.skip('should put the app in updating state when update mutation is called', async () => {
|
||||
// Arrange
|
||||
const { result } = renderHook(() => useToastStore());
|
||||
const app = createAppEntity({ overrides: { version: 2 }, overridesInfo: { tipi_version: 3 } });
|
||||
server.use(getTRPCMock({ path: ['app', 'updateApp'], type: 'mutation', response: app, delay: 100 }));
|
||||
render(<AppDetailsContainer app={app} />);
|
||||
|
||||
// Act
|
||||
const updateButton = screen.getByTestId('action-button-update');
|
||||
updateButton.click();
|
||||
const modalUpdateButton = screen.getByTestId('modal-update-button');
|
||||
modalUpdateButton.click();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('updating')).toBeInTheDocument();
|
||||
expect(result.current.toasts).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test: Uninstall app', () => {
|
||||
|
@ -240,11 +206,11 @@ describe('Test: AppDetailsContainer', () => {
|
|||
server.use(getTRPCMock({ path: ['app', 'uninstallApp'], type: 'mutation', response: { id: app.id, config: {}, status: 'missing' } }));
|
||||
const { result } = renderHook(() => useToastStore());
|
||||
render(<AppDetailsContainer app={app} />);
|
||||
const openModalButton = screen.getByRole('button', { name: 'Remove' });
|
||||
fireEvent.click(openModalButton);
|
||||
|
||||
// Act
|
||||
const uninstallButton = screen.getByTestId('action-button-remove');
|
||||
uninstallButton.click();
|
||||
const modalUninstallButton = screen.getByText('Uninstall');
|
||||
const modalUninstallButton = screen.getByRole('button', { name: 'Uninstall' });
|
||||
modalUninstallButton.click();
|
||||
|
||||
// Assert
|
||||
|
@ -261,11 +227,11 @@ describe('Test: AppDetailsContainer', () => {
|
|||
server.use(getTRPCMockError({ path: ['app', 'uninstallApp'], type: 'mutation', message: 'my big error' }));
|
||||
const app = createAppEntity({ status: 'stopped' });
|
||||
render(<AppDetailsContainer app={app} />);
|
||||
const openModalButton = screen.getByRole('button', { name: 'Remove' });
|
||||
fireEvent.click(openModalButton);
|
||||
|
||||
// Act
|
||||
const uninstallButton = screen.getByTestId('action-button-remove');
|
||||
uninstallButton.click();
|
||||
const modalUninstallButton = screen.getByText('Uninstall');
|
||||
const modalUninstallButton = screen.getByRole('button', { name: 'Uninstall' });
|
||||
modalUninstallButton.click();
|
||||
|
||||
// Assert
|
||||
|
@ -275,27 +241,6 @@ describe('Test: AppDetailsContainer', () => {
|
|||
expect(result.current.toasts[0].status).toEqual('error');
|
||||
});
|
||||
});
|
||||
|
||||
// Skipping because trpc.useContext is not working in tests
|
||||
it.skip('should put the app in uninstalling state when uninstall mutation is called', async () => {
|
||||
// Arrange
|
||||
const { result } = renderHook(() => useToastStore());
|
||||
const app = createAppEntity({ status: 'stopped' });
|
||||
server.use(getTRPCMock({ path: ['app', 'uninstallApp'], type: 'mutation', response: { id: app.id, config: {}, status: 'missing' }, delay: 100 }));
|
||||
render(<AppDetailsContainer app={app} />);
|
||||
|
||||
// Act
|
||||
const uninstallButton = screen.getByTestId('action-button-remove');
|
||||
uninstallButton.click();
|
||||
const modalUninstallButton = screen.getByText('Uninstall');
|
||||
modalUninstallButton.click();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('uninstalling')).toBeInTheDocument();
|
||||
expect(screen.queryByText('installing')).not.toBeInTheDocument();
|
||||
expect(result.current.toasts).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test: Start app', () => {
|
||||
|
@ -307,7 +252,7 @@ describe('Test: AppDetailsContainer', () => {
|
|||
render(<AppDetailsContainer app={app} />);
|
||||
|
||||
// Act
|
||||
const startButton = screen.getByTestId('action-button-start');
|
||||
const startButton = screen.getByRole('button', { name: 'Start' });
|
||||
startButton.click();
|
||||
|
||||
// Assert
|
||||
|
@ -326,7 +271,7 @@ describe('Test: AppDetailsContainer', () => {
|
|||
render(<AppDetailsContainer app={app} />);
|
||||
|
||||
// Act
|
||||
const startButton = screen.getByTestId('action-button-start');
|
||||
const startButton = screen.getByRole('button', { name: 'Start' });
|
||||
startButton.click();
|
||||
|
||||
// Assert
|
||||
|
@ -336,24 +281,6 @@ describe('Test: AppDetailsContainer', () => {
|
|||
expect(result.current.toasts[0].status).toEqual('error');
|
||||
});
|
||||
});
|
||||
|
||||
// Skipping because trpc.useContext is not working in tests
|
||||
it.skip('should put the app in starting state when start mutation is called', async () => {
|
||||
// Arrange
|
||||
const { result } = renderHook(() => useToastStore());
|
||||
const app = createAppEntity({ status: 'stopped' });
|
||||
server.use(getTRPCMock({ path: ['app', 'startApp'], type: 'mutation', response: app, delay: 100 }));
|
||||
render(<AppDetailsContainer app={app} />);
|
||||
|
||||
// Act
|
||||
const startButton = screen.getByTestId('action-button-start');
|
||||
startButton.click();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('starting')).toBeInTheDocument();
|
||||
expect(result.current.toasts).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test: Stop app', () => {
|
||||
|
@ -363,11 +290,11 @@ describe('Test: AppDetailsContainer', () => {
|
|||
server.use(getTRPCMock({ path: ['app', 'stopApp'], type: 'mutation', response: app }));
|
||||
const { result } = renderHook(() => useToastStore());
|
||||
render(<AppDetailsContainer app={app} />);
|
||||
const openModalButton = screen.getByRole('button', { name: 'Stop' });
|
||||
fireEvent.click(openModalButton);
|
||||
|
||||
// Act
|
||||
const stopButton = screen.getByTestId('action-button-stop');
|
||||
stopButton.click();
|
||||
const modalStopButton = screen.getByTestId('modal-stop-button');
|
||||
const modalStopButton = screen.getByRole('button', { name: 'Stop' });
|
||||
modalStopButton.click();
|
||||
|
||||
// Assert
|
||||
|
@ -384,11 +311,11 @@ describe('Test: AppDetailsContainer', () => {
|
|||
server.use(getTRPCMockError({ path: ['app', 'stopApp'], type: 'mutation', message: 'my big error' }));
|
||||
const app = createAppEntity({ status: 'running' });
|
||||
render(<AppDetailsContainer app={app} />);
|
||||
const openModalButton = screen.getByRole('button', { name: 'Stop' });
|
||||
fireEvent.click(openModalButton);
|
||||
|
||||
// Act
|
||||
const stopButton = screen.getByTestId('action-button-stop');
|
||||
stopButton.click();
|
||||
const modalStopButton = screen.getByTestId('modal-stop-button');
|
||||
const modalStopButton = screen.getByRole('button', { name: 'Stop' });
|
||||
modalStopButton.click();
|
||||
|
||||
// Assert
|
||||
|
@ -398,26 +325,6 @@ describe('Test: AppDetailsContainer', () => {
|
|||
expect(result.current.toasts[0].status).toEqual('error');
|
||||
});
|
||||
});
|
||||
|
||||
// Skipping because trpc.useContext is not working in tests
|
||||
it.skip('should put the app in stopping state when stop mutation is called', async () => {
|
||||
// Arrange
|
||||
const { result } = renderHook(() => useToastStore());
|
||||
const app = createAppEntity({ status: 'running' });
|
||||
server.use(getTRPCMock({ path: ['app', 'stopApp'], type: 'mutation', response: app }));
|
||||
render(<AppDetailsContainer app={app} />);
|
||||
|
||||
// Act
|
||||
const stopButton = screen.getByTestId('action-button-stop');
|
||||
stopButton.click();
|
||||
const modalStopButton = screen.getByTestId('modal-stop-button');
|
||||
modalStopButton.click();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('stopping')).toBeInTheDocument();
|
||||
expect(result.current.toasts).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test: Update app config', () => {
|
||||
|
@ -427,12 +334,12 @@ describe('Test: AppDetailsContainer', () => {
|
|||
server.use(getTRPCMock({ path: ['app', 'updateAppConfig'], type: 'mutation', response: app }));
|
||||
const { result } = renderHook(() => useToastStore());
|
||||
render(<AppDetailsContainer app={app} />);
|
||||
const openModalButton = screen.getByRole('button', { name: 'Settings' });
|
||||
fireEvent.click(openModalButton);
|
||||
|
||||
// Act
|
||||
const configButton = screen.getByTestId('action-button-settings');
|
||||
const configButton = screen.getByRole('button', { name: 'Update' });
|
||||
configButton.click();
|
||||
const modalConfigButton = screen.getAllByText('Update');
|
||||
modalConfigButton[1]?.click();
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
|
@ -448,12 +355,12 @@ describe('Test: AppDetailsContainer', () => {
|
|||
server.use(getTRPCMockError({ path: ['app', 'updateAppConfig'], type: 'mutation', message: 'my big error' }));
|
||||
const app = createAppEntity({ status: 'running', overridesInfo: { exposable: true } });
|
||||
render(<AppDetailsContainer app={app} />);
|
||||
const openModalButton = screen.getByRole('button', { name: 'Settings' });
|
||||
fireEvent.click(openModalButton);
|
||||
|
||||
// Act
|
||||
const configButton = screen.getByTestId('action-button-settings');
|
||||
const configButton = screen.getByRole('button', { name: 'Update' });
|
||||
configButton.click();
|
||||
const modalConfigButton = screen.getAllByText('Update');
|
||||
modalConfigButton[1]?.click();
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader } from '@/components/ui/Dialog';
|
||||
import { Button } from '../../../../components/ui/Button';
|
||||
import { Modal, ModalBody, ModalFooter, ModalHeader } from '../../../../components/ui/Modal';
|
||||
|
||||
interface IProps {
|
||||
isOpen: boolean;
|
||||
|
@ -10,17 +10,19 @@ interface IProps {
|
|||
}
|
||||
|
||||
export const RestartModal: React.FC<IProps> = ({ isOpen, onClose, onConfirm, loading }) => (
|
||||
<Modal size="sm" onClose={onClose} isOpen={isOpen}>
|
||||
<ModalHeader>
|
||||
<h5 className="modal-title">Restart Tipi</h5>
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="text-muted">Would you like to restart your Tipi server?</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button data-testid="settings-modal-restart-button" onClick={onConfirm} className="btn-danger" loading={loading}>
|
||||
Restart
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent type="danger" size="sm">
|
||||
<DialogHeader>
|
||||
<h5 className="modal-title">Restart Tipi</h5>
|
||||
</DialogHeader>
|
||||
<DialogDescription>
|
||||
<div className="text-muted">Would you like to restart your Tipi server?</div>
|
||||
</DialogDescription>
|
||||
<DialogFooter>
|
||||
<Button onClick={onConfirm} className="btn-danger" loading={loading}>
|
||||
Restart
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
|
|
@ -83,7 +83,7 @@ export const SettingsForm = (props: IProps) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<form data-testid="settings-form" className="flex flex-col" onSubmit={handleSubmit(validate)}>
|
||||
<form className="flex flex-col" onSubmit={handleSubmit(validate)}>
|
||||
<h2 className="text-2xl font-bold">General settings</h2>
|
||||
<p className="mb-4">This will update your settings.json file. Make sure you know what you are doing before updating these values.</p>
|
||||
<div className="mb-3">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Button } from '../../../../components/ui/Button';
|
||||
import { Modal, ModalBody, ModalFooter, ModalHeader } from '../../../../components/ui/Modal';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader } from '../../../../components/ui/Dialog';
|
||||
|
||||
interface IProps {
|
||||
isOpen: boolean;
|
||||
|
@ -10,17 +10,19 @@ interface IProps {
|
|||
}
|
||||
|
||||
export const UpdateModal: React.FC<IProps> = ({ isOpen, onClose, onConfirm, loading }) => (
|
||||
<Modal size="sm" onClose={onClose} isOpen={isOpen}>
|
||||
<ModalHeader>
|
||||
<h5 className="modal-title">Update Tipi</h5>
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<div className="text-muted">Would you like to update Tipi to the latest version?</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button onClick={onConfirm} className="btn-success" loading={loading}>
|
||||
Update
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent size="sm">
|
||||
<DialogHeader>
|
||||
<h5 className="modal-title">Update Tipi</h5>
|
||||
</DialogHeader>
|
||||
<DialogDescription>
|
||||
<div className="text-muted">Would you like to update Tipi to the latest version?</div>
|
||||
</DialogDescription>
|
||||
<DialogFooter>
|
||||
<Button onClick={onConfirm} className="btn-success" loading={loading}>
|
||||
Update
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
|
|
@ -9,7 +9,7 @@ describe('Test: GeneralActions', () => {
|
|||
it('should render without error', () => {
|
||||
render(<GeneralActions />);
|
||||
|
||||
expect(screen.getByText('Update')).toBeInTheDocument();
|
||||
expect(screen.getByText('Actions')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show toast if update mutation fails', async () => {
|
||||
|
@ -21,10 +21,12 @@ describe('Test: GeneralActions', () => {
|
|||
await waitFor(() => {
|
||||
expect(screen.getByText('Update to 2.0.0')).toBeInTheDocument();
|
||||
});
|
||||
const updateButton = screen.getByText('Update');
|
||||
const updateButton = screen.getByRole('button', { name: /Update/i });
|
||||
fireEvent.click(updateButton);
|
||||
|
||||
// act
|
||||
fireEvent.click(updateButton);
|
||||
const updateButtonModal = screen.getByRole('button', { name: /Update/i });
|
||||
fireEvent.click(updateButtonModal);
|
||||
|
||||
// assert
|
||||
await waitFor(() => {
|
||||
|
@ -42,12 +44,14 @@ describe('Test: GeneralActions', () => {
|
|||
server.use(getTRPCMock({ path: ['system', 'update'], response: true }));
|
||||
render(<GeneralActions />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Update to 2.0.0')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Update to 2.0.0' })).toBeInTheDocument();
|
||||
});
|
||||
const updateButton = screen.getByText('Update');
|
||||
const updateButton = screen.getByRole('button', { name: /Update to 2.0.0/i });
|
||||
fireEvent.click(updateButton);
|
||||
|
||||
// act
|
||||
fireEvent.click(updateButton);
|
||||
const updateButtonModal = screen.getByRole('button', { name: /Update/i });
|
||||
fireEvent.click(updateButtonModal);
|
||||
|
||||
// assert
|
||||
await waitFor(() => {
|
||||
|
@ -60,12 +64,12 @@ describe('Test: GeneralActions', () => {
|
|||
const { result } = renderHook(() => useToastStore());
|
||||
server.use(getTRPCMockError({ path: ['system', 'restart'], type: 'mutation', status: 500, message: 'Something went wrong' }));
|
||||
render(<GeneralActions />);
|
||||
|
||||
// Find button near the top of the page
|
||||
const restartButton = screen.getByTestId('settings-modal-restart-button');
|
||||
const restartButton = screen.getByRole('button', { name: /Restart/i });
|
||||
|
||||
// act
|
||||
fireEvent.click(restartButton);
|
||||
const restartButtonModal = screen.getByRole('button', { name: /Restart/i });
|
||||
fireEvent.click(restartButtonModal);
|
||||
|
||||
// assert
|
||||
await waitFor(() => {
|
||||
|
@ -83,10 +87,12 @@ describe('Test: GeneralActions', () => {
|
|||
render(<GeneralActions />);
|
||||
|
||||
// Find button near the top of the page
|
||||
const restartButton = screen.getByTestId('settings-modal-restart-button');
|
||||
const restartButton = screen.getByRole('button', { name: /Restart/i });
|
||||
|
||||
// act
|
||||
fireEvent.click(restartButton);
|
||||
const restartButtonModal = screen.getByRole('button', { name: /Restart/i });
|
||||
fireEvent.click(restartButtonModal);
|
||||
|
||||
// assert
|
||||
await waitFor(() => {
|
||||
|
|
|
@ -68,7 +68,7 @@ export const GeneralActions = () => {
|
|||
);
|
||||
};
|
||||
return (
|
||||
<div className="col d-flex flex-column">
|
||||
<>
|
||||
<div className="card-body">
|
||||
<h2 className="mb-4">Actions</h2>
|
||||
<h3 className="card-title mt-4">Version {versionQuery.data?.current}</h3>
|
||||
|
@ -82,6 +82,6 @@ export const GeneralActions = () => {
|
|||
</div>
|
||||
<RestartModal isOpen={restartDisclosure.isOpen} onClose={restartDisclosure.close} onConfirm={() => restart.mutate()} loading={loading} />
|
||||
<UpdateModal isOpen={updateDisclosure.isOpen} onClose={updateDisclosure.close} onConfirm={() => update.mutate()} loading={loading} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -15,6 +15,12 @@ jest.mock('remark-gfm', () => () => ({}));
|
|||
|
||||
console.error = jest.fn();
|
||||
|
||||
class ResizeObserver {
|
||||
observe() {}
|
||||
|
||||
unobserve() {}
|
||||
}
|
||||
|
||||
// Mock localStorage
|
||||
const localStorageMock = (() => {
|
||||
let store: Record<string, string> = {};
|
||||
|
@ -35,6 +41,7 @@ const localStorageMock = (() => {
|
|||
})();
|
||||
|
||||
Object.defineProperty(window, 'localStorage', { value: localStorageMock });
|
||||
Object.defineProperty(window, 'ResizeObserver', { value: ResizeObserver });
|
||||
|
||||
beforeAll(() => {
|
||||
// Enable the mocking in tests.
|
||||
|
|
Loading…
Add table
Reference in a new issue