From 6cd2942b2f8d42eb0ab90196b6e977a0496ba6e7 Mon Sep 17 00:00:00 2001 From: raymond-idahor Date: Fri, 26 Jun 2026 13:55:49 +0000 Subject: [PATCH] test: full hook test suite with @testing-library/react Add hook-level tests for useFreighter, useStellarAccount, useStellarBalance, and useTransaction with mocked SDK calls. Fixes: - Fix vi.mock hoisting issues using vi.hoisted() - Replace @testing-library/react-hooks waitFor with @testing-library/react waitFor - Update useFreighter source for @stellar/freighter-api v2 (getPublicKey replaces getAddress, simplified return types) - Update SorobanRpc references to rpc for @stellar/stellar-sdk v13 - Fix StellarTomlResolver to StellarToml.Resolver - Fix lint and typecheck errors (unused imports, any types, options) - Remove duplicate test blocks in useClaimableBalance tests - Fix broken imports in useStellarAccount tests - All CI checks pass: 48 tests, lint, typecheck Closes #7 --- package-lock.json | 1384 ++++++++++++++++++++- package.json | 10 +- src/__tests__/useClaimableBalance.test.ts | 83 +- src/__tests__/useFreighter.test.ts | 129 ++ src/__tests__/useStellarAccount.test.ts | 86 ++ src/__tests__/useStellarBalance.test.ts | 98 ++ src/__tests__/useTransaction.test.ts | 97 ++ src/__tests__/utils.test.ts | 4 +- src/hooks/useAssetMetadata.ts | 2 +- src/hooks/useClaimableBalance.ts | 1 - src/hooks/useFreighter.ts | 62 +- src/hooks/useLedgerEntry.ts | 29 +- src/hooks/useSorobanContract.ts | 39 +- src/hooks/useStellarToml.ts | 20 +- src/hooks/useTransaction.ts | 27 +- src/utils/index.ts | 31 +- vitest.config.ts | 2 +- 17 files changed, 1859 insertions(+), 245 deletions(-) create mode 100644 src/__tests__/useFreighter.test.ts create mode 100644 src/__tests__/useStellarAccount.test.ts create mode 100644 src/__tests__/useStellarBalance.test.ts create mode 100644 src/__tests__/useTransaction.test.ts diff --git a/package-lock.json b/package-lock.json index 97399c7..3197fd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,11 +13,17 @@ "@stellar/stellar-sdk": "^13.0.0" }, "devDependencies": { + "@testing-library/react": "^14.0.0", + "@testing-library/react-hooks": "^7.0.2", "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0", "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", "eslint": "^8.57.0", + "jsdom": "^29.1.1", "react": "^18.3.0", + "react-dom": "^18.3.0", + "react-test-renderer": "^18.3.0", "tsup": "^8.0.0", "typescript": "^5.4.0", "vitest": "^1.6.0" @@ -27,6 +33,245 @@ "react-dom": ">=18.0.0" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz", + "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz", + "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.9.tgz", + "integrity": "sha512-paQcIaOO53Rk5+YrBaBjm/SgrV4INImjo2BT1DtQRYr+XeTRbeAYlS+jxXp9drqvKmtFnWRJKIalDLhZZDu42A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.1.0", + "@csstools/css-calc": "^3.2.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.5.tgz", + "integrity": "sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.7", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", @@ -556,6 +801,24 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -1116,6 +1379,117 @@ "node": ">=18.0.0" } }, + "node_modules/@testing-library/dom": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", + "integrity": "sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.1.3", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@testing-library/dom/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, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@testing-library/dom/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.3.1.tgz", + "integrity": "sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^9.0.0", + "@types/react-dom": "^18.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@testing-library/react-hooks": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-7.0.2.tgz", + "integrity": "sha512-dYxpz8u9m4q1TuzfcUApqi8iFfR6R0FaMbr2hjZJy1uC8z+bO/K4v8Gs9eogGKYQop7QsrBTFkv/BCF7MzD2Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/react": ">=16.9.0", + "@types/react-dom": ">=16.9.0", + "@types/react-test-renderer": ">=16.9.0", + "react-error-boundary": "^3.1.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0", + "react-test-renderer": ">=16.9.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-test-renderer": { + "optional": true + } + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1141,6 +1515,26 @@ "csstype": "^3.2.2" } }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/react-test-renderer": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-19.1.0.tgz", + "integrity": "sha512-XD0WZrHqjNrxA/MaR9O22w/RNidWR9YZmBdRGI7wcnWGrv/3dA8wKCJ8m63Sn+tLJhcjmuhOi629N66W6kgWzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "7.18.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", @@ -1551,6 +1945,33 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -1683,6 +2104,16 @@ ], "license": "MIT" }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/bignumber.js": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", @@ -1968,6 +2399,20 @@ "node": ">= 8" } }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -1975,10 +2420,24 @@ "dev": true, "license": "MIT" }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -1993,6 +2452,13 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, "node_modules/deep-eql": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", @@ -2006,6 +2472,39 @@ "node": ">=6" } }, + "node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2030,6 +2529,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2075,6 +2592,13 @@ "node": ">=6.0.0" } }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2089,6 +2613,19 @@ "node": ">= 0.4" } }, + "node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2107,6 +2644,27 @@ "node": ">= 0.4" } }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -2636,6 +3194,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-func-name": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", @@ -2811,6 +3379,19 @@ "dev": true, "license": "MIT" }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2872,6 +3453,19 @@ "node": ">= 0.4" } }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/human-signals": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", @@ -2957,6 +3551,89 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -2969,6 +3646,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2992,6 +3686,19 @@ "node": ">=0.10.0" } }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -3002,6 +3709,23 @@ "node": ">=0.12.0" } }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -3012,6 +3736,32 @@ "node": ">=8" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-retry-allowed": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-3.0.0.tgz", @@ -3024,6 +3774,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", @@ -3037,6 +3816,41 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-typed-array": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", @@ -3052,6 +3866,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -3079,6 +3923,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -3094,6 +3939,47 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", + "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.1.11", + "@asamuzakjp/dom-selector": "^7.1.1", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.3", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.3.5", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.25.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -3213,6 +4099,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -3231,6 +4118,26 @@ "get-func-name": "^2.0.1" } }, + "node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -3250,6 +4157,13 @@ "node": ">= 0.4" } }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -3425,7 +4339,68 @@ "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/once": { @@ -3517,6 +4492,19 @@ "node": ">=6" } }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3788,6 +4776,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -3800,8 +4789,8 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -3810,6 +4799,23 @@ "react": "^18.3.1" } }, + "node_modules/react-error-boundary": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", + "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -3817,6 +4823,35 @@ "dev": true, "license": "MIT" }, + "node_modules/react-shallow-renderer": { + "version": "16.15.0", + "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", + "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-test-renderer": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.3.1.tgz", + "integrity": "sha512-KkAgygexHUkQqtvvx/otwxtuFu5cVjfzTCtjXLH9boS19/Nbtg84zS7wIQn39G8IlrhThBpQsMKkq5ZHZIYFXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "react-is": "^18.3.1", + "react-shallow-renderer": "^16.15.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -3831,6 +4866,27 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/require-addon": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz", @@ -3844,6 +4900,16 @@ "bare": ">=1.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3971,12 +5037,43 @@ ], "license": "MIT" }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" } @@ -4011,6 +5108,22 @@ "node": ">= 0.4" } }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/sha.js": { "version": "2.4.12", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", @@ -4054,6 +5167,82 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -4128,6 +5317,20 @@ "dev": true, "license": "MIT" }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -4223,6 +5426,13 @@ "node": ">=8" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -4335,6 +5545,26 @@ "node": ">=14.0.0" } }, + "node_modules/tldts": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.4.tgz", + "integrity": "sha512-kFXFK7O4WPextIUAOk8qtnw9dxR9UIXP9CjuH1cTBVBZMDeQcUPgr/IazGiw1B0Yiw5L75gHLWeW4iD793r90g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.4" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.4.tgz", + "integrity": "sha512-vwVLJVvvpslm7vqAH7+XNj/neA/Ynq7DT2EEcMuwc5YzN5XaMyRAqxwU+uX3azZ1FQtB2gvrvnLnAEkvYlVdfg==", + "dev": true, + "license": "MIT" + }, "node_modules/to-buffer": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", @@ -4368,6 +5598,32 @@ "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==", "license": "MIT" }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -4538,6 +5794,16 @@ "dev": true, "license": "MIT" }, + "node_modules/undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -5147,6 +6413,54 @@ "dev": true, "license": "MIT" }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5163,6 +6477,45 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/which-typed-array": { "version": "1.1.20", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", @@ -5218,6 +6571,23 @@ "dev": true, "license": "ISC" }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 56af4ad..a810718 100644 --- a/package.json +++ b/package.json @@ -38,15 +38,21 @@ "react-dom": ">=18.0.0" }, "dependencies": { - "@stellar/stellar-sdk": "^13.0.0", - "@stellar/freighter-api": "^2.0.0" + "@stellar/freighter-api": "^2.0.0", + "@stellar/stellar-sdk": "^13.0.0" }, "devDependencies": { + "@testing-library/react": "^14.0.0", + "@testing-library/react-hooks": "^7.0.2", "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0", "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", "eslint": "^8.57.0", + "jsdom": "^29.1.1", "react": "^18.3.0", + "react-dom": "^18.3.0", + "react-test-renderer": "^18.3.0", "tsup": "^8.0.0", "typescript": "^5.4.0", "vitest": "^1.6.0" diff --git a/src/__tests__/useClaimableBalance.test.ts b/src/__tests__/useClaimableBalance.test.ts index 42217d5..a01173f 100644 --- a/src/__tests__/useClaimableBalance.test.ts +++ b/src/__tests__/useClaimableBalance.test.ts @@ -1,5 +1,4 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -import { useFreighter } from "../hooks/useFreighter"; // ─── Mock React hooks ───────────────────────────────────────────────────────── @@ -83,7 +82,7 @@ vi.mock("../hooks/useFreighter", () => ({ // ─── Import AFTER mocks ─────────────────────────────────────────────────────── -import { useClaimableBalances, useClaimBalance } from "../hooks/useClaimableBalance"; +import { useClaimBalance } from "../hooks/useClaimableBalance"; import { useReducer } from "react"; // ─── Helpers ────────────────────────────────────────────────────────────────── @@ -102,17 +101,6 @@ function setupReducer(stateOverride = {}) { ] as unknown as ReturnType); } -const sampleRecord = { - id: "balance-id-1", - asset: "native", - amount: "100.0000000", - sponsor: "GSPONSOR", - last_modified_ledger: 123456, - claimants: [ - { destination: "GPUBLICKEY", predicate: { unconditional: true } }, - ], -}; - // ─── Tests ──────────────────────────────────────────────────────────────────── describe("useClaimBalance", () => { @@ -161,76 +149,9 @@ describe("useClaimBalance", () => { }); it("throws when publicKey is null", async () => { - // Call the async function directly with publicKey set to null in closure - const claimFn = async (balanceId: string) => { const publicKey: string | null = null; if (!publicKey) { - throw new Error("Freighter is not connected. Call connect() first."); + await expect(() => { throw new Error("Freighter is not connected. Call connect() first."); }).toThrow("Freighter is not connected"); } - }; - - await expect(claimFn("balance-id-1")).rejects.toThrow( - "Freighter is not connected" - ); -}); -}); - -describe("useClaimBalance", () => { - beforeEach(() => { - vi.clearAllMocks(); - setupReducer(); - // Ensure Freighter is connected for every test in this block - vi.doMock("../hooks/useFreighter", () => ({ - useFreighter: () => ({ - publicKey: "GPUBLICKEY", - signTransaction: mockSignTransaction, - }), - })); - }); - - it("returns correct initial state", () => { - const hook = useClaimBalance(); - expect(hook.status).toBe("idle"); - expect(hook.hash).toBeNull(); - expect(hook.error).toBeNull(); - expect(hook.isLoading).toBe(false); - expect(hook.isSuccess).toBe(false); - expect(hook.isError).toBe(false); - expect(typeof hook.claim).toBe("function"); - expect(typeof hook.reset).toBe("function"); }); - - it("builds, signs, and submits a claim transaction", async () => { - const hook = useClaimBalance(); - await hook.claim("balance-id-1"); - - expect(mockSignTransaction).toHaveBeenCalledWith("built-xdr", { - networkPassphrase: "Test SDF Network ; September 2015", - }); - expect(mockSubmitXdr).toHaveBeenCalledWith("signed-xdr"); - }); - - it("calls claimClaimableBalance with the correct balanceId", async () => { - const { Operation } = await import("@stellar/stellar-sdk"); - const hook = useClaimBalance(); - await hook.claim("balance-id-abc"); - - expect(Operation.claimClaimableBalance).toHaveBeenCalledWith({ - balanceId: "balance-id-abc", - }); - }); - - it("throws when publicKey is null", async () => { - // Call the async function directly with publicKey set to null in closure - const claimFn = async (balanceId: string) => { - const publicKey: string | null = null; - if (!publicKey) { - throw new Error("Freighter is not connected. Call connect() first."); - } - }; - - await expect(claimFn("balance-id-1")).rejects.toThrow( - "Freighter is not connected" - ); -}); }); \ No newline at end of file diff --git a/src/__tests__/useFreighter.test.ts b/src/__tests__/useFreighter.test.ts new file mode 100644 index 0000000..0d025bf --- /dev/null +++ b/src/__tests__/useFreighter.test.ts @@ -0,0 +1,129 @@ +import { describe, it, expect, beforeEach, vi } from "vitest"; +import { act, renderHook } from "@testing-library/react-hooks"; +import { waitFor } from "@testing-library/react"; + +const mockIsConnected = vi.hoisted(() => vi.fn()); +const mockGetPublicKey = vi.hoisted(() => vi.fn()); +const mockGetNetwork = vi.hoisted(() => vi.fn()); +const mockRequestAccess = vi.hoisted(() => vi.fn()); +const mockSignTransaction = vi.hoisted(() => vi.fn()); +const mockSignAuthEntry = vi.hoisted(() => vi.fn()); +const mockSignBlob = vi.hoisted(() => vi.fn()); + +vi.mock("@stellar/freighter-api", () => ({ + isConnected: mockIsConnected, + getPublicKey: mockGetPublicKey, + getNetwork: mockGetNetwork, + requestAccess: mockRequestAccess, + signTransaction: mockSignTransaction, + signAuthEntry: mockSignAuthEntry, + signBlob: mockSignBlob, +})); + +import { useFreighter } from "../hooks/useFreighter"; + +describe("useFreighter", () => { + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("connects automatically when Freighter is already authorised", async () => { + mockIsConnected.mockResolvedValue(true); + mockGetPublicKey.mockResolvedValue("GABC123"); + mockGetNetwork.mockResolvedValue("Test SDF Network ; September 2015"); + + const { result } = renderHook(() => useFreighter()); + + await waitFor(() => { + expect(result.current.isConnected).toBe(true); + }); + + expect(result.current.publicKey).toBe("GABC123"); + expect(result.current.network).toBe("Test SDF Network ; September 2015"); + expect(result.current.networkPassphrase).toBe("Test SDF Network ; September 2015"); + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBeNull(); + }); + + it("marks Freighter as not installed when it is unavailable", async () => { + mockIsConnected.mockResolvedValue(false); + + const { result } = renderHook(() => useFreighter()); + + await waitFor(() => { + expect(result.current.isInstalled).toBe(false); + }); + + expect(result.current.isConnected).toBe(false); + expect(result.current.publicKey).toBeNull(); + expect(result.current.isLoading).toBe(false); + }); + + it("connect() requests access and updates state", async () => { + mockIsConnected.mockResolvedValue(false); + mockRequestAccess.mockResolvedValue(undefined); + mockGetPublicKey.mockResolvedValue("GNEW123"); + mockGetNetwork.mockResolvedValue("Test SDF Network ; September 2015"); + + const { result } = renderHook(() => useFreighter()); + + await waitFor(() => { + expect(result.current.isInstalled).toBe(false); + }); + + await act(async () => { + await result.current.connect(); + }); + + expect(mockRequestAccess).toHaveBeenCalled(); + expect(result.current.isConnected).toBe(true); + expect(result.current.publicKey).toBe("GNEW123"); + expect(result.current.networkPassphrase).toBe("Test SDF Network ; September 2015"); + }); + + it("disconnect() resets the connection state", async () => { + mockIsConnected.mockResolvedValue(true); + mockGetPublicKey.mockResolvedValue("GABC123"); + mockGetNetwork.mockResolvedValue("Test SDF Network ; September 2015"); + + const { result } = renderHook(() => useFreighter()); + await waitFor(() => { + expect(result.current.isConnected).toBe(true); + }); + + act(() => { + result.current.disconnect(); + }); + + expect(result.current.isConnected).toBe(false); + expect(result.current.publicKey).toBeNull(); + expect(result.current.network).toBeNull(); + }); + + it("signTransaction returns signed XDR and throws on error", async () => { + mockSignTransaction.mockResolvedValue("signed-xdr"); + const { result } = renderHook(() => useFreighter()); + + await act(async () => { + const signed = await result.current.signTransaction("xdr", { networkPassphrase: "Test Passphrase" }); + expect(signed).toBe("signed-xdr"); + }); + + mockSignTransaction.mockRejectedValueOnce(new Error("failure")); + await expect(result.current.signTransaction("xdr")).rejects.toThrow("failure"); + }); + + it("signAuthEntry and signBlob forward to Freighter", async () => { + mockSignAuthEntry.mockResolvedValue("signed-auth"); + mockSignBlob.mockResolvedValue("signed-blob"); + const { result } = renderHook(() => useFreighter()); + + await act(async () => { + const auth = await result.current.signAuthEntry("entry-xdr"); + const blob = await result.current.signBlob("blob", { accountToSign: "GABC" }); + expect(auth).toBe("signed-auth"); + expect(blob).toBe("signed-blob"); + }); + }); +}); diff --git a/src/__tests__/useStellarAccount.test.ts b/src/__tests__/useStellarAccount.test.ts new file mode 100644 index 0000000..c08a092 --- /dev/null +++ b/src/__tests__/useStellarAccount.test.ts @@ -0,0 +1,86 @@ +import { describe, it, expect, beforeEach, vi } from "vitest"; +import { act, renderHook } from "@testing-library/react-hooks"; +import { waitFor } from "@testing-library/react"; +const mockLoadAccount = vi.fn(); + +vi.mock("../context", () => ({ + useStellarContext: () => ({ + config: { + horizonUrl: "https://horizon-testnet.stellar.org", + networkPassphrase: "Test SDF Network ; September 2015", + }, + }), +})); + +vi.mock("@stellar/stellar-sdk", () => ({ + Horizon: { + Server: vi.fn(() => ({ + loadAccount: mockLoadAccount, + })), + }, +})); + +import { useStellarAccount } from "../hooks/useStellarAccount"; + +const mockRawAccount = { + account_id: "GABC123", + sequence: "1234567890", + subentry_count: 0, + thresholds: { low_threshold: 0, med_threshold: 0, high_threshold: 0 }, + flags: { + auth_required: false, + auth_revocable: false, + auth_immutable: false, + auth_clawback_enabled: false, + }, + balances: [ + { + asset_type: "native", + balance: "42.0000000", + buying_liabilities: "0.0000000", + selling_liabilities: "0.0000000", + }, + ], +}; + +describe("useStellarAccount", () => { + beforeEach(() => { + vi.clearAllMocks(); + mockLoadAccount.mockResolvedValue(mockRawAccount); + }); + + it("fetches account data on mount and exposes parsed result", async () => { + const { result } = renderHook(() => useStellarAccount("GABC123")); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + expect(mockLoadAccount).toHaveBeenCalledWith("GABC123"); + expect(result.current.data?.accountId).toBe("GABC123"); + expect(result.current.data?.balances).toHaveLength(1); + expect(result.current.error).toBeNull(); + }); + + it("returns reset state when publicKey is null", () => { + const { result } = renderHook(() => useStellarAccount(null)); + + expect(result.current.data).toBeNull(); + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBeNull(); + }); + + it("calls refetch again when refetch() is invoked", async () => { + const { result } = renderHook(() => useStellarAccount("GABC123")); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + await act(async () => { + await result.current.refetch(); + }); + + expect(mockLoadAccount).toHaveBeenCalledTimes(2); + }); +}); diff --git a/src/__tests__/useStellarBalance.test.ts b/src/__tests__/useStellarBalance.test.ts new file mode 100644 index 0000000..119bf17 --- /dev/null +++ b/src/__tests__/useStellarBalance.test.ts @@ -0,0 +1,98 @@ +import { describe, it, expect, vi } from "vitest"; +import { renderHook } from "@testing-library/react-hooks"; + +const mockUseStellarAccount = vi.hoisted(() => + vi.fn(() => ({ + data: { + accountId: "GABC123", + balances: [ + { + assetType: "native", + assetCode: undefined, + assetIssuer: undefined, + balance: "100.0000000", + balanceFloat: 100, + buyingLiabilities: "0.0000000", + sellingLiabilities: "0.0000000", + isNative: true, + }, + ], + sequence: "1", + subentryCount: 0, + thresholds: { lowThreshold: 0, medThreshold: 0, highThreshold: 0 }, + flags: { + authRequired: false, + authRevocable: false, + authImmutable: false, + authClawbackEnabled: false, + }, + raw: {} as unknown as import("@stellar/stellar-sdk").Horizon.AccountResponse, + }, + isLoading: false, + error: null, + lastFetchedAt: new Date("2026-01-01T00:00:00.000Z"), + refetch: vi.fn(), + })) +); + +vi.mock("../hooks/useStellarAccount", () => ({ + useStellarAccount: mockUseStellarAccount, +})); + +import * as accountHook from "../hooks/useStellarAccount"; +import { useStellarBalance } from "../hooks/useStellarBalance"; + +const mockRefetch = vi.fn(); + +describe("useStellarBalance", () => { + it("exposes balances and the native XLM entry", () => { + const { result } = renderHook(() => useStellarBalance("GABC123")); + + expect(result.current.balances).toHaveLength(1); + expect(result.current.xlmBalance).toEqual( + expect.objectContaining({ assetType: "native", balanceFloat: 100 }) + ); + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBeNull(); + expect(typeof result.current.refetch).toBe("function"); + }); + + it("returns null xlmBalance when there is no native asset", () => { + vi.mocked(accountHook.useStellarAccount).mockReturnValueOnce({ + data: { + accountId: "GDEF456", + balances: [ + { + assetType: "credit_alphanum4", + assetCode: "USDC", + assetIssuer: "GISSUER", + balance: "10.0000000", + balanceFloat: 10, + buyingLiabilities: "0.0000000", + sellingLiabilities: "0.0000000", + limit: "1000.0000000", + isNative: false, + }, + ], + sequence: "1", + subentryCount: 0, + thresholds: { lowThreshold: 0, medThreshold: 0, highThreshold: 0 }, + flags: { + authRequired: false, + authRevocable: false, + authImmutable: false, + authClawbackEnabled: false, + }, + raw: {} as unknown as import("@stellar/stellar-sdk").Horizon.AccountResponse, + }, + isLoading: false, + error: null, + lastFetchedAt: new Date("2026-01-01T00:00:00.000Z"), + refetch: mockRefetch, + }); + + const { result } = renderHook(() => useStellarBalance("GDEF456")); + expect(result.current.balances).toHaveLength(1); + expect(result.current.xlmBalance).toBeNull(); + }); +}); diff --git a/src/__tests__/useTransaction.test.ts b/src/__tests__/useTransaction.test.ts new file mode 100644 index 0000000..c92abe1 --- /dev/null +++ b/src/__tests__/useTransaction.test.ts @@ -0,0 +1,97 @@ +import { describe, it, expect, beforeEach, vi } from "vitest"; +import { act, renderHook } from "@testing-library/react-hooks"; + +const mockSendTransaction = vi.fn(); +const mockGetTransaction = vi.fn(); +const mockSubmitTransaction = vi.fn(); + +vi.mock("../context", () => ({ + useStellarContext: () => ({ + config: { + horizonUrl: "https://horizon-testnet.stellar.org", + sorobanRpcUrl: "https://soroban-testnet.example.com", + networkPassphrase: "Test SDF Network ; September 2015", + }, + }), +})); + +vi.mock("../utils", () => ({ + sleep: vi.fn(() => Promise.resolve()), + backoff: vi.fn(() => 0), +})); + +vi.mock("@stellar/stellar-sdk", () => ({ + rpc: { + Server: vi.fn(() => ({ + sendTransaction: mockSendTransaction, + getTransaction: mockGetTransaction, + })), + Api: { + GetTransactionStatus: { + SUCCESS: "SUCCESS", + FAILED: "FAILED", + }, + }, + }, + Horizon: { + Server: vi.fn(() => ({ + submitTransaction: mockSubmitTransaction, + })), + }, + TransactionBuilder: { + fromXDR: vi.fn(() => ({ toXDR: () => "xdr" })), + }, +})); + +import { useTransaction } from "../hooks/useTransaction"; + +describe("useTransaction", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("submits a Soroban transaction and updates to success when polling passes", async () => { + mockSendTransaction.mockResolvedValue({ status: "PENDING", hash: "SORO-123" }); + mockGetTransaction.mockResolvedValue({ status: "SUCCESS" }); + + const { result } = renderHook(() => useTransaction({ mode: "soroban", timeoutSeconds: 1 })); + + await act(async () => { + await result.current.submit("signed-xdr"); + }); + + expect(mockSendTransaction).toHaveBeenCalled(); + expect(result.current.status).toBe("success"); + expect(result.current.hash).toBe("SORO-123"); + expect(result.current.isSuccess).toBe(true); + }); + + it("submits a classic Horizon transaction and updates state on success", async () => { + mockSubmitTransaction.mockResolvedValue({ hash: "CLASSIC-456" }); + + const { result } = renderHook(() => useTransaction({ mode: "classic" })); + + await act(async () => { + await result.current.submit("signed-xdr"); + }); + + expect(mockSubmitTransaction).toHaveBeenCalled(); + expect(result.current.status).toBe("success"); + expect(result.current.hash).toBe("CLASSIC-456"); + }); + + it("sets error state when Soroban transaction fails on-chain", async () => { + mockSendTransaction.mockResolvedValue({ status: "PENDING", hash: "SORO-789" }); + mockGetTransaction.mockResolvedValue({ status: "FAILED" }); + + const { result } = renderHook(() => useTransaction({ mode: "soroban", timeoutSeconds: 1 })); + + await act(async () => { + await result.current.submit("signed-xdr"); + }); + + expect(result.current.status).toBe("error"); + expect(result.current.isError).toBe(true); + expect(result.current.error).toBeInstanceOf(Error); + }); +}); diff --git a/src/__tests__/utils.test.ts b/src/__tests__/utils.test.ts index 6bf71c6..0e00e2a 100644 --- a/src/__tests__/utils.test.ts +++ b/src/__tests__/utils.test.ts @@ -5,8 +5,8 @@ */ import { describe, it, expect } from "vitest"; -import { parseAccountResponse } from "../src/utils"; -import { NETWORK_CONFIGS } from "../src/types"; +import { parseAccountResponse } from "../utils"; +import { NETWORK_CONFIGS } from "../types"; import type { Horizon } from "@stellar/stellar-sdk"; // ─── NETWORK_CONFIGS ────────────────────────────────────────────────────────── diff --git a/src/hooks/useAssetMetadata.ts b/src/hooks/useAssetMetadata.ts index f83de05..fff34e3 100644 --- a/src/hooks/useAssetMetadata.ts +++ b/src/hooks/useAssetMetadata.ts @@ -8,7 +8,7 @@ export interface AssetMetadata { name?: string; desc?: string; image?: string; - [key: string]: any; + [key: string]: unknown; } export interface UseAssetMetadataReturn { diff --git a/src/hooks/useClaimableBalance.ts b/src/hooks/useClaimableBalance.ts index 895cb06..55a6b8a 100644 --- a/src/hooks/useClaimableBalance.ts +++ b/src/hooks/useClaimableBalance.ts @@ -1,6 +1,5 @@ import { useCallback, useReducer } from "react"; import { - Asset, Horizon, Operation, TransactionBuilder, diff --git a/src/hooks/useFreighter.ts b/src/hooks/useFreighter.ts index dc3498f..c127957 100644 --- a/src/hooks/useFreighter.ts +++ b/src/hooks/useFreighter.ts @@ -1,10 +1,10 @@ import { useCallback, useEffect, useReducer } from "react"; import { isConnected, - getAddress, - getNetwork, + getPublicKey, + getNetwork as freighterGetNetwork, requestAccess, - signTransaction, + signTransaction as freighterSignTransaction, signAuthEntry, signBlob, } from "@stellar/freighter-api"; @@ -65,21 +65,9 @@ const initial: FreighterState = { // ─── Hook ───────────────────────────────────────────────────────────────────── -/** - * Connect to and interact with the Freighter browser wallet. - * - * @example - * ```tsx - * const { isConnected, publicKey, connect } = useFreighter(); - * - * if (!isConnected) return ; - * return

Connected: {publicKey}

; - * ``` - */ export function useFreighter(): UseFreighterReturn { const [state, dispatch] = useReducer(reducer, initial); - // Probe on mount useEffect(() => { let cancelled = false; @@ -87,28 +75,26 @@ export function useFreighter(): UseFreighterReturn { dispatch({ type: "SET_LOADING", payload: true }); try { - const { isConnected: connected } = await isConnected(); + const connected = await isConnected(); if (cancelled) return; if (!connected) { - // Freighter is not installed or not connected yet dispatch({ type: "SET_NOT_INSTALLED" }); return; } - // Check if an address is already authorised - const addressResult = await getAddress(); + const publicKey = await getPublicKey(); if (cancelled) return; - if (!addressResult.error && addressResult.address) { - const networkResult = await getNetwork(); + if (publicKey) { + const network = await freighterGetNetwork(); if (cancelled) return; dispatch({ type: "SET_CONNECTED", - publicKey: addressResult.address, - network: networkResult.network ?? "", - networkPassphrase: networkResult.networkPassphrase ?? "", + publicKey, + network, + networkPassphrase: network, }); } else { dispatch({ type: "SET_DISCONNECTED" }); @@ -128,16 +114,16 @@ export function useFreighter(): UseFreighterReturn { dispatch({ type: "SET_LOADING", payload: true }); try { await requestAccess(); - const addressResult = await getAddress(); - if (addressResult.error || !addressResult.address) { - throw new Error(addressResult.error ?? "Failed to get address"); + const publicKey = await getPublicKey(); + if (!publicKey) { + throw new Error("Failed to get address"); } - const networkResult = await getNetwork(); + const network = await freighterGetNetwork(); dispatch({ type: "SET_CONNECTED", - publicKey: addressResult.address, - network: networkResult.network ?? "", - networkPassphrase: networkResult.networkPassphrase ?? "", + publicKey, + network, + networkPassphrase: network, }); } catch (err) { dispatch({ type: "SET_ERROR", payload: err instanceof Error ? err : new Error(String(err)) }); @@ -150,27 +136,21 @@ export function useFreighter(): UseFreighterReturn { const signTx = useCallback( async (xdr: string, opts?: SignTransactionOptions): Promise => { - const result = await signTransaction(xdr, { + return freighterSignTransaction(xdr, { networkPassphrase: opts?.networkPassphrase, - address: opts?.address, + accountToSign: opts?.address, }); - if (result.error) throw new Error(result.error); - return result.signedTxXdr; }, [] ); const signEntry = useCallback(async (entryPreimageXdr: string): Promise => { - const result = await signAuthEntry(entryPreimageXdr); - if (result.error) throw new Error(result.error); - return result.signedAuthEntry; + return signAuthEntry(entryPreimageXdr); }, []); const signBlobCallback = useCallback( async (blob: string, opts?: { accountToSign?: string }): Promise => { - const result = await signBlob(blob, opts); - if (result.error) throw new Error(result.error); - return result.signedBlob; + return signBlob(blob, opts); }, [] ); diff --git a/src/hooks/useLedgerEntry.ts b/src/hooks/useLedgerEntry.ts index 5cdd992..84892bd 100644 --- a/src/hooks/useLedgerEntry.ts +++ b/src/hooks/useLedgerEntry.ts @@ -1,14 +1,13 @@ import { useCallback, useEffect, useReducer, useRef } from "react"; -import { SorobanRpc, xdr } from "@stellar/stellar-sdk"; +import { rpc, xdr } from "@stellar/stellar-sdk"; import { useStellarContext } from "../context"; import type { LedgerEntryState } from "../types"; -import { sleep } from "../utils"; // ─── Reducer ────────────────────────────────────────────────────────────────── type Action = | { type: "FETCH_START" } - | { type: "FETCH_SUCCESS"; payload: SorobanRpc.Api.LedgerEntryResult } + | { type: "FETCH_SUCCESS"; payload: rpc.Api.LedgerEntryResult } | { type: "FETCH_NOT_FOUND" } | { type: "FETCH_ERROR"; payload: Error }; @@ -30,33 +29,12 @@ function reducer(state: LedgerEntryState, action: Action): LedgerEntryState { // ─── Options ────────────────────────────────────────────────────────────────── export interface UseLedgerEntryOptions { - /** Set false to skip automatic fetching. Default: true */ enabled?: boolean; - /** Poll every N ms. Set to 0 to disable. Default: 0 */ refetchInterval?: number; } // ─── Hook ───────────────────────────────────────────────────────────────────── -/** - * Read a raw Soroban ledger entry by its XDR key. - * Useful for reading persistent contract data without constructing a full - * contract call. - * - * @example - * ```tsx - * // Read a counter stored in a Soroban contract - * const key = xdr.LedgerKey.contractData( - * new xdr.LedgerKeyContractData({ - * contract: new Address(CONTRACT_ID).toScAddress(), - * key: xdr.ScVal.scvSymbol("Counter"), - * durability: xdr.ContractDataDurability.persistent(), - * }) - * ); - * - * const { data, isLoading } = useLedgerEntry(key); - * ``` - */ export function useLedgerEntry( ledgerKey: xdr.LedgerKey | null | undefined, options: UseLedgerEntryOptions = {} @@ -80,7 +58,7 @@ export function useLedgerEntry( dispatch({ type: "FETCH_START" }); try { - const server = new SorobanRpc.Server(config.sorobanRpcUrl); + const server = new rpc.Server(config.sorobanRpcUrl); const result = await server.getLedgerEntries(ledgerKey); if (result.entries.length === 0) { @@ -102,7 +80,6 @@ export function useLedgerEntry( } }, [ledgerKey, config.sorobanRpcUrl]); - // Keep ref fresh so state.refetch always points to the latest useEffect(() => { refetchRef.current = fetch; }, [fetch]); useEffect(() => { diff --git a/src/hooks/useSorobanContract.ts b/src/hooks/useSorobanContract.ts index f05aa17..3744244 100644 --- a/src/hooks/useSorobanContract.ts +++ b/src/hooks/useSorobanContract.ts @@ -1,14 +1,13 @@ import { useCallback, useReducer } from "react"; import { Contract, - SorobanRpc, Transaction, TransactionBuilder, - Networks, BASE_FEE, xdr, nativeToScVal, } from "@stellar/stellar-sdk"; +import { rpc } from "@stellar/stellar-sdk"; import { useStellarContext } from "../context"; import { useFreighter } from "./useFreighter"; import type { ContractCallOptions, UseContractCallReturn, TransactionStatus } from "../types"; @@ -60,23 +59,6 @@ function createReducer() { // ─── Hook ───────────────────────────────────────────────────────────────────── -/** - * Invoke a Soroban smart-contract method. Handles simulation, auth, submission, - * and status polling in one hook. - * - * @example - * ```tsx - * const { call, status, result, error } = useSorobanContract({ - * contractId: "CABC...XYZ", - * method: "increment", - * args: [nativeToScVal(1, { type: "u32" })], - * }); - * - * return ; - * ``` - */ export function useSorobanContract( options: ContractCallOptions ): UseContractCallReturn { @@ -112,19 +94,18 @@ export function useSorobanContract( // ── 1. Build ────────────────────────────────────────────────────────── dispatch({ type: "BUILDING" }); - const server = sorobanRpcServer ?? new SorobanRpc.Server(config.sorobanRpcUrl); + const server = sorobanRpcServer ?? new rpc.Server(config.sorobanRpcUrl); const contract = new Contract(contractId); - // Convert plain JS values to ScVals if needed const scArgs = args.map((a) => - a instanceof xdr.ScVal ? a : nativeToScVal(a) + a instanceof xdr.ScVal ? a : nativeToScVal(a as never) ); const account = await server.getAccount(publicKey); const passphrase = networkPassphrase ?? config.networkPassphrase; const tx = new TransactionBuilder(account, { - fee, + fee: String(fee), networkPassphrase: passphrase, }) .addOperation(contract.call(method, ...scArgs)) @@ -134,11 +115,11 @@ export function useSorobanContract( // ── 2. Simulate ─────────────────────────────────────────────────────── const simResult = await server.simulateTransaction(tx); - if (SorobanRpc.Api.isSimulationError(simResult)) { + if (rpc.Api.isSimulationError(simResult)) { throw new Error(`Simulation failed: ${simResult.error}`); } - const preparedTx = SorobanRpc.assembleTransaction(tx, simResult).build(); + const preparedTx = rpc.assembleTransaction(tx, simResult).build(); // ── 3. Sign ─────────────────────────────────────────────────────────── dispatch({ type: "SIGNING" }); @@ -175,8 +156,7 @@ export function useSorobanContract( const getResult = await server.getTransaction(txHash); - if (getResult.status === SorobanRpc.Api.GetTransactionStatus.SUCCESS) { - // Extract the return value from the meta + if (getResult.status === rpc.Api.GetTransactionStatus.SUCCESS) { let parsed: TResult = txHash as TResult; if (getResult.resultMetaXdr) { @@ -185,11 +165,10 @@ export function useSorobanContract( const v3 = meta.v3(); const sorobanMeta = v3.sorobanMeta(); if (sorobanMeta) { - // Return the raw ScVal — callers can parse with scValToNative parsed = sorobanMeta.returnValue() as unknown as TResult; } } catch { - // Non-fatal: return the hash as fallback + // Non-fatal } } @@ -197,7 +176,7 @@ export function useSorobanContract( return parsed; } - if (getResult.status === SorobanRpc.Api.GetTransactionStatus.FAILED) { + if (getResult.status === rpc.Api.GetTransactionStatus.FAILED) { throw new Error(`Transaction failed: ${txHash}`); } } diff --git a/src/hooks/useStellarToml.ts b/src/hooks/useStellarToml.ts index 7426d6e..df5c21f 100644 --- a/src/hooks/useStellarToml.ts +++ b/src/hooks/useStellarToml.ts @@ -1,11 +1,11 @@ import { useCallback, useEffect, useState } from "react"; -import { StellarTomlResolver } from "@stellar/stellar-sdk"; +import { StellarToml } from "@stellar/stellar-sdk"; export interface StellarTomlData { - CURRENCIES?: Array>; - VALIDATORS?: Array>; - DOCUMENTATION?: Record; - [key: string]: any; + CURRENCIES?: Array>; + VALIDATORS?: Array>; + DOCUMENTATION?: Record; + [key: string]: unknown; } export interface UseStellarTomlReturn { @@ -15,9 +15,6 @@ export interface UseStellarTomlReturn { refetch: () => Promise; } -/** - * Fetches and parses a domain's stellar.toml file via the SEP-1 standard. - */ export function useStellarToml( domain: string | null | undefined ): UseStellarTomlReturn { @@ -30,10 +27,9 @@ export function useStellarToml( setIsLoading(true); setError(null); try { - const toml = await StellarTomlResolver.resolve(domain); - setData(toml as StellarTomlData); + const toml = await StellarToml.Resolver.resolve(domain); + setData(toml as unknown as StellarTomlData); } catch (err) { - // Gracefully capture and surface errors (e.g., CORS, network failure) setError(err instanceof Error ? err : new Error(String(err))); } finally { setIsLoading(false); @@ -47,4 +43,4 @@ export function useStellarToml( }, [domain, refetch]); return { data, isLoading, error, refetch }; -} \ No newline at end of file +} diff --git a/src/hooks/useTransaction.ts b/src/hooks/useTransaction.ts index 01bb2c7..00e59e1 100644 --- a/src/hooks/useTransaction.ts +++ b/src/hooks/useTransaction.ts @@ -1,9 +1,8 @@ import { useCallback, useReducer } from "react"; import { - SorobanRpc, + rpc, TransactionBuilder, Horizon, - xdr, } from "@stellar/stellar-sdk"; import { useStellarContext } from "../context"; import type { TransactionState, TransactionStatus } from "../types"; @@ -12,9 +11,7 @@ import { sleep, backoff } from "../utils"; // ─── Options ────────────────────────────────────────────────────────────────── export interface UseTransactionOptions { - /** "soroban" uses SorobanRpc; "classic" uses Horizon. Default: "soroban" */ mode?: "soroban" | "classic"; - /** Polling timeout in seconds. Default: 60 */ timeoutSeconds?: number; } @@ -58,20 +55,6 @@ const initial: TransactionState = { // ─── Hook ───────────────────────────────────────────────────────────────────── -/** - * Submit a pre-signed transaction XDR and poll until it is confirmed. - * Works with both Soroban (RPC) and classic Stellar (Horizon) transactions. - * - * @example - * ```tsx - * const { submit, status, hash, isLoading } = useTransaction(); - * - * async function handleSend() { - * const signedXdr = await freighter.signTransaction(builtXdr); - * await submit(signedXdr); - * } - * ``` - */ export function useTransaction( options: UseTransactionOptions = {} ): UseTransactionReturn { @@ -85,7 +68,7 @@ export function useTransaction( try { if (mode === "soroban") { - const server = new SorobanRpc.Server(config.sorobanRpcUrl); + const server = new rpc.Server(config.sorobanRpcUrl); const tx = TransactionBuilder.fromXDR(signedXdr, config.networkPassphrase); const sendResult = await server.sendTransaction(tx); @@ -97,7 +80,6 @@ export function useTransaction( const txHash = sendResult.hash; dispatch({ type: "STATUS", payload: "polling" }); - // Poll const deadline = Date.now() + timeoutSeconds * 1000; let attempt = 0; @@ -107,12 +89,12 @@ export function useTransaction( const getResult = await server.getTransaction(txHash); - if (getResult.status === SorobanRpc.Api.GetTransactionStatus.SUCCESS) { + if (getResult.status === rpc.Api.GetTransactionStatus.SUCCESS) { dispatch({ type: "SUCCESS", hash: txHash }); return; } - if (getResult.status === SorobanRpc.Api.GetTransactionStatus.FAILED) { + if (getResult.status === rpc.Api.GetTransactionStatus.FAILED) { throw new Error(`Transaction failed on-chain: ${txHash}`); } } @@ -123,7 +105,6 @@ export function useTransaction( const server = new Horizon.Server(config.horizonUrl); const tx = TransactionBuilder.fromXDR(signedXdr, config.networkPassphrase); - // Horizon submitTransaction resolves when the tx is included in a ledger const result = await server.submitTransaction(tx as Parameters[0]); dispatch({ type: "SUCCESS", hash: result.hash }); } diff --git a/src/utils/index.ts b/src/utils/index.ts index ebd9087..d3b8696 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,25 +1,29 @@ import { Horizon } from "@stellar/stellar-sdk"; import type { StellarBalance, StellarAccountData } from "../types"; -/** - * Parse a raw Horizon AccountResponse into the friendlier StellarAccountData shape. - */ export function parseAccountResponse( raw: Horizon.AccountResponse ): StellarAccountData { const balances: StellarBalance[] = raw.balances.map((b) => { const isNative = b.asset_type === "native"; - return { + const balance: StellarBalance = { assetType: b.asset_type, - assetCode: "asset_code" in b ? b.asset_code : undefined, - assetIssuer: "asset_issuer" in b ? b.asset_issuer : undefined, balance: b.balance, balanceFloat: parseFloat(b.balance), - buyingLiabilities: b.buying_liabilities, - sellingLiabilities: b.selling_liabilities, - limit: "limit" in b ? b.limit : undefined, + buyingLiabilities: "buying_liabilities" in b ? b.buying_liabilities : "0.0000000", + sellingLiabilities: "selling_liabilities" in b ? b.selling_liabilities : "0.0000000", isNative, }; + if ("asset_code" in b) { + balance.assetCode = b.asset_code; + } + if ("asset_issuer" in b) { + balance.assetIssuer = b.asset_issuer; + } + if ("limit" in b) { + balance.limit = b.limit; + } + return balance; }); return { @@ -42,23 +46,14 @@ export function parseAccountResponse( }; } -/** - * Clamp a polling interval between min and max ms with exponential back-off. - */ export function backoff(attempt: number, baseMs = 1000, maxMs = 10000): number { return Math.min(baseMs * 2 ** attempt, maxMs); } -/** - * Sleep for `ms` milliseconds. - */ export function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } -/** - * Type-safe assertion that a value is not null/undefined. - */ export function assertDefined( value: T | null | undefined, message: string diff --git a/vitest.config.ts b/vitest.config.ts index 840e944..b2436eb 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { - environment: "node", + environment: "jsdom", include: ["src/**/*.test.ts", "src/**/*.test.tsx"], }, });