Repository: joinmarket-webui/jam
Pull request #698 (2023-11-26 01:31:39)

dev: ability to reset fee values in dev mode
Ability to reset fee values in dev mode.

Some fee properties (`max_cj_fee_abs` and `max_cj_fee_rel`) do not have default values if JM is installed from scratch.
You can now reset those fee values if you want to test what the UI looks like, when a user does not have these values configured.

Misc:
- also added the "dev" badge to the test toggle on the scheduler page

## How to test
- Start the app in dev mode with `npm run dev:start`
- Go to settings -> Click "Adjust fee limits"
- Click the "Reset form values" button (notice how the form becomes invalid)
- Click "Save" - form should not be able to be submitted
- Toggle (disable) "Enable form validation"
- Now click "Save" again - config will be updated
- Visit "Send" or "Sweep" page and verify the alert message saying fee config values are missing

## :camera_flash: 
<img src="https://github.com/joinmarket-webui/jam/assets/3358649/3e945414-f91b-496e-818c-19b7b5359e0c" width=450 />
<br />
<br />
<br />
<img src="https://github.com/joinmarket-webui/jam/assets/3358649/38f2a6c3-11c6-4fb5-8d82-72b0c681f979" width=450 />

(This message was there all along.. as dev you just never really saw it)







Patch:
diff --git a/package-lock.json b/package-lock.json
index 4dc7d77..983f776 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -37,8 +37,8 @@
         "conventional-changelog": "^3.1.25",
         "http-proxy-middleware": "^2.0.6",
         "husky": "^8.0.1",
-        "jest-watch-typeahead": "^0.6.5",
-        "jest-websocket-mock": "^2.4.0",
+        "jest-watch-typeahead": "^2.2.2",
+        "jest-websocket-mock": "^2.5.0",
         "lint-staged": "^13.0.3",
         "prettier": "^3.0.3",
         "react-scripts": "^5.0.1",
@@ -50,9 +50,9 @@
       }
     },
     "node_modules/@adobe/css-tools": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.0.1.tgz",
-      "integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==",
+      "version": "4.3.1",
+      "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz",
+      "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==",
       "dev": true
     },
     "node_modules/@ampproject/remapping": {
@@ -3241,17 +3241,23 @@
       }
     },
     "node_modules/@jest/schemas": {
-      "version": "29.0.0",
-      "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz",
-      "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==",
+      "version": "29.6.3",
+      "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+      "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
       "dev": true,
       "dependencies": {
-        "@sinclair/typebox": "^0.24.1"
+        "@sinclair/typebox": "^0.27.8"
       },
       "engines": {
         "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
       }
     },
+    "node_modules/@jest/schemas/node_modules/@sinclair/typebox": {
+      "version": "0.27.8",
+      "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
+      "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
+      "dev": true
+    },
     "node_modules/@jest/source-map": {
       "version": "27.5.1",
       "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz",
@@ -12984,18 +12990,18 @@
       }
     },
     "node_modules/jest-message-util": {
-      "version": "29.2.1",
-      "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.2.1.tgz",
-      "integrity": "sha512-Dx5nEjw9V8C1/Yj10S/8ivA8F439VS8vTq1L7hEgwHFn9ovSKNpYW/kwNh7UglaEgXO42XxzKJB+2x0nSglFVw==",
+      "version": "29.7.0",
+      "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz",
+      "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==",
       "dev": true,
       "dependencies": {
         "@babel/code-frame": "^7.12.13",
-        "@jest/types": "^29.2.1",
+        "@jest/types": "^29.6.3",
         "@types/stack-utils": "^2.0.0",
         "chalk": "^4.0.0",
         "graceful-fs": "^4.2.9",
         "micromatch": "^4.0.4",
-        "pretty-format": "^29.2.1",
+        "pretty-format": "^29.7.0",
         "slash": "^3.0.0",
         "stack-utils": "^2.0.3"
       },
@@ -13004,12 +13010,12 @@
       }
     },
     "node_modules/jest-message-util/node_modules/@jest/types": {
-      "version": "29.2.1",
-      "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.2.1.tgz",
-      "integrity": "sha512-O/QNDQODLnINEPAI0cl9U6zUIDXEWXt6IC1o2N2QENuos7hlGUIthlKyV4p6ki3TvXFX071blj8HUhgLGquPjw==",
+      "version": "29.6.3",
+      "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
+      "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
       "dev": true,
       "dependencies": {
-        "@jest/schemas": "^29.0.0",
+        "@jest/schemas": "^29.6.3",
         "@types/istanbul-lib-coverage": "^2.0.0",
         "@types/istanbul-reports": "^3.0.0",
         "@types/node": "*",
@@ -13021,9 +13027,9 @@
       }
     },
     "node_modules/jest-message-util/node_modules/@types/yargs": {
-      "version": "17.0.13",
-      "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz",
-      "integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==",
+      "version": "17.0.32",
+      "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz",
+      "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==",
       "dev": true,
       "dependencies": {
         "@types/yargs-parser": "*"
@@ -13088,12 +13094,12 @@
       }
     },
     "node_modules/jest-message-util/node_modules/pretty-format": {
-      "version": "29.2.1",
-      "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz",
-      "integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==",
+      "version": "29.7.0",
+      "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+      "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
       "dev": true,
       "dependencies": {
-        "@jest/schemas": "^29.0.0",
+        "@jest/schemas": "^29.6.3",
         "ansi-styles": "^5.0.0",
         "react-is": "^18.0.0"
       },
@@ -13803,12 +13809,12 @@
       }
     },
     "node_modules/jest-util": {
-      "version": "29.2.1",
-      "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.2.1.tgz",
-      "integrity": "sha512-P5VWDj25r7kj7kl4pN2rG/RN2c1TLfYYYZYULnS/35nFDjBai+hBeo3MDrYZS7p6IoY3YHZnt2vq4L6mKnLk0g==",
+      "version": "29.7.0",
+      "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
+      "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
       "dev": true,
       "dependencies": {
-        "@jest/types": "^29.2.1",
+        "@jest/types": "^29.6.3",
         "@types/node": "*",
         "chalk": "^4.0.0",
         "ci-info": "^3.2.0",
@@ -13820,12 +13826,12 @@
       }
     },
     "node_modules/jest-util/node_modules/@jest/types": {
-      "version": "29.2.1",
-      "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.2.1.tgz",
-      "integrity": "sha512-O/QNDQODLnINEPAI0cl9U6zUIDXEWXt6IC1o2N2QENuos7hlGUIthlKyV4p6ki3TvXFX071blj8HUhgLGquPjw==",
+      "version": "29.6.3",
+      "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
+      "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
       "dev": true,
       "dependencies": {
-        "@jest/schemas": "^29.0.0",
+        "@jest/schemas": "^29.6.3",
         "@types/istanbul-lib-coverage": "^2.0.0",
         "@types/istanbul-reports": "^3.0.0",
         "@types/node": "*",
@@ -13837,9 +13843,9 @@
       }
     },
     "node_modules/jest-util/node_modules/@types/yargs": {
-      "version": "17.0.13",
-      "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz",
-      "integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==",
+      "version": "17.0.32",
+      "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz",
+      "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==",
       "dev": true,
       "dependencies": {
         "@types/yargs-parser": "*"
@@ -14024,24 +14030,150 @@
       }
     },
     "node_modules/jest-watch-typeahead": {
-      "version": "0.6.5",
-      "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-0.6.5.tgz",
-      "integrity": "sha512-GIbV6h37/isatMDtqZlA8Q5vC6T3w+5qdvtF+3LIkPc58zEWzbKmTHvlUIp3wvBm400RzrQWcVPcsAJqKWu7XQ==",
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-2.2.2.tgz",
+      "integrity": "sha512-+QgOFW4o5Xlgd6jGS5X37i08tuuXNW8X0CV9WNFi+3n8ExCIP+E1melYhvYLjv5fE6D0yyzk74vsSO8I6GqtvQ==",
       "dev": true,
       "dependencies": {
-        "ansi-escapes": "^4.3.1",
+        "ansi-escapes": "^6.0.0",
+        "chalk": "^5.2.0",
+        "jest-regex-util": "^29.0.0",
+        "jest-watcher": "^29.0.0",
+        "slash": "^5.0.0",
+        "string-length": "^5.0.1",
+        "strip-ansi": "^7.0.1"
+      },
+      "engines": {
+        "node": "^14.17.0 || ^16.10.0 || >=18.0.0"
+      },
+      "peerDependencies": {
+        "jest": "^27.0.0 || ^28.0.0 || ^29.0.0"
+      }
+    },
+    "node_modules/jest-watch-typeahead/node_modules/@jest/console": {
+      "version": "29.7.0",
+      "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz",
+      "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==",
+      "dev": true,
+      "dependencies": {
+        "@jest/types": "^29.6.3",
+        "@types/node": "*",
         "chalk": "^4.0.0",
-        "jest-regex-util": "^27.0.0",
-        "jest-watcher": "^27.0.0",
-        "slash": "^3.0.0",
-        "string-length": "^4.0.1",
-        "strip-ansi": "^6.0.0"
+        "jest-message-util": "^29.7.0",
+        "jest-util": "^29.7.0",
+        "slash": "^3.0.0"
+      },
+      "engines": {
+        "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+      }
+    },
+    "node_modules/jest-watch-typeahead/node_modules/@jest/console/node_modules/chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
       },
       "engines": {
         "node": ">=10"
       },
-      "peerDependencies": {
-        "jest": "^26.0.0 || ^27.0.0"
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/jest-watch-typeahead/node_modules/@jest/console/node_modules/slash": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+      "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/jest-watch-typeahead/node_modules/@jest/test-result": {
+      "version": "29.7.0",
+      "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz",
+      "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==",
+      "dev": true,
+      "dependencies": {
+        "@jest/console": "^29.7.0",
+        "@jest/types": "^29.6.3",
+        "@types/istanbul-lib-coverage": "^2.0.0",
+        "collect-v8-coverage": "^1.0.0"
+      },
+      "engines": {
+        "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+      }
+    },
+    "node_modules/jest-watch-typeahead/node_modules/@jest/types": {
+      "version": "29.6.3",
+      "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
+      "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
+      "dev": true,
+      "dependencies": {
+        "@jest/schemas": "^29.6.3",
+        "@types/istanbul-lib-coverage": "^2.0.0",
+        "@types/istanbul-reports": "^3.0.0",
+        "@types/node": "*",
+        "@types/yargs": "^17.0.8",
+        "chalk": "^4.0.0"
+      },
+      "engines": {
+        "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+      }
+    },
+    "node_modules/jest-watch-typeahead/node_modules/@jest/types/node_modules/chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/jest-watch-typeahead/node_modules/@types/yargs": {
+      "version": "17.0.32",
+      "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz",
+      "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==",
+      "dev": true,
+      "dependencies": {
+        "@types/yargs-parser": "*"
+      }
+    },
+    "node_modules/jest-watch-typeahead/node_modules/ansi-escapes": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz",
+      "integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==",
+      "dev": true,
+      "dependencies": {
+        "type-fest": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=14.16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/jest-watch-typeahead/node_modules/ansi-regex": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+      "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
       }
     },
     "node_modules/jest-watch-typeahead/node_modules/ansi-styles": {
@@ -14060,16 +14192,12 @@
       }
     },
     "node_modules/jest-watch-typeahead/node_modules/chalk": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
-      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
+      "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
       "dev": true,
-      "dependencies": {
-        "ansi-styles": "^4.1.0",
-        "supports-color": "^7.1.0"
-      },
       "engines": {
-        "node": ">=10"
+        "node": "^12.17.0 || ^14.13 || >=16.0.0"
       },
       "funding": {
         "url": "https://github.com/chalk/chalk?sponsor=1"
@@ -14093,6 +14221,18 @@
       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
       "dev": true
     },
+    "node_modules/jest-watch-typeahead/node_modules/emittery": {
+      "version": "0.13.1",
+      "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
+      "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sindresorhus/emittery?sponsor=1"
+      }
+    },
     "node_modules/jest-watch-typeahead/node_modules/has-flag": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -14102,52 +14242,59 @@
         "node": ">=8"
       }
     },
-    "node_modules/jest-watch-typeahead/node_modules/supports-color": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
-      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+    "node_modules/jest-watch-typeahead/node_modules/jest-regex-util": {
+      "version": "29.6.3",
+      "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz",
+      "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==",
       "dev": true,
-      "dependencies": {
-        "has-flag": "^4.0.0"
-      },
       "engines": {
-        "node": ">=8"
+        "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
       }
     },
-    "node_modules/jest-watcher": {
-      "version": "27.5.1",
-      "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz",
-      "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==",
+    "node_modules/jest-watch-typeahead/node_modules/jest-watcher": {
+      "version": "29.7.0",
+      "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz",
+      "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==",
       "dev": true,
       "dependencies": {
-        "@jest/test-result": "^27.5.1",
-        "@jest/types": "^27.5.1",
+        "@jest/test-result": "^29.7.0",
+        "@jest/types": "^29.6.3",
         "@types/node": "*",
         "ansi-escapes": "^4.2.1",
         "chalk": "^4.0.0",
-        "jest-util": "^27.5.1",
+        "emittery": "^0.13.1",
+        "jest-util": "^29.7.0",
         "string-length": "^4.0.1"
       },
       "engines": {
-        "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+        "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
       }
     },
-    "node_modules/jest-watcher/node_modules/ansi-styles": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+    "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/ansi-escapes": {
+      "version": "4.3.2",
+      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+      "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
       "dev": true,
       "dependencies": {
-        "color-convert": "^2.0.1"
+        "type-fest": "^0.21.3"
       },
       "engines": {
         "node": ">=8"
       },
       "funding": {
-        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+        "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/jest-watcher/node_modules/chalk": {
+    "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/chalk": {
       "version": "4.1.2",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
       "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
@@ -14163,51 +14310,96 @@
         "url": "https://github.com/chalk/chalk?sponsor=1"
       }
     },
-    "node_modules/jest-watcher/node_modules/color-convert": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+    "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/string-length": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
+      "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
       "dev": true,
       "dependencies": {
-        "color-name": "~1.1.4"
+        "char-regex": "^1.0.2",
+        "strip-ansi": "^6.0.0"
       },
       "engines": {
-        "node": ">=7.0.0"
+        "node": ">=10"
       }
     },
-    "node_modules/jest-watcher/node_modules/color-name": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-      "dev": true
-    },
-    "node_modules/jest-watcher/node_modules/has-flag": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+    "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
       "dev": true,
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
       "engines": {
         "node": ">=8"
       }
     },
-    "node_modules/jest-watcher/node_modules/jest-util": {
-      "version": "27.5.1",
-      "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz",
-      "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==",
+    "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/type-fest": {
+      "version": "0.21.3",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+      "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/jest-watch-typeahead/node_modules/slash": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
+      "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
+      "dev": true,
+      "engines": {
+        "node": ">=14.16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/jest-watch-typeahead/node_modules/string-length": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz",
+      "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==",
       "dev": true,
       "dependencies": {
-        "@jest/types": "^27.5.1",
-        "@types/node": "*",
-        "chalk": "^4.0.0",
-        "ci-info": "^3.2.0",
-        "graceful-fs": "^4.2.9",
-        "picomatch": "^2.2.3"
+        "char-regex": "^2.0.0",
+        "strip-ansi": "^7.0.1"
       },
       "engines": {
-        "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+        "node": ">=12.20"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/jest-watcher/node_modules/supports-color": {
+    "node_modules/jest-watch-typeahead/node_modules/string-length/node_modules/char-regex": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.1.tgz",
+      "integrity": "sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==",
+      "dev": true,
+      "engines": {
+        "node": ">=12.20"
+      }
+    },
+    "node_modules/jest-watch-typeahead/node_modules/strip-ansi": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+      "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+      "dev": true,
+      "dependencies": {
+        "ansi-regex": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+      }
+    },
+    "node_modules/jest-watch-typeahead/node_modules/supports-color": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
       "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
@@ -14219,29 +14411,37 @@
         "node": ">=8"
       }
     },
-    "node_modules/jest-websocket-mock": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/jest-websocket-mock/-/jest-websocket-mock-2.4.0.tgz",
-      "integrity": "sha512-AOwyuRw6fgROXHxMOiTDl1/T4dh3fV4jDquha5N0csS/PNp742HeTZWPAuKppVRSQ8s3fUGgJHoyZT9JDO0hMA==",
+    "node_modules/jest-watch-typeahead/node_modules/type-fest": {
+      "version": "3.13.1",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz",
+      "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==",
       "dev": true,
-      "dependencies": {
-        "jest-diff": "^28.0.2",
-        "mock-socket": "^9.1.0"
+      "engines": {
+        "node": ">=14.16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/jest-websocket-mock/node_modules/@jest/schemas": {
-      "version": "28.1.3",
-      "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz",
-      "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==",
+    "node_modules/jest-watcher": {
+      "version": "27.5.1",
+      "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz",
+      "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==",
       "dev": true,
       "dependencies": {
-        "@sinclair/typebox": "^0.24.1"
+        "@jest/test-result": "^27.5.1",
+        "@jest/types": "^27.5.1",
+        "@types/node": "*",
+        "ansi-escapes": "^4.2.1",
+        "chalk": "^4.0.0",
+        "jest-util": "^27.5.1",
+        "string-length": "^4.0.1"
       },
       "engines": {
-        "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
+        "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
       }
     },
-    "node_modules/jest-websocket-mock/node_modules/ansi-styles": {
+    "node_modules/jest-watcher/node_modules/ansi-styles": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
       "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
@@ -14256,7 +14456,7 @@
         "url": "https://github.com/chalk/ansi-styles?sponsor=1"
       }
     },
-    "node_modules/jest-websocket-mock/node_modules/chalk": {
+    "node_modules/jest-watcher/node_modules/chalk": {
       "version": "4.1.2",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
       "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
@@ -14272,7 +14472,7 @@
         "url": "https://github.com/chalk/chalk?sponsor=1"
       }
     },
-    "node_modules/jest-websocket-mock/node_modules/color-convert": {
+    "node_modules/jest-watcher/node_modules/color-convert": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
       "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
@@ -14284,22 +14484,13 @@
         "node": ">=7.0.0"
       }
     },
-    "node_modules/jest-websocket-mock/node_modules/color-name": {
+    "node_modules/jest-watcher/node_modules/color-name": {
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
       "dev": true
     },
-    "node_modules/jest-websocket-mock/node_modules/diff-sequences": {
-      "version": "28.1.1",
-      "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz",
-      "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==",
-      "dev": true,
-      "engines": {
-        "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
-      }
-    },
-    "node_modules/jest-websocket-mock/node_modules/has-flag": {
+    "node_modules/jest-watcher/node_modules/has-flag": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
       "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
@@ -14308,64 +14499,24 @@
         "node": ">=8"
       }
     },
-    "node_modules/jest-websocket-mock/node_modules/jest-diff": {
-      "version": "28.1.3",
-      "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz",
-      "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==",
+    "node_modules/jest-watcher/node_modules/jest-util": {
+      "version": "27.5.1",
+      "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz",
+      "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==",
       "dev": true,
       "dependencies": {
+        "@jest/types": "^27.5.1",
+        "@types/node": "*",
         "chalk": "^4.0.0",
-        "diff-sequences": "^28.1.1",
-        "jest-get-type": "^28.0.2",
-        "pretty-format": "^28.1.3"
-      },
-      "engines": {
-        "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
-      }
-    },
-    "node_modules/jest-websocket-mock/node_modules/jest-get-type": {
-      "version": "28.0.2",
-      "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz",
-      "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==",
-      "dev": true,
-      "engines": {
-        "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
-      }
-    },
-    "node_modules/jest-websocket-mock/node_modules/pretty-format": {
-      "version": "28.1.3",
-      "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz",
-      "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==",
-      "dev": true,
-      "dependencies": {
-        "@jest/schemas": "^28.1.3",
-        "ansi-regex": "^5.0.1",
-        "ansi-styles": "^5.0.0",
-        "react-is": "^18.0.0"
+        "ci-info": "^3.2.0",
+        "graceful-fs": "^4.2.9",
+        "picomatch": "^2.2.3"
       },
       "engines": {
-        "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
-      }
-    },
-    "node_modules/jest-websocket-mock/node_modules/pretty-format/node_modules/ansi-styles": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
-      "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
-      "dev": true,
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+        "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
       }
     },
-    "node_modules/jest-websocket-mock/node_modules/react-is": {
-      "version": "18.2.0",
-      "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
-      "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
-      "dev": true
-    },
-    "node_modules/jest-websocket-mock/node_modules/supports-color": {
+    "node_modules/jest-watcher/node_modules/supports-color": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
       "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
@@ -14377,6 +14528,16 @@
         "node": ">=8"
       }
     },
+    "node_modules/jest-websocket-mock": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/jest-websocket-mock/-/jest-websocket-mock-2.5.0.tgz",
+      "integrity": "sha512-a+UJGfowNIWvtIKIQBHoEWIUqRxxQHFx4CXT+R5KxxKBtEQ5rS3pPOV/5299sHzqbmeCzxxY5qE4+yfXePePig==",
+      "dev": true,
+      "dependencies": {
+        "jest-diff": "^29.2.0",
+        "mock-socket": "^9.3.0"
+      }
+    },
     "node_modules/jest-worker": {
       "version": "27.5.1",
       "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
@@ -15653,9 +15814,9 @@
       }
     },
     "node_modules/mock-socket": {
-      "version": "9.1.5",
-      "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.1.5.tgz",
-      "integrity": "sha512-3DeNIcsQixWHHKk6NdoBhWI4t1VMj5/HzfnI1rE/pLl5qKx7+gd4DNA07ehTaZ6MoUU053si6Hd+YtiM/tQZfg==",
+      "version": "9.3.1",
+      "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz",
+      "integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==",
       "dev": true,
       "engines": {
         "node": ">= 8"
@@ -22417,9 +22578,9 @@
   },
   "dependencies": {
     "@adobe/css-tools": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.0.1.tgz",
-      "integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==",
+      "version": "4.3.1",
+      "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz",
+      "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==",
       "dev": true
     },
     "@ampproject/remapping": {
@@ -24647,12 +24808,20 @@
       }
     },
     "@jest/schemas": {
-      "version": "29.0.0",
-      "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz",
-      "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==",
+      "version": "29.6.3",
+      "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+      "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
       "dev": true,
       "requires": {
-        "@sinclair/typebox": "^0.24.1"
+        "@sinclair/typebox": "^0.27.8"
+      },
+      "dependencies": {
+        "@sinclair/typebox": {
+          "version": "0.27.8",
+          "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
+          "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
+          "dev": true
+        }
       }
     },
     "@jest/source-map": {
@@ -31963,29 +32132,29 @@
       }
     },
     "jest-message-util": {
-      "version": "29.2.1",
-      "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.2.1.tgz",
-      "integrity": "sha512-Dx5nEjw9V8C1/Yj10S/8ivA8F439VS8vTq1L7hEgwHFn9ovSKNpYW/kwNh7UglaEgXO42XxzKJB+2x0nSglFVw==",
+      "version": "29.7.0",
+      "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz",
+      "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==",
       "dev": true,
       "requires": {
         "@babel/code-frame": "^7.12.13",
-        "@jest/types": "^29.2.1",
+        "@jest/types": "^29.6.3",
         "@types/stack-utils": "^2.0.0",
         "chalk": "^4.0.0",
         "graceful-fs": "^4.2.9",
         "micromatch": "^4.0.4",
-        "pretty-format": "^29.2.1",
+        "pretty-format": "^29.7.0",
         "slash": "^3.0.0",
         "stack-utils": "^2.0.3"
       },
       "dependencies": {
         "@jest/types": {
-          "version": "29.2.1",
-          "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.2.1.tgz",
-          "integrity": "sha512-O/QNDQODLnINEPAI0cl9U6zUIDXEWXt6IC1o2N2QENuos7hlGUIthlKyV4p6ki3TvXFX071blj8HUhgLGquPjw==",
+          "version": "29.6.3",
+          "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
+          "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
           "dev": true,
           "requires": {
-            "@jest/schemas": "^29.0.0",
+            "@jest/schemas": "^29.6.3",
             "@types/istanbul-lib-coverage": "^2.0.0",
             "@types/istanbul-reports": "^3.0.0",
             "@types/node": "*",
@@ -31994,9 +32163,9 @@
           }
         },
         "@types/yargs": {
-          "version": "17.0.13",
-          "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz",
-          "integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==",
+          "version": "17.0.32",
+          "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz",
+          "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==",
           "dev": true,
           "requires": {
             "@types/yargs-parser": "*"
@@ -32043,12 +32212,12 @@
           "dev": true
         },
         "pretty-format": {
-          "version": "29.2.1",
-          "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.2.1.tgz",
-          "integrity": "sha512-Y41Sa4aLCtKAXvwuIpTvcFBkyeYp2gdFWzXGA+ZNES3VwURIB165XO/z7CjETwzCCS53MjW/rLMyyqEnTtaOfA==",
+          "version": "29.7.0",
+          "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+          "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
           "dev": true,
           "requires": {
-            "@jest/schemas": "^29.0.0",
+            "@jest/schemas": "^29.6.3",
             "ansi-styles": "^5.0.0",
             "react-is": "^18.0.0"
           },
@@ -32598,12 +32767,12 @@
       }
     },
     "jest-util": {
-      "version": "29.2.1",
-      "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.2.1.tgz",
-      "integrity": "sha512-P5VWDj25r7kj7kl4pN2rG/RN2c1TLfYYYZYULnS/35nFDjBai+hBeo3MDrYZS7p6IoY3YHZnt2vq4L6mKnLk0g==",
+      "version": "29.7.0",
+      "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
+      "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
       "dev": true,
       "requires": {
-        "@jest/types": "^29.2.1",
+        "@jest/types": "^29.6.3",
         "@types/node": "*",
         "chalk": "^4.0.0",
         "ci-info": "^3.2.0",
@@ -32612,12 +32781,12 @@
       },
       "dependencies": {
         "@jest/types": {
-          "version": "29.2.1",
-          "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.2.1.tgz",
-          "integrity": "sha512-O/QNDQODLnINEPAI0cl9U6zUIDXEWXt6IC1o2N2QENuos7hlGUIthlKyV4p6ki3TvXFX071blj8HUhgLGquPjw==",
+          "version": "29.6.3",
+          "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
+          "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
           "dev": true,
           "requires": {
-            "@jest/schemas": "^29.0.0",
+            "@jest/schemas": "^29.6.3",
             "@types/istanbul-lib-coverage": "^2.0.0",
             "@types/istanbul-reports": "^3.0.0",
             "@types/node": "*",
@@ -32626,9 +32795,9 @@
           }
         },
         "@types/yargs": {
-          "version": "17.0.13",
-          "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz",
-          "integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==",
+          "version": "17.0.32",
+          "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz",
+          "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==",
           "dev": true,
           "requires": {
             "@types/yargs-parser": "*"
@@ -32763,20 +32932,114 @@
       }
     },
     "jest-watch-typeahead": {
-      "version": "0.6.5",
-      "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-0.6.5.tgz",
-      "integrity": "sha512-GIbV6h37/isatMDtqZlA8Q5vC6T3w+5qdvtF+3LIkPc58zEWzbKmTHvlUIp3wvBm400RzrQWcVPcsAJqKWu7XQ==",
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-2.2.2.tgz",
+      "integrity": "sha512-+QgOFW4o5Xlgd6jGS5X37i08tuuXNW8X0CV9WNFi+3n8ExCIP+E1melYhvYLjv5fE6D0yyzk74vsSO8I6GqtvQ==",
       "dev": true,
       "requires": {
-        "ansi-escapes": "^4.3.1",
-        "chalk": "^4.0.0",
-        "jest-regex-util": "^27.0.0",
-        "jest-watcher": "^27.0.0",
-        "slash": "^3.0.0",
-        "string-length": "^4.0.1",
-        "strip-ansi": "^6.0.0"
+        "ansi-escapes": "^6.0.0",
+        "chalk": "^5.2.0",
+        "jest-regex-util": "^29.0.0",
+        "jest-watcher": "^29.0.0",
+        "slash": "^5.0.0",
+        "string-length": "^5.0.1",
+        "strip-ansi": "^7.0.1"
       },
       "dependencies": {
+        "@jest/console": {
+          "version": "29.7.0",
+          "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz",
+          "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==",
+          "dev": true,
+          "requires": {
+            "@jest/types": "^29.6.3",
+            "@types/node": "*",
+            "chalk": "^4.0.0",
+            "jest-message-util": "^29.7.0",
+            "jest-util": "^29.7.0",
+            "slash": "^3.0.0"
+          },
+          "dependencies": {
+            "chalk": {
+              "version": "4.1.2",
+              "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+              "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+              "dev": true,
+              "requires": {
+                "ansi-styles": "^4.1.0",
+                "supports-color": "^7.1.0"
+              }
+            },
+            "slash": {
+              "version": "3.0.0",
+              "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+              "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+              "dev": true
+            }
+          }
+        },
+        "@jest/test-result": {
+          "version": "29.7.0",
+          "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz",
+          "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==",
+          "dev": true,
+          "requires": {
+            "@jest/console": "^29.7.0",
+            "@jest/types": "^29.6.3",
+            "@types/istanbul-lib-coverage": "^2.0.0",
+            "collect-v8-coverage": "^1.0.0"
+          }
+        },
+        "@jest/types": {
+          "version": "29.6.3",
+          "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
+          "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
+          "dev": true,
+          "requires": {
+            "@jest/schemas": "^29.6.3",
+            "@types/istanbul-lib-coverage": "^2.0.0",
+            "@types/istanbul-reports": "^3.0.0",
+            "@types/node": "*",
+            "@types/yargs": "^17.0.8",
+            "chalk": "^4.0.0"
+          },
+          "dependencies": {
+            "chalk": {
+              "version": "4.1.2",
+              "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+              "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+              "dev": true,
+              "requires": {
+                "ansi-styles": "^4.1.0",
+                "supports-color": "^7.1.0"
+              }
+            }
+          }
+        },
+        "@types/yargs": {
+          "version": "17.0.32",
+          "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz",
+          "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==",
+          "dev": true,
+          "requires": {
+            "@types/yargs-parser": "*"
+          }
+        },
+        "ansi-escapes": {
+          "version": "6.2.0",
+          "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.0.tgz",
+          "integrity": "sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==",
+          "dev": true,
+          "requires": {
+            "type-fest": "^3.0.0"
+          }
+        },
+        "ansi-regex": {
+          "version": "6.0.1",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+          "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+          "dev": true
+        },
         "ansi-styles": {
           "version": "4.3.0",
           "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -32787,14 +33050,10 @@
           }
         },
         "chalk": {
-          "version": "4.1.2",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
-          "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-          "dev": true,
-          "requires": {
-            "ansi-styles": "^4.1.0",
-            "supports-color": "^7.1.0"
-          }
+          "version": "5.3.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
+          "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
+          "dev": true
         },
         "color-convert": {
           "version": "2.0.1",
@@ -32811,12 +33070,125 @@
           "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
           "dev": true
         },
+        "emittery": {
+          "version": "0.13.1",
+          "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
+          "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==",
+          "dev": true
+        },
         "has-flag": {
           "version": "4.0.0",
           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
           "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
           "dev": true
         },
+        "jest-regex-util": {
+          "version": "29.6.3",
+          "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz",
+          "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==",
+          "dev": true
+        },
+        "jest-watcher": {
+          "version": "29.7.0",
+          "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz",
+          "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==",
+          "dev": true,
+          "requires": {
+            "@jest/test-result": "^29.7.0",
+            "@jest/types": "^29.6.3",
+            "@types/node": "*",
+            "ansi-escapes": "^4.2.1",
+            "chalk": "^4.0.0",
+            "emittery": "^0.13.1",
+            "jest-util": "^29.7.0",
+            "string-length": "^4.0.1"
+          },
+          "dependencies": {
+            "ansi-escapes": {
+              "version": "4.3.2",
+              "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+              "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+              "dev": true,
+              "requires": {
+                "type-fest": "^0.21.3"
+              }
+            },
+            "ansi-regex": {
+              "version": "5.0.1",
+              "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+              "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+              "dev": true
+            },
+            "chalk": {
+              "version": "4.1.2",
+              "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+              "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+              "dev": true,
+              "requires": {
+                "ansi-styles": "^4.1.0",
+                "supports-color": "^7.1.0"
+              }
+            },
+            "string-length": {
+              "version": "4.0.2",
+              "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
+              "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
+              "dev": true,
+              "requires": {
+                "char-regex": "^1.0.2",
+                "strip-ansi": "^6.0.0"
+              }
+            },
+            "strip-ansi": {
+              "version": "6.0.1",
+              "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+              "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+              "dev": true,
+              "requires": {
+                "ansi-regex": "^5.0.1"
+              }
+            },
+            "type-fest": {
+              "version": "0.21.3",
+              "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+              "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+              "dev": true
+            }
+          }
+        },
+        "slash": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
+          "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
+          "dev": true
+        },
+        "string-length": {
+          "version": "5.0.1",
+          "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz",
+          "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==",
+          "dev": true,
+          "requires": {
+            "char-regex": "^2.0.0",
+            "strip-ansi": "^7.0.1"
+          },
+          "dependencies": {
+            "char-regex": {
+              "version": "2.0.1",
+              "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.1.tgz",
+              "integrity": "sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==",
+              "dev": true
+            }
+          }
+        },
+        "strip-ansi": {
+          "version": "7.1.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+          "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^6.0.1"
+          }
+        },
         "supports-color": {
           "version": "7.2.0",
           "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -32825,6 +33197,12 @@
           "requires": {
             "has-flag": "^4.0.0"
           }
+        },
+        "type-fest": {
+          "version": "3.13.1",
+          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz",
+          "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==",
+          "dev": true
         }
       }
     },
@@ -32909,123 +33287,13 @@
       }
     },
     "jest-websocket-mock": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/jest-websocket-mock/-/jest-websocket-mock-2.4.0.tgz",
-      "integrity": "sha512-AOwyuRw6fgROXHxMOiTDl1/T4dh3fV4jDquha5N0csS/PNp742HeTZWPAuKppVRSQ8s3fUGgJHoyZT9JDO0hMA==",
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/jest-websocket-mock/-/jest-websocket-mock-2.5.0.tgz",
+      "integrity": "sha512-a+UJGfowNIWvtIKIQBHoEWIUqRxxQHFx4CXT+R5KxxKBtEQ5rS3pPOV/5299sHzqbmeCzxxY5qE4+yfXePePig==",
       "dev": true,
       "requires": {
-        "jest-diff": "^28.0.2",
-        "mock-socket": "^9.1.0"
-      },
-      "dependencies": {
-        "@jest/schemas": {
-          "version": "28.1.3",
-          "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz",
-          "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==",
-          "dev": true,
-          "requires": {
-            "@sinclair/typebox": "^0.24.1"
-          }
-        },
-        "ansi-styles": {
-          "version": "4.3.0",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-          "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-          "dev": true,
-          "requires": {
-            "color-convert": "^2.0.1"
-          }
-        },
-        "chalk": {
-          "version": "4.1.2",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
-          "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-          "dev": true,
-          "requires": {
-            "ansi-styles": "^4.1.0",
-            "supports-color": "^7.1.0"
-          }
-        },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
-        "color-name": {
-          "version": "1.1.4",
-          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-          "dev": true
-        },
-        "diff-sequences": {
-          "version": "28.1.1",
-          "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz",
-          "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==",
-          "dev": true
-        },
-        "has-flag": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-          "dev": true
-        },
-        "jest-diff": {
-          "version": "28.1.3",
-          "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz",
-          "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==",
-          "dev": true,
-          "requires": {
-            "chalk": "^4.0.0",
-            "diff-sequences": "^28.1.1",
-            "jest-get-type": "^28.0.2",
-            "pretty-format": "^28.1.3"
-          }
-        },
-        "jest-get-type": {
-          "version": "28.0.2",
-          "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz",
-          "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==",
-          "dev": true
-        },
-        "pretty-format": {
-          "version": "28.1.3",
-          "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz",
-          "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==",
-          "dev": true,
-          "requires": {
-            "@jest/schemas": "^28.1.3",
-            "ansi-regex": "^5.0.1",
-            "ansi-styles": "^5.0.0",
-            "react-is": "^18.0.0"
-          },
-          "dependencies": {
-            "ansi-styles": {
-              "version": "5.2.0",
-              "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
-              "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
-              "dev": true
-            }
-          }
-        },
-        "react-is": {
-          "version": "18.2.0",
-          "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
-          "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
-          "dev": true
-        },
-        "supports-color": {
-          "version": "7.2.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
-          "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-          "dev": true,
-          "requires": {
-            "has-flag": "^4.0.0"
-          }
-        }
+        "jest-diff": "^29.2.0",
+        "mock-socket": "^9.3.0"
       }
     },
     "jest-worker": {
@@ -33987,9 +34255,9 @@
       }
     },
     "mock-socket": {
-      "version": "9.1.5",
-      "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.1.5.tgz",
-      "integrity": "sha512-3DeNIcsQixWHHKk6NdoBhWI4t1VMj5/HzfnI1rE/pLl5qKx7+gd4DNA07ehTaZ6MoUU053si6Hd+YtiM/tQZfg==",
+      "version": "9.3.1",
+      "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz",
+      "integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==",
       "dev": true
     },
     "modify-values": {
diff --git a/package.json b/package.json
index 8291f08..3b5b11c 100644
--- a/package.json
+++ b/package.json
@@ -22,8 +22,8 @@
     "conventional-changelog": "^3.1.25",
     "http-proxy-middleware": "^2.0.6",
     "husky": "^8.0.1",
-    "jest-watch-typeahead": "^0.6.5",
-    "jest-websocket-mock": "^2.4.0",
+    "jest-watch-typeahead": "^2.2.2",
+    "jest-websocket-mock": "^2.5.0",
     "lint-staged": "^13.0.3",
     "prettier": "^3.0.3",
     "react-scripts": "^5.0.1",
diff --git a/src/components/Balance.module.css b/src/components/Balance.module.css
index ced0b16..013532e 100644
--- a/src/components/Balance.module.css
+++ b/src/components/Balance.module.css
@@ -22,23 +22,35 @@
   color: var(--jam-balance-color);
 }
 
-.frozenSymbol {
-  order: -2;
+.hideSymbol {
+  padding-left: 0.1em;
+  color: var(--jam-balance-deemphasize-color);
 }
 
 .bitcoinSymbol {
-  padding-right: 0.1em;
   order: -1;
+  width: 1em;
+  padding-right: 0.1em;
 }
 
 .satsSymbol {
-  padding-right: 0.1em;
-  order: 6;
+  order: 5;
 }
 
-.hideSymbol {
-  padding-left: 0.1em;
-  color: var(--jam-balance-deemphasize-color);
+.frozenSymbol {
+  order: 5;
+}
+.bitcoinAmount + .frozenSymbol {
+  order: -2;
+  width: 1em;
+  height: 1em;
+}
+
+.frozenSymbol,
+.bitcoinSymbol,
+.satsSymbol {
+  display: flex;
+  justify-content: center;
 }
 
 .bitcoinAmount .fractionalPart :nth-child(3)::before,
diff --git a/src/components/Balance.tsx b/src/components/Balance.tsx
index 9d49437..880d0f6 100644
--- a/src/components/Balance.tsx
+++ b/src/components/Balance.tsx
@@ -1,4 +1,4 @@
-import { MouseEventHandler, useCallback, useEffect, useMemo, useState } from 'react'
+import { PropsWithChildren, MouseEventHandler, useEffect, useMemo, useState } from 'react'
 import classNames from 'classnames'
 import Sprite from './Sprite'
 import { SATS, BTC, btcToSats, satsToBtc, isValidNumber, formatBtc, formatSats } from '../utils'
@@ -15,74 +15,99 @@ const getDisplayMode = (unit: Unit, showBalance: boolean) => {
   return DISPLAY_MODE_HIDDEN
 }
 
-const DECIMAL_POINT_CHAR = '.'
-
-const BitcoinAmountComponent = ({ value }: { value: number }) => {
-  const numberString = formatBtc(value)
-  const [integerPart, fractionalPart] = numberString.split(DECIMAL_POINT_CHAR)
-
-  const fractionPartArray = fractionalPart.split('')
-  const integerPartIsZero = integerPart === '0'
-  const fractionalPartStartsWithZero = fractionPartArray[0] === '0'
-
-  return (
-    <span
-      className={`${styles.bitcoinAmount} slashed-zeroes`}
-      data-testid="bitcoin-amount"
-      data-integer-part-is-zero={integerPartIsZero}
-      data-fractional-part-starts-with-zero={fractionalPartStartsWithZero}
-      data-raw-value={value}
-      data-formatted-value={numberString}
-    >
-      <span className={styles.integerPart}>{integerPart}</span>
-      <span className={styles.decimalPoint}>{DECIMAL_POINT_CHAR}</span>
-      <span className={styles.fractionalPart}>
-        {fractionPartArray.map((digit, index) => (
-          <span key={index} data-digit={digit}>
-            {digit}
-          </span>
-        ))}
-      </span>
-    </span>
-  )
-}
-
-const SatsAmountComponent = ({ value }: { value: number }) => {
-  return (
-    <span className={`${styles.satsAmount} slashed-zeroes`} data-testid="sats-amount" data-raw-value={value}>
-      {formatSats(value)}
-    </span>
-  )
-}
-
-const BTC_SYMBOL = <span className={styles.bitcoinSymbol}>{'\u20BF'}</span>
+const BTC_SYMBOL = (
+  <span data-testid="bitcoin-symbol" className={styles.bitcoinSymbol}>
+    {'\u20BF'}
+  </span>
+)
 
-const SAT_SYMBOL = <Sprite className={styles.satsSymbol} symbol="sats" width="1.2em" height="1.2em" />
+const SAT_SYMBOL = (
+  <Sprite data-testid="sats-symbol" className={styles.satsSymbol} symbol="sats" width="1.2em" height="1.2em" />
+)
 
 const FROZEN_SYMBOL = (
-  <Sprite className={`${styles.frozenSymbol} frozen-symbol-hook`} symbol="snowflake" width="1.2em" height="1.2em" />
+  <Sprite
+    data-testid="frozen-symbol"
+    className={`${styles.frozenSymbol} frozen-symbol-hook`}
+    symbol="snowflake"
+    width="1.2em"
+    height="1.2em"
+  />
 )
 
 interface BalanceComponentProps {
-  symbol: JSX.Element
-  value: JSX.Element
+  symbol?: JSX.Element
+  showSymbol?: boolean
   frozen?: boolean
 }
 
-const BalanceComponent = ({ symbol, value, frozen = false }: BalanceComponentProps) => {
+const BalanceComponent = ({
+  symbol,
+  showSymbol = true,
+  frozen = false,
+  children,
+}: PropsWithChildren<BalanceComponentProps>) => {
   return (
     <span
       className={classNames(styles.balance, 'balance-hook', 'd-inline-flex align-items-center', {
         [styles.frozen]: frozen,
       })}
     >
+      {children}
+      {showSymbol && symbol}
       {frozen && FROZEN_SYMBOL}
-      {value}
-      {symbol}
     </span>
   )
 }
 
+const DECIMAL_POINT_CHAR = '.'
+
+type BitcoinBalanceProps = Omit<BalanceComponentProps, 'symbol'> & { value: number }
+
+const BitcoinBalance = ({ value, ...props }: BitcoinBalanceProps) => {
+  const numberString = formatBtc(value)
+  const [integerPart, fractionalPart] = numberString.split(DECIMAL_POINT_CHAR)
+
+  const fractionPartArray = fractionalPart.split('')
+  const integerPartIsZero = integerPart === '0'
+  const fractionalPartStartsWithZero = fractionPartArray[0] === '0'
+
+  return (
+    <BalanceComponent symbol={BTC_SYMBOL} {...props}>
+      <span
+        className={`${styles.bitcoinAmount} slashed-zeroes`}
+        data-testid="bitcoin-amount"
+        data-integer-part-is-zero={integerPartIsZero}
+        data-fractional-part-starts-with-zero={fractionalPartStartsWithZero}
+        data-raw-value={value}
+        data-formatted-value={numberString}
+      >
+        <span className={styles.integerPart}>{integerPart}</span>
+        <span className={styles.decimalPoint}>{DECIMAL_POINT_CHAR}</span>
+        <span className={styles.fractionalPart}>
+          {fractionPartArray.map((digit, index) => (
+            <span key={index} data-digit={digit}>
+              {digit}
+            </span>
+          ))}
+        </span>
+      </span>
+    </BalanceComponent>
+  )
+}
+
+type SatsBalanceProps = Omit<BalanceComponentProps, 'symbol'> & { value: number }
+
+const SatsBalance = ({ value, ...props }: SatsBalanceProps) => {
+  return (
+    <BalanceComponent symbol={SAT_SYMBOL} {...props}>
+      <span className={`${styles.satsAmount} slashed-zeroes`} data-testid="sats-amount" data-raw-value={value}>
+        {formatSats(value)}
+      </span>
+    </BalanceComponent>
+  )
+}
+
 /**
  * Options argument for Balance component.
  *
@@ -97,12 +122,11 @@ const BalanceComponent = ({ symbol, value, frozen = false }: BalanceComponentPro
  * @param {loading}: A loading flag that renders a placeholder while true.
  * @param {enableVisibilityToggle}: A flag that controls whether the balance can mask/unmask when clicked
  */
-interface BalanceProps {
+type BalanceProps = Omit<BalanceComponentProps, 'symbol'> & {
   valueString: string
   convertToUnit: Unit
   showBalance?: boolean
   enableVisibilityToggle?: boolean
-  frozen?: boolean
 }
 
 /**
@@ -113,7 +137,7 @@ export default function Balance({
   convertToUnit,
   showBalance = false,
   enableVisibilityToggle = !showBalance,
-  frozen = false,
+  ...props
 }: BalanceProps) {
   const [isBalanceVisible, setIsBalanceVisible] = useState(showBalance)
   const displayMode = useMemo(() => getDisplayMode(convertToUnit, isBalanceVisible), [convertToUnit, isBalanceVisible])
@@ -122,28 +146,29 @@ export default function Balance({
     setIsBalanceVisible(showBalance)
   }, [showBalance])
 
-  const toggleVisibility: MouseEventHandler = useCallback((e) => {
+  const toggleVisibility: MouseEventHandler = (e) => {
     e.preventDefault()
     e.stopPropagation()
 
     setIsBalanceVisible((current) => !current)
-  }, [])
+  }
 
   const balanceComponent = useMemo(() => {
     if (displayMode === DISPLAY_MODE_HIDDEN) {
       return (
         <BalanceComponent
           symbol={<Sprite symbol="hide" width="1.2em" height="1.2em" className={styles.hideSymbol} />}
-          value={<span className="slashed-zeroes">{'*****'}</span>}
-          frozen={frozen}
-        />
+          {...props}
+        >
+          <span className="slashed-zeroes">{'*****'}</span>
+        </BalanceComponent>
       )
     }
 
     const valueNumber = parseFloat(valueString)
     if (!isValidNumber(valueNumber)) {
       console.warn('<Balance /> component expects number input as string')
-      return <BalanceComponent symbol={<></>} value={<>{valueString}</>} frozen={frozen} />
+      return <BalanceComponent {...props}>{valueString}</BalanceComponent>
     }
 
     // Treat integers as sats.
@@ -151,35 +176,17 @@ export default function Balance({
     // Treat decimal numbers as btc.
     const valueIsBtc = !valueIsSats && valueString.indexOf('.') > -1
 
-    if (valueIsBtc && displayMode === DISPLAY_MODE_BTC)
-      return (
-        <BalanceComponent symbol={BTC_SYMBOL} value={<BitcoinAmountComponent value={valueNumber} />} frozen={frozen} />
-      )
-    if (valueIsSats && displayMode === DISPLAY_MODE_SATS)
-      return (
-        <BalanceComponent symbol={SAT_SYMBOL} value={<SatsAmountComponent value={valueNumber} />} frozen={frozen} />
-      )
+    if (valueIsBtc && displayMode === DISPLAY_MODE_BTC) return <BitcoinBalance value={valueNumber} {...props} />
+    if (valueIsSats && displayMode === DISPLAY_MODE_SATS) return <SatsBalance value={valueNumber} {...props} />
 
     if (valueIsBtc && displayMode === DISPLAY_MODE_SATS)
-      return (
-        <BalanceComponent
-          symbol={SAT_SYMBOL}
-          value={<SatsAmountComponent value={btcToSats(valueString)} />}
-          frozen={frozen}
-        />
-      )
+      return <SatsBalance value={btcToSats(valueString)} {...props} />
     if (valueIsSats && displayMode === DISPLAY_MODE_BTC)
-      return (
-        <BalanceComponent
-          symbol={BTC_SYMBOL}
-          value={<BitcoinAmountComponent value={satsToBtc(valueString)} />}
-          frozen={frozen}
-        />
-      )
+      return <BitcoinBalance value={satsToBtc(valueString)} {...props} />
 
     console.warn('<Balance /> component cannot determine balance format')
-    return <BalanceComponent symbol={<></>} value={<>{valueString}</>} frozen={frozen} />
-  }, [valueString, displayMode, frozen])
+    return <BalanceComponent {...props}>{valueString}</BalanceComponent>
+  }, [valueString, displayMode, props])
 
   if (!enableVisibilityToggle) {
     return <>{balanceComponent}</>
diff --git a/src/components/Jam.tsx b/src/components/Jam.tsx
index aa14830..7410094 100644
--- a/src/components/Jam.tsx
+++ b/src/components/Jam.tsx
@@ -464,7 +464,6 @@ export default function Jam({ wallet }: JamProps) {
                       setFieldValue,
                       validateForm,
                       isValid,
-                      dirty,
                       touched,
                       errors,
                     }) => (
@@ -474,7 +473,12 @@ export default function Jam({ wallet }: JamProps) {
                           {isDebugFeatureEnabled('insecureScheduleTesting') && (
                             <rb.Form.Group className="mb-4" controlId="offertype">
                               <ToggleSwitch
-                                label={'Use insecure testing settings'}
+                                label={
+                                  <>
+                                    Use insecure testing settings
+                                    <span className="ms-2 badge rounded-pill bg-warning">dev</span>
+                                  </>
+                                }
                                 subtitle={
                                   "This is completely insecure but makes testing the schedule much faster. This option won't be available in production."
                                 }
diff --git a/src/components/ToggleSwitch.tsx b/src/components/ToggleSwitch.tsx
index de6f2b5..cda7c65 100644
--- a/src/components/ToggleSwitch.tsx
+++ b/src/components/ToggleSwitch.tsx
@@ -1,8 +1,8 @@
-import { ChangeEvent } from 'react'
+import { ReactNode, ChangeEvent } from 'react'
 import styles from './ToggleSwitch.module.css'
 
 interface ToggleSwitchProps {
-  label: string
+  label: string | ReactNode
   subtitle?: string
   onToggle: (isToggled: boolean) => void
   toggledOn: boolean
diff --git a/src/components/jars/Jar.module.css b/src/components/jars/Jar.module.css
index b24d8b0..9cb838d 100644
--- a/src/components/jars/Jar.module.css
+++ b/src/components/jars/Jar.module.css
@@ -58,10 +58,6 @@
   font-size: 0.8rem;
 }
 
-.frozen.jarBalance {
-  font-size: 0.7rem;
-}
-
 .selectableJarContainer {
   display: flex;
   flex-direction: column;
diff --git a/src/components/jars/Jar.tsx b/src/components/jars/Jar.tsx
index 4def150..5b2fae6 100644
--- a/src/components/jars/Jar.tsx
+++ b/src/components/jars/Jar.tsx
@@ -135,6 +135,7 @@ const Jar = ({ index, balance, frozenBalance, fillLevel, isOpen = false }: JarPr
               convertToUnit={settings.unit}
               showBalance={settings.showBalance}
               frozen={true}
+              showSymbol={false}
             />
           )}
         </div>
diff --git a/src/components/settings/FeeConfigModal.tsx b/src/components/settings/FeeConfigModal.tsx
index 41b1c00..881ffcd 100644
--- a/src/components/settings/FeeConfigModal.tsx
+++ b/src/components/settings/FeeConfigModal.tsx
@@ -1,7 +1,7 @@
 import { forwardRef, useRef, useCallback, useEffect, useState } from 'react'
 import * as rb from 'react-bootstrap'
 import { Trans, useTranslation } from 'react-i18next'
-import { Formik, FormikErrors } from 'formik'
+import { Formik, FormikErrors, FormikProps } from 'formik'
 import classNames from 'classnames'
 import { FEE_CONFIG_KEYS, TxFeeValueUnit, toTxFeeValueUnit, FeeValues, useLoadFeeConfigValues } from '../../hooks/Fees'
 import { useUpdateConfigValues } from '../../context/ServiceConfigContext'
@@ -9,6 +9,10 @@ import { isValidNumber, factorToPercentage, percentageToFactor } from '../../uti
 import Sprite from '../Sprite'
 import SegmentedTabs from '../SegmentedTabs'
 import styles from './FeeConfigModal.module.css'
+import { isDebugFeatureEnabled } from '../../constants/debugFeatures'
+import ToggleSwitch from '../ToggleSwitch'
+
+const __dev_allowFeeValuesReset = isDebugFeatureEnabled('allowFeeValuesReset')
 
 type SatsPerKiloVByte = number
 
@@ -53,17 +57,21 @@ export type FeeConfigSectionKey = 'tx_fee' | 'cj_fee'
 const TX_FEE_SECTION_KEY: FeeConfigSectionKey = 'tx_fee'
 const CJ_FEE_SECTION_KEY: FeeConfigSectionKey = 'cj_fee'
 
+type FeeFormValues = FeeValues & {
+  enableValidation?: boolean
+}
+
 interface FeeConfigFormProps {
-  initialValues: FeeValues
-  validate: (values: FeeValues, txFeesUnit: TxFeeValueUnit) => FormikErrors<FeeValues>
-  onSubmit: (values: FeeValues, txFeesUnit: TxFeeValueUnit) => void
+  initialValues: FeeFormValues
+  validate: (values: FeeFormValues, txFeesUnit: TxFeeValueUnit) => FormikErrors<FeeFormValues>
+  onSubmit: (values: FeeFormValues, txFeesUnit: TxFeeValueUnit) => void
   defaultActiveSectionKey?: FeeConfigSectionKey
 }
 
 const FeeConfigForm = forwardRef(
   (
     { onSubmit, validate, initialValues, defaultActiveSectionKey }: FeeConfigFormProps,
-    ref: React.Ref<HTMLFormElement>,
+    ref: React.Ref<FormikProps<FeeFormValues>>,
   ) => {
     const { t, i18n } = useTranslation()
 
@@ -71,12 +79,34 @@ const FeeConfigForm = forwardRef(
 
     return (
       <Formik
+        innerRef={ref}
         initialValues={initialValues}
         validate={(values) => validate(values, txFeesUnit)}
         onSubmit={(values) => onSubmit(values, txFeesUnit)}
       >
         {({ handleSubmit, setFieldValue, handleBlur, validateForm, values, touched, errors, isSubmitting }) => (
-          <rb.Form ref={ref} onSubmit={handleSubmit} noValidate lang={i18n.resolvedLanguage || i18n.language}>
+          <rb.Form onSubmit={handleSubmit} noValidate lang={i18n.resolvedLanguage || i18n.language}>
+            {__dev_allowFeeValuesReset && (
+              <div className="mb-4">
+                <ToggleSwitch
+                  label={
+                    <>
+                      Enable form validation
+                      <span className="ms-2 badge rounded-pill bg-warning">dev</span>
+                    </>
+                  }
+                  subtitle={
+                    'Ability to reset fee values to test what the UI looks like, when a user does not have these values configured.'
+                  }
+                  toggledOn={values.enableValidation ?? true}
+                  onToggle={(isToggled) => {
+                    setFieldValue('enableValidation', isToggled, true)
+                  }}
+                  disabled={isSubmitting}
+                />
+              </div>
+            )}
+
             <rb.Accordion flush defaultActiveKey={defaultActiveSectionKey}>
               <rb.Accordion.Item eventKey={CJ_FEE_SECTION_KEY}>
                 <rb.Accordion.Header>
@@ -332,9 +362,9 @@ export default function FeeConfigModal({
   const [isLoading, setIsLoading] = useState(true)
   const [isSubmitting, setIsSubmitting] = useState(false)
   const [loadError, setLoadError] = useState(false)
-  const [saveErrorMessage, setSaveErrorMessage] = useState<string | undefined>(undefined)
-  const [feeConfigValues, setFeeConfigValues] = useState<FeeValues | null>(null)
-  const formRef = useRef<HTMLFormElement>(null)
+  const [saveErrorMessage, setSaveErrorMessage] = useState<string>()
+  const [feeFormValues, setFeeFormValues] = useState<FeeFormValues | null>(null)
+  const formRef = useRef<FormikProps<FeeFormValues>>(null)
 
   useEffect(() => {
     setLoadError(false)
@@ -347,7 +377,7 @@ export default function FeeConfigModal({
         .then((val) => {
           if (abortCtrl.signal.aborted) return
           setIsLoading(false)
-          setFeeConfigValues(val)
+          setFeeFormValues(val)
         })
         .catch((e) => {
           if (abortCtrl.signal.aborted) return
@@ -415,8 +445,14 @@ export default function FeeConfigModal({
   }
 
   const validate = useCallback(
-    (values: FeeValues, txFeesUnit: TxFeeValueUnit) => {
-      const errors = {} as FormikErrors<FeeValues>
+    (values: FeeFormValues, txFeesUnit: TxFeeValueUnit) => {
+      const errors = {} as FormikErrors<FeeFormValues>
+
+      if (values.enableValidation === false) {
+        // do not validate form to enable resetting the values
+        // this can only be done in dev mode!
+        return errors
+      }
 
       if (
         !isValidNumber(values.tx_fees_factor) ||
@@ -536,10 +572,10 @@ export default function FeeConfigModal({
             </>
           ) : (
             <>
-              {feeConfigValues && (
+              {feeFormValues && (
                 <FeeConfigForm
                   ref={formRef}
-                  initialValues={feeConfigValues}
+                  initialValues={feeFormValues}
                   validate={validate}
                   onSubmit={submit}
                   defaultActiveSectionKey={defaultActiveSectionKey}
@@ -559,12 +595,32 @@ export default function FeeConfigModal({
           <rb.Button variant="light" onClick={cancel} className="d-flex justify-content-center align-items-center">
             {t('settings.fees.text_button_cancel')}
           </rb.Button>
+
+          {__dev_allowFeeValuesReset && (
+            <rb.Button
+              variant="outline-dark"
+              className="position-relative"
+              onClick={() => {
+                formRef.current?.setFieldValue('max_cj_fee_abs', '', false)
+                formRef.current?.setFieldValue('max_cj_fee_rel', '', false)
+                formRef.current?.setFieldValue('tx_fees', '', false)
+                formRef.current?.setFieldValue('tx_fees_factor', '', false)
+                setTimeout(() => formRef.current?.validateForm(), 4)
+              }}
+              disabled={isLoading || isSubmitting}
+            >
+              Reset form values
+              <span className="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-warning">
+                dev
+              </span>
+            </rb.Button>
+          )}
           <rb.Button
             variant="dark"
             type="submit"
             className="d-flex justify-content-center align-items-center"
             disabled={isLoading || isSubmitting}
-            onClick={() => formRef.current?.requestSubmit()}
+            onClick={() => formRef.current?.submitForm()}
           >
             {isSubmitting ? (
               <>
diff --git a/src/constants/debugFeatures.ts b/src/constants/debugFeatures.ts
index 43f9cfd..ce284b3 100644
--- a/src/constants/debugFeatures.ts
+++ b/src/constants/debugFeatures.ts
@@ -6,6 +6,7 @@ interface DebugFeatures {
   devSetupPage: boolean
   importDummyMnemonicPhrase: boolean
   rescanChainPage: boolean
+  allowFeeValuesReset: boolean
   fastThemeToggle: boolean
 }
 
@@ -19,6 +20,7 @@ const debugFeatures: DebugFeatures = {
   devSetupPage: devMode,
   importDummyMnemonicPhrase: devMode,
   rescanChainPage: devMode,
+  allowFeeValuesReset: devMode,
   fastThemeToggle: devMode,
 }
 
diff --git a/src/context/WebsocketContext.jsx b/src/context/WebsocketContext.jsx
index 6fd0062..f8d3df8 100644
--- a/src/context/WebsocketContext.jsx
+++ b/src/context/WebsocketContext.jsx
@@ -1,5 +1,4 @@
 import { createContext, useEffect, useState, useContext } from 'react'
-
 import { useCurrentWallet } from './WalletContext'
 import { noop } from '../utils'
 import { isDevMode } from '../constants/debugFeatures'
@@ -30,7 +29,8 @@ const logToDebugConsoleInDevMode = isDevMode() ? console.debug : noop
 const createWebSocket = () => {
   const { protocol, host } = window.location
   const scheme = protocol === 'https:' ? 'wss' : 'ws'
-  const websocket = new WebSocket(`${scheme}://${host}${WEBSOCKET_ENDPOINT_PATH}`)
+  const url = `${scheme}://${host}${WEBSOCKET_ENDPOINT_PATH}`
+  const websocket = new WebSocket(url)
 
   websocket.onerror = (error) => {
     console.error('[Websocket] error', error)


Test patch:
diff --git a/src/components/App.test.tsx b/src/components/App.test.tsx
index f2ccc7d..59a190b 100644
--- a/src/components/App.test.tsx
+++ b/src/components/App.test.tsx
@@ -1,5 +1,4 @@
-import { render, screen } from '../testUtils'
-import { act } from 'react-dom/test-utils'
+import { render, screen, act } from '../testUtils'
 import user from '@testing-library/user-event'
 import * as apiMock from '../libs/JmWalletApi'
 
diff --git a/src/components/Balance.test.tsx b/src/components/Balance.test.tsx
index 9429387..68377b1 100644
--- a/src/components/Balance.test.tsx
+++ b/src/components/Balance.test.tsx
@@ -11,31 +11,46 @@ describe('<Balance />', () => {
     expect(screen.getByText(`NaN`)).toBeInTheDocument()
   })
 
-  it('should render BTC using satscomma formatting', () => {
+  it('should render balance in BTC', () => {
     render(<Balance valueString={'123.456'} convertToUnit={BTC} showBalance={true} />)
     expect(screen.getByTestId('bitcoin-amount').dataset.formattedValue).toBe(`123.45600000`)
+    expect(screen.getByTestId('bitcoin-symbol')).toBeVisible()
+    expect(screen.queryByTestId('sats-symbol')).not.toBeInTheDocument()
+    expect(screen.queryByTestId('frozen-symbol')).not.toBeInTheDocument()
+  })
+
+  it('should render balance in SATS', () => {
+    render(<Balance valueString={'123.456'} convertToUnit={SATS} showBalance={true} />)
+    expect(screen.getByTestId('sats-amount')).toHaveTextContent(`12,345,600,000`)
+    expect(screen.getByTestId('sats-symbol')).toBeVisible()
+    expect(screen.queryByTestId('bitcoin-symbol')).not.toBeInTheDocument()
+    expect(screen.queryByTestId('frozen-symbol')).not.toBeInTheDocument()
   })
 
   it('should hide balance for BTC by default', () => {
     render(<Balance valueString={'123.456'} convertToUnit={BTC} />)
     expect(screen.getByText(`*****`)).toBeInTheDocument()
     expect(screen.queryByTestId('bitcoin-amount')).not.toBeInTheDocument()
+    expect(screen.queryByTestId('bitcoin-symbol')).not.toBeInTheDocument()
   })
 
   it('should hide balance for SATS by default', () => {
     render(<Balance valueString={'123'} convertToUnit={SATS} />)
     expect(screen.getByText(`*****`)).toBeInTheDocument()
     expect(screen.queryByTestId(`sats-amount`)).not.toBeInTheDocument()
+    expect(screen.queryByTestId('sats-symbol')).not.toBeInTheDocument()
   })
 
   it('should render a string BTC value correctly as BTC', () => {
     render(<Balance valueString={'123.03224961'} convertToUnit={BTC} showBalance={true} />)
     expect(screen.getByTestId('bitcoin-amount').dataset.formattedValue).toBe(`123.03224961`)
+    expect(screen.getByTestId('bitcoin-symbol')).toBeVisible()
   })
 
   it('should render a string BTC value correctly as SATS', () => {
     render(<Balance valueString={'123.03224961'} convertToUnit={SATS} showBalance={true} />)
     expect(screen.getByTestId(`sats-amount`)).toHaveTextContent(`12,303,224,961`)
+    expect(screen.getByTestId('sats-symbol')).toBeVisible()
   })
 
   it('should render a zero string BTC value correctly as BTC', () => {
@@ -108,6 +123,27 @@ describe('<Balance />', () => {
     expect(screen.getByTestId(`sats-amount`)).toHaveTextContent(`2,100,000,000,000,000`)
   })
 
+  it('should render frozen balance in BTC', () => {
+    render(<Balance valueString={'123.456'} convertToUnit={BTC} showBalance={true} frozen={true} />)
+    expect(screen.getByTestId('bitcoin-amount').dataset.formattedValue).toBe(`123.45600000`)
+    expect(screen.getByTestId('bitcoin-symbol')).toBeVisible()
+    expect(screen.getByTestId('frozen-symbol')).toBeVisible()
+  })
+
+  it('should render frozen balance in SATS', () => {
+    render(<Balance valueString={'123.456'} convertToUnit={SATS} showBalance={true} frozen={true} />)
+    expect(screen.getByTestId('sats-amount')).toHaveTextContent(`12,345,600,000`)
+    expect(screen.getByTestId('sats-symbol')).toBeVisible()
+    expect(screen.getByTestId('frozen-symbol')).toBeVisible()
+  })
+
+  it('should render balance without symbol', () => {
+    render(<Balance valueString={'123.456'} convertToUnit={SATS} showBalance={true} frozen={true} showSymbol={false} />)
+    expect(screen.getByTestId('sats-amount')).toBeVisible()
+    expect(screen.getByTestId('frozen-symbol')).toBeVisible()
+    expect(screen.queryByTestId('sats-symbol')).not.toBeInTheDocument()
+  })
+
   it('should toggle visibility of initially hidden balance on click by default', () => {
     render(<Balance valueString={`21`} convertToUnit={SATS} showBalance={false} />)
     expect(screen.queryByTestId(`sats-amount`)).not.toBeInTheDocument()
diff --git a/src/setupTests.ts b/src/setupTests.ts
index c5d08cf..3cbeeea 100644
--- a/src/setupTests.ts
+++ b/src/setupTests.ts
@@ -25,13 +25,9 @@ global.__DEV__.addToAppSettings = () => {
 ;(function setupWebsocketServerMock() {
   global.__DEV__.JM_WEBSOCKET_SERVER_MOCK = new WebSocketServer('ws://localhost/jmws', { jsonProtocol: true })
 
-  afterEach(() => {
-    // gracefully close all open connections and reset the environment between test runs
-    WebSocketServer.clean()
-  })
-
   afterAll(() => {
     global.__DEV__.JM_WEBSOCKET_SERVER_MOCK.close()
+    WebSocketServer.clean()
   })
 })()