From c8b556954539afaf8ad260fc58116f06522b2562 Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Sun, 22 Mar 2026 10:59:30 +0000 Subject: [PATCH 1/3] [#246] Frontend test foundation: component tests with Vitest + Testing Library Setup: - Install @testing-library/react, @testing-library/jest-dom, @testing-library/user-event, jsdom - Configure vitest with jsdom environment + jest-dom setup file - Add npm test to CI workflow alongside lint + typecheck Component tests (25 new tests): - NavBar: logo links home, wallet connect renders, nav links present - StoryCard: title, genre, author, story link, moleskine class, plot count, genre prop override, agent badge - StoryGrid: correct card count, empty state, responsive grid classes - FilterBar: all dropdowns render, token toggle, option navigation, click outside closes, active option highlighted - TradingWidget: disconnected returns null, buy/sell toggle, 4-option pay token selector, amount input, tab switching All 69 tests pass (44 existing + 25 new). Fixes #246 Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yml | 1 + package-lock.json | 878 ++++++++++++++++++ package.json | 4 + src/components/__tests__/FilterBar.test.tsx | 66 ++ src/components/__tests__/NavBar.test.tsx | 42 + src/components/__tests__/StoryCard.test.tsx | 96 ++ src/components/__tests__/StoryGrid.test.tsx | 79 ++ .../__tests__/TradingWidget.test.tsx | 103 ++ vitest.config.ts | 10 +- vitest.setup.ts | 1 + 10 files changed, 1279 insertions(+), 1 deletion(-) create mode 100644 src/components/__tests__/FilterBar.test.tsx create mode 100644 src/components/__tests__/NavBar.test.tsx create mode 100644 src/components/__tests__/StoryCard.test.tsx create mode 100644 src/components/__tests__/StoryGrid.test.tsx create mode 100644 src/components/__tests__/TradingWidget.test.tsx create mode 100644 vitest.setup.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1651134d..a0fe84b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,3 +17,4 @@ jobs: - run: npm ci - run: npm run lint - run: npm run typecheck + - run: npm test diff --git a/package-lock.json b/package-lock.json index 5fa6e2a3..75c3807f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,16 +24,27 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "16.1.6", + "jsdom": "^27.0.1", "tailwindcss": "^4", "typescript": "^5", "vitest": "^3.2.4" } }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, "node_modules/@adraffy/ens-normalize": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", @@ -53,6 +64,61 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.2.tgz", + "integrity": "sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.0.0", + "@csstools/css-color-parser": "^4.0.1", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.5" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", + "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.6" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "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/@aws-crypto/crc32": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", @@ -1153,6 +1219,146 @@ "node": ">=6.9.0" } }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "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.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", + "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.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", + "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.0.2", + "@csstools/css-calc": "^3.1.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.1", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.1.tgz", + "integrity": "sha512-BvqN0AMWNAnLk9G8jnUT77D+mUbY/H2b3uDTvg2isJkHaOufUE2R3AOwxWo7VBQKT1lOdwdvorddo2B/lk64+w==", + "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/@emnapi/core": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz", @@ -4233,6 +4439,107 @@ "react": "^18 || ^19" } }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -4244,6 +4551,14 @@ "tslib": "^2.4.0" } }, + "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", + "peer": true + }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -5100,6 +5415,16 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/agentkeepalive": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", @@ -5129,6 +5454,17 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "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, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -5433,6 +5769,16 @@ "node": ">=6.0.0" } }, + "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/bn.js": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", @@ -5760,6 +6106,53 @@ "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/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz", + "integrity": "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^4.1.1", + "@csstools/css-syntax-patches-for-csstree": "^1.0.21", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cssstyle/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -5774,6 +6167,67 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/data-urls": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.1.tgz", + "integrity": "sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^15.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls/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/data-urls/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/data-urls/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/data-urls/node_modules/whatwg-url": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -5846,6 +6300,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": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", @@ -5911,6 +6372,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -5934,6 +6406,14 @@ "node": ">=0.10.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", + "peer": true + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -5977,6 +6457,19 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-abstract": { "version": "1.24.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", @@ -7224,6 +7717,47 @@ "hermes-estree": "0.25.1" } }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", @@ -7242,6 +7776,19 @@ "node": ">=20.0.0" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -7299,6 +7846,16 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -7584,6 +8141,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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", @@ -7907,6 +8471,83 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "27.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.1.tgz", + "integrity": "sha512-SNSQteBL1IlV2zqhwwolaG9CwhIhTvVHWg3kTss/cLE7H/X4644mtPQqYvCfsSrGQWt9hSZcgOXX8bOZaMN+kA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/dom-selector": "^6.7.2", + "cssstyle": "^5.3.1", + "data-urls": "^6.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.1.0", + "ws": "^8.18.3", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/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/jsdom/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/jsdom/node_modules/whatwg-url": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -8334,6 +8975,17 @@ "yallist": "^3.0.2" } }, + "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", + "peer": true, + "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", @@ -8354,6 +9006,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/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -8378,6 +9037,16 @@ "node": ">=8.6" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -8846,6 +9515,19 @@ "node": ">=6" } }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.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", @@ -8977,6 +9659,44 @@ "node": ">= 0.8.0" } }, + "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", + "peer": true, + "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/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, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/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", + "peer": true + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -9048,6 +9768,20 @@ "dev": true, "license": "MIT" }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -9092,6 +9826,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -9225,6 +9969,13 @@ "uuid": "dist/esm/bin/uuid" } }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -9324,6 +10075,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "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.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -9735,6 +10506,19 @@ "node": ">=4" } }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -9838,6 +10622,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/tailwindcss": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", @@ -9956,6 +10747,26 @@ "node": ">=14.0.0" } }, + "node_modules/tldts": { + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.27.tgz", + "integrity": "sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.27" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.27.tgz", + "integrity": "sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -9969,6 +10780,19 @@ "node": ">=8.0" } }, + "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": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -10598,6 +11422,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "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/wagmi": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/wagmi/-/wagmi-3.5.0.tgz", @@ -10762,6 +11599,30 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "license": "BSD-2-Clause" }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -10925,6 +11786,23 @@ } } }, + "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/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index d38ccb21..1e832212 100644 --- a/package.json +++ b/package.json @@ -27,11 +27,15 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "16.1.6", + "jsdom": "^27.0.1", "tailwindcss": "^4", "typescript": "^5", "vitest": "^3.2.4" diff --git a/src/components/__tests__/FilterBar.test.tsx b/src/components/__tests__/FilterBar.test.tsx new file mode 100644 index 00000000..b0f5e911 --- /dev/null +++ b/src/components/__tests__/FilterBar.test.tsx @@ -0,0 +1,66 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { render, screen, fireEvent, cleanup } from "@testing-library/react"; +import { FilterBar } from "../FilterBar"; + +afterEach(cleanup); + +const mockPush = vi.fn(); +vi.mock("next/navigation", () => ({ + useRouter: () => ({ push: mockPush }), +})); + +const defaultProps = { + writer: "all", + genre: "all", + lang: "all", + tab: "new", +}; + +describe("FilterBar", () => { + beforeEach(() => { + mockPush.mockClear(); + }); + + it("renders all filter dropdowns (writer, genre, lang, sort)", () => { + render(); + expect(screen.getByText("writer:")).toBeInTheDocument(); + expect(screen.getByText("genre:")).toBeInTheDocument(); + expect(screen.getByText("lang:")).toBeInTheDocument(); + }); + + it("clicking writer token toggles dropdown", () => { + render(); + const writerBtn = screen.getByText("writer:").closest("button")!; + fireEvent.click(writerBtn); + expect(screen.getByText("Human")).toBeInTheDocument(); + expect(screen.getByText("AI")).toBeInTheDocument(); + }); + + it("selecting an option navigates to correct URL params", () => { + render(); + const writerBtn = screen.getByText("writer:").closest("button")!; + fireEvent.click(writerBtn); + fireEvent.click(screen.getByText("Human")); + expect(mockPush).toHaveBeenCalledWith(expect.stringContaining("writer=human")); + }); + + it("click outside closes dropdown", () => { + render(); + const writerBtn = screen.getByText("writer:").closest("button")!; + fireEvent.click(writerBtn); + expect(screen.getByText("Human")).toBeInTheDocument(); + fireEvent.mouseDown(document.body); + expect(screen.queryByText("Human")).not.toBeInTheDocument(); + }); + + it("active option is highlighted", () => { + render(); + const writerBtn = screen.getByText("writer:").closest("button")!; + fireEvent.click(writerBtn); + // "Human" appears both in the token display and the dropdown + const humanOptions = screen.getAllByText("Human"); + // The dropdown option (button) should have text-accent class + const dropdownOption = humanOptions.find((el) => el.tagName === "BUTTON" && el.closest("[class*='absolute']")); + expect(dropdownOption).toHaveClass("text-accent"); + }); +}); diff --git a/src/components/__tests__/NavBar.test.tsx b/src/components/__tests__/NavBar.test.tsx new file mode 100644 index 00000000..44ba1b19 --- /dev/null +++ b/src/components/__tests__/NavBar.test.tsx @@ -0,0 +1,42 @@ +import { describe, it, expect, vi, afterEach } from "vitest"; +import { render, screen, cleanup } from "@testing-library/react"; +import { NavBar } from "../NavBar"; + +afterEach(cleanup); + +vi.mock("next/link", () => ({ + default: ({ href, children, ...props }: { href: string; children: React.ReactNode; [key: string]: unknown }) => ( + {children} + ), +})); + +vi.mock("next/navigation", () => ({ + usePathname: () => "/", +})); + +vi.mock("../ConnectWallet", () => ({ + ConnectWallet: () => , +})); + +describe("NavBar", () => { + it("renders logo linking to home", () => { + render(); + const logo = screen.getByText("PlotLink"); + expect(logo).toBeInTheDocument(); + expect(logo.closest("a")).toHaveAttribute("href", "/"); + }); + + it("renders wallet connect button", () => { + render(); + const buttons = screen.getAllByTestId("connect-wallet"); + expect(buttons.length).toBeGreaterThan(0); + }); + + it("renders navigation links", () => { + render(); + const createLinks = screen.getAllByText("Create"); + expect(createLinks[0].closest("a")).toHaveAttribute("href", "/create"); + expect(screen.getAllByText("Writer")[0].closest("a")).toHaveAttribute("href", "/dashboard/writer"); + expect(screen.getAllByText("Reader")[0].closest("a")).toHaveAttribute("href", "/dashboard/reader"); + }); +}); diff --git a/src/components/__tests__/StoryCard.test.tsx b/src/components/__tests__/StoryCard.test.tsx new file mode 100644 index 00000000..2301b088 --- /dev/null +++ b/src/components/__tests__/StoryCard.test.tsx @@ -0,0 +1,96 @@ +import { describe, it, expect, vi, afterEach } from "vitest"; +import { render, screen, cleanup } from "@testing-library/react"; +import { StoryCard } from "../StoryCard"; +import type { Storyline } from "../../../lib/supabase"; + +afterEach(cleanup); + +vi.mock("next/link", () => ({ + default: ({ href, children, ...props }: { href: string; children: React.ReactNode; [key: string]: unknown }) => ( + {children} + ), +})); + +vi.mock("../WriterIdentityClient", () => ({ + WriterIdentityClient: ({ address }: { address: string }) => ( + {address.slice(0, 8)} + ), +})); + +vi.mock("../AgentBadge", () => ({ + AgentBadge: () => AI, +})); + +vi.mock("../RatingSummary", () => ({ + RatingSummary: () => null, +})); + +vi.mock("../StoryCardStats", () => ({ + StoryCardTVL: () => TVL, +})); + +function makeStoryline(overrides: Partial = {}): Storyline { + return { + id: 1, + storyline_id: 42, + title: "Test Story Title", + writer_address: "0x1234567890123456789012345678901234567890", + writer_type: 0, + plot_count: 5, + token_address: "0xABCDEF1234567890ABCDEF1234567890ABCDEF12", + genre: "Fiction", + language: "English", + sunset: false, + last_plot_time: null, + created_at: "2026-01-01T00:00:00Z", + ...overrides, + } as Storyline; +} + +describe("StoryCard", () => { + it("renders title correctly", () => { + render(); + // Title appears in the cover h3 + const titles = screen.getAllByText("Test Story Title"); + expect(titles.length).toBeGreaterThan(0); + }); + + it("renders genre badge", () => { + render(); + const genres = screen.getAllByText("Fiction"); + expect(genres.length).toBeGreaterThan(0); + }); + + it("renders author via WriterIdentityClient", () => { + render(); + const writers = screen.getAllByTestId("writer"); + expect(writers.length).toBeGreaterThan(0); + }); + + it("links to correct story page", () => { + render(); + const link = screen.getAllByText("Test Story Title")[0].closest("a"); + expect(link).toHaveAttribute("href", "/story/42"); + }); + + it("applies moleskine-notebook class for hover animation", () => { + render(); + const link = screen.getAllByText("Test Story Title")[0].closest("a"); + expect(link).toHaveClass("moleskine-notebook"); + }); + + it("shows plot count", () => { + render(); + expect(screen.getAllByText(/3 plots? linked/).length).toBeGreaterThan(0); + }); + + it("displays genre prop when provided", () => { + render(); + expect(screen.getAllByText("Sci-Fi").length).toBeGreaterThan(0); + }); + + it("shows agent badge for AI writers", () => { + render(); + expect(screen.getByTestId("agent-badge")).toBeInTheDocument(); + }); +}); diff --git a/src/components/__tests__/StoryGrid.test.tsx b/src/components/__tests__/StoryGrid.test.tsx new file mode 100644 index 00000000..c69a2f50 --- /dev/null +++ b/src/components/__tests__/StoryGrid.test.tsx @@ -0,0 +1,79 @@ +import { describe, it, expect, vi, afterEach } from "vitest"; +import { render, screen, cleanup } from "@testing-library/react"; + +afterEach(cleanup); +import { StoryGrid } from "../StoryGrid"; +import type { Storyline } from "../../../lib/supabase"; + +// Mock Next.js Link +vi.mock("next/link", () => ({ + default: ({ href, children, ...props }: { href: string; children: React.ReactNode; [key: string]: unknown }) => ( + {children} + ), +})); + +// Mock child components +vi.mock("../BatchTokenDataProvider", () => ({ + BatchTokenDataProvider: ({ children }: { children: React.ReactNode }) =>
{children}
, +})); + +vi.mock("../WriterIdentityClient", () => ({ + WriterIdentityClient: () => writer, +})); + +vi.mock("../AgentBadge", () => ({ + AgentBadge: () => null, +})); + +vi.mock("../RatingSummary", () => ({ + RatingSummary: () => null, +})); + +vi.mock("../StoryCardStats", () => ({ + StoryCardTVL: () => null, +})); + +function makeStoryline(id: number, title: string): Storyline { + return { + id, + storyline_id: id, + title, + writer_address: "0x1234567890123456789012345678901234567890", + writer_type: 0, + plot_count: 1, + token_address: `0x${"A".repeat(40)}`, + genre: "Fiction", + language: "English", + sunset: false, + last_plot_time: null, + created_at: "2026-01-01T00:00:00Z", + } as unknown as Storyline; +} + +describe("StoryGrid", () => { + it("renders correct number of story cards", () => { + const storylines = [ + makeStoryline(1, "Story One"), + makeStoryline(2, "Story Two"), + makeStoryline(3, "Story Three"), + ]; + render(); + expect(screen.getByText("Story One")).toBeInTheDocument(); + expect(screen.getByText("Story Two")).toBeInTheDocument(); + expect(screen.getByText("Story Three")).toBeInTheDocument(); + }); + + it("renders empty grid when no storylines", () => { + const { container } = render(); + const grid = container.querySelector(".grid"); + expect(grid).toBeInTheDocument(); + expect(grid?.children.length).toBe(0); + }); + + it("uses responsive grid classes", () => { + const { container } = render(); + const grid = container.querySelector(".grid"); + expect(grid).toHaveClass("grid-cols-2"); + expect(grid).toHaveClass("lg:grid-cols-3"); + }); +}); diff --git a/src/components/__tests__/TradingWidget.test.tsx b/src/components/__tests__/TradingWidget.test.tsx new file mode 100644 index 00000000..f6847dc1 --- /dev/null +++ b/src/components/__tests__/TradingWidget.test.tsx @@ -0,0 +1,103 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { render, screen, fireEvent, cleanup } from "@testing-library/react"; + +afterEach(cleanup); + +// Mock wagmi hooks +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const mockUseAccount = vi.fn((): any => ({ address: undefined, isConnected: false })); +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const mockUseBalance = vi.fn((): any => ({ data: undefined, refetch: vi.fn() })); +vi.mock("wagmi", () => ({ + useAccount: () => mockUseAccount(), + useBalance: () => mockUseBalance(), + useWriteContract: () => ({ writeContractAsync: vi.fn() }), +})); + +vi.mock("@tanstack/react-query", () => ({ + useQuery: () => ({ data: undefined, refetch: vi.fn() }), +})); + +vi.mock("../../../lib/rpc", () => ({ + browserClient: { multicall: vi.fn(), readContract: vi.fn(), simulateContract: vi.fn() }, +})); + +vi.mock("../../../lib/price", () => ({ + mcv2BondAbi: [], + erc20Abi: [], +})); + +vi.mock("../../../lib/zap", () => ({ + getZapQuote: vi.fn(), + buildZapMintTx: vi.fn(), +})); + +// Mock constants with zap enabled (non-zero address) +vi.mock("../../../lib/contracts/constants", () => ({ + MCV2_BOND: "0xc5a076cad94176c2996B32d8466Be1cE757FAa27", + PLOT_TOKEN: "0xF8A2C39111FCEB9C950aAf28A9E34EBaD99b85C1", + RESERVE_LABEL: "PLOT", + EXPLORER_URL: "https://basescan.org", + ZAP_PLOTLINK: "0xEF6a8640c836b16Eb8cCD8016Ead4C8517aC3033", + ETH_ADDRESS: "0x0000000000000000000000000000000000000000", + SUPPORTED_ZAP_TOKENS: [ + { symbol: "ETH", address: "0x0000000000000000000000000000000000000000", decimals: 18 }, + { symbol: "USDC", address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", decimals: 6 }, + { symbol: "HUNT", address: "0x37f0c2915CeCC7e977183B8543Fc0864d03E064C", decimals: 18 }, + ], +})); + +import { TradingWidget } from "../TradingWidget"; + +const TOKEN = "0x1234567890123456789012345678901234567890" as const; + +describe("TradingWidget", () => { + it("returns null when wallet is not connected", () => { + mockUseAccount.mockReturnValue({ address: undefined, isConnected: false }); + const { container } = render(); + expect(container.innerHTML).toBe(""); + }); + + describe("connected", () => { + beforeEach(() => { + mockUseAccount.mockReturnValue({ + address: "0xABCDEF1234567890ABCDEF1234567890ABCDEF12", + isConnected: true, + }); + mockUseBalance.mockReturnValue({ data: { value: BigInt(1e18) }, refetch: vi.fn() }); + }); + + it("renders buy/sell tab toggle", () => { + render(); + expect(screen.getByText("Buy")).toBeInTheDocument(); + expect(screen.getByText("Sell")).toBeInTheDocument(); + }); + + it("renders pay token selector with all 4 options (ETH, USDC, HUNT, PLOT)", () => { + render(); + expect(screen.getByText("ETH")).toBeInTheDocument(); + expect(screen.getByText("USDC")).toBeInTheDocument(); + expect(screen.getByText("HUNT")).toBeInTheDocument(); + // PLOT shows as RESERVE_LABEL + expect(screen.getByText("PLOT")).toBeInTheDocument(); + }); + + it("renders amount input with placeholder", () => { + render(); + expect(screen.getByPlaceholderText("0.0")).toBeInTheDocument(); + }); + + it("validates amount input accepts numbers", () => { + render(); + const input = screen.getByPlaceholderText("0.0"); + fireEvent.change(input, { target: { value: "100" } }); + expect(input).toHaveValue("100"); + }); + + it("switches between buy and sell tabs", () => { + render(); + fireEvent.click(screen.getByText("Sell")); + expect(screen.getByText("Tokens to sell")).toBeInTheDocument(); + }); + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts index 57b945a6..ef90f1e5 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,7 +1,15 @@ import { defineConfig } from "vitest/config"; +import path from "path"; export default defineConfig({ test: { - include: ["**/*.test.ts"], + include: ["**/*.test.ts", "**/*.test.tsx"], + environment: "jsdom", + setupFiles: ["./vitest.setup.ts"], + }, + resolve: { + alias: { + "../../lib": path.resolve(__dirname, "lib"), + }, }, }); diff --git a/vitest.setup.ts b/vitest.setup.ts new file mode 100644 index 00000000..f149f27a --- /dev/null +++ b/vitest.setup.ts @@ -0,0 +1 @@ +import "@testing-library/jest-dom/vitest"; From 3287909eda6c90c7e84780bb80361e2b8552ae02 Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Sun, 22 Mar 2026 11:01:39 +0000 Subject: [PATCH 2/3] [#246] Address review: remove RPC mocks from TradingWidget tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TradingWidget: reduced to disconnected-state test only (per spec: "do NOT mock Supabase, RPC, or blockchain calls") - StoryGrid: added comment noting useShelfSize/chunk don't exist in current implementation — grid is pure CSS (grid-cols-2/lg:grid-cols-3) 64 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/components/__tests__/StoryGrid.test.tsx | 4 + .../__tests__/TradingWidget.test.tsx | 78 ++----------------- 2 files changed, 12 insertions(+), 70 deletions(-) diff --git a/src/components/__tests__/StoryGrid.test.tsx b/src/components/__tests__/StoryGrid.test.tsx index c69a2f50..c6f45b8d 100644 --- a/src/components/__tests__/StoryGrid.test.tsx +++ b/src/components/__tests__/StoryGrid.test.tsx @@ -50,6 +50,10 @@ function makeStoryline(id: number, title: string): Storyline { } as unknown as Storyline; } +// Note: The current StoryGrid implementation uses a plain CSS grid +// (grid-cols-2 / lg:grid-cols-3) rather than useShelfSize() or chunk() helpers. +// Responsive behavior is handled entirely by Tailwind CSS classes, not JS logic. +// Tests cover the actual implementation: card rendering, empty state, and grid classes. describe("StoryGrid", () => { it("renders correct number of story cards", () => { const storylines = [ diff --git a/src/components/__tests__/TradingWidget.test.tsx b/src/components/__tests__/TradingWidget.test.tsx index f6847dc1..2294037e 100644 --- a/src/components/__tests__/TradingWidget.test.tsx +++ b/src/components/__tests__/TradingWidget.test.tsx @@ -1,16 +1,12 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { render, screen, fireEvent, cleanup } from "@testing-library/react"; +import { describe, it, expect, vi, afterEach } from "vitest"; +import { render, cleanup } from "@testing-library/react"; afterEach(cleanup); -// Mock wagmi hooks -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const mockUseAccount = vi.fn((): any => ({ address: undefined, isConnected: false })); -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const mockUseBalance = vi.fn((): any => ({ data: undefined, refetch: vi.fn() })); +// Mock only wagmi (wallet layer, not RPC) — per spec: test disconnected state vi.mock("wagmi", () => ({ - useAccount: () => mockUseAccount(), - useBalance: () => mockUseBalance(), + useAccount: () => ({ address: undefined, isConnected: false }), + useBalance: () => ({ data: undefined, refetch: vi.fn() }), useWriteContract: () => ({ writeContractAsync: vi.fn() }), })); @@ -18,8 +14,9 @@ vi.mock("@tanstack/react-query", () => ({ useQuery: () => ({ data: undefined, refetch: vi.fn() }), })); +// Minimal module stubs so the component can be imported (not mocking RPC behavior) vi.mock("../../../lib/rpc", () => ({ - browserClient: { multicall: vi.fn(), readContract: vi.fn(), simulateContract: vi.fn() }, + browserClient: {}, })); vi.mock("../../../lib/price", () => ({ @@ -32,72 +29,13 @@ vi.mock("../../../lib/zap", () => ({ buildZapMintTx: vi.fn(), })); -// Mock constants with zap enabled (non-zero address) -vi.mock("../../../lib/contracts/constants", () => ({ - MCV2_BOND: "0xc5a076cad94176c2996B32d8466Be1cE757FAa27", - PLOT_TOKEN: "0xF8A2C39111FCEB9C950aAf28A9E34EBaD99b85C1", - RESERVE_LABEL: "PLOT", - EXPLORER_URL: "https://basescan.org", - ZAP_PLOTLINK: "0xEF6a8640c836b16Eb8cCD8016Ead4C8517aC3033", - ETH_ADDRESS: "0x0000000000000000000000000000000000000000", - SUPPORTED_ZAP_TOKENS: [ - { symbol: "ETH", address: "0x0000000000000000000000000000000000000000", decimals: 18 }, - { symbol: "USDC", address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", decimals: 6 }, - { symbol: "HUNT", address: "0x37f0c2915CeCC7e977183B8543Fc0864d03E064C", decimals: 18 }, - ], -})); - import { TradingWidget } from "../TradingWidget"; const TOKEN = "0x1234567890123456789012345678901234567890" as const; describe("TradingWidget", () => { - it("returns null when wallet is not connected", () => { - mockUseAccount.mockReturnValue({ address: undefined, isConnected: false }); + it("returns null when wallet is not connected (isConnected = false)", () => { const { container } = render(); expect(container.innerHTML).toBe(""); }); - - describe("connected", () => { - beforeEach(() => { - mockUseAccount.mockReturnValue({ - address: "0xABCDEF1234567890ABCDEF1234567890ABCDEF12", - isConnected: true, - }); - mockUseBalance.mockReturnValue({ data: { value: BigInt(1e18) }, refetch: vi.fn() }); - }); - - it("renders buy/sell tab toggle", () => { - render(); - expect(screen.getByText("Buy")).toBeInTheDocument(); - expect(screen.getByText("Sell")).toBeInTheDocument(); - }); - - it("renders pay token selector with all 4 options (ETH, USDC, HUNT, PLOT)", () => { - render(); - expect(screen.getByText("ETH")).toBeInTheDocument(); - expect(screen.getByText("USDC")).toBeInTheDocument(); - expect(screen.getByText("HUNT")).toBeInTheDocument(); - // PLOT shows as RESERVE_LABEL - expect(screen.getByText("PLOT")).toBeInTheDocument(); - }); - - it("renders amount input with placeholder", () => { - render(); - expect(screen.getByPlaceholderText("0.0")).toBeInTheDocument(); - }); - - it("validates amount input accepts numbers", () => { - render(); - const input = screen.getByPlaceholderText("0.0"); - fireEvent.change(input, { target: { value: "100" } }); - expect(input).toHaveValue("100"); - }); - - it("switches between buy and sell tabs", () => { - render(); - fireEvent.click(screen.getByText("Sell")); - expect(screen.getByText("Tokens to sell")).toBeInTheDocument(); - }); - }); }); From 580774a879a17924f1744d9867de447b2719f9ac Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Sun, 22 Mar 2026 11:03:47 +0000 Subject: [PATCH 3/3] =?UTF-8?q?[#246]=20Remove=20TradingWidget=20test=20?= =?UTF-8?q?=E2=80=94=20cannot=20import=20without=20RPC=20module=20stubs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TradingWidget imports lib/rpc at module level, which creates a viem client. This fails in jsdom without module stubs, and per #246 spec we must not mock RPC or blockchain calls. Removing the test file entirely rather than violating the constraint. TradingWidget can be tested in an integration/E2E suite instead. 63 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../__tests__/TradingWidget.test.tsx | 41 ------------------- 1 file changed, 41 deletions(-) delete mode 100644 src/components/__tests__/TradingWidget.test.tsx diff --git a/src/components/__tests__/TradingWidget.test.tsx b/src/components/__tests__/TradingWidget.test.tsx deleted file mode 100644 index 2294037e..00000000 --- a/src/components/__tests__/TradingWidget.test.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { describe, it, expect, vi, afterEach } from "vitest"; -import { render, cleanup } from "@testing-library/react"; - -afterEach(cleanup); - -// Mock only wagmi (wallet layer, not RPC) — per spec: test disconnected state -vi.mock("wagmi", () => ({ - useAccount: () => ({ address: undefined, isConnected: false }), - useBalance: () => ({ data: undefined, refetch: vi.fn() }), - useWriteContract: () => ({ writeContractAsync: vi.fn() }), -})); - -vi.mock("@tanstack/react-query", () => ({ - useQuery: () => ({ data: undefined, refetch: vi.fn() }), -})); - -// Minimal module stubs so the component can be imported (not mocking RPC behavior) -vi.mock("../../../lib/rpc", () => ({ - browserClient: {}, -})); - -vi.mock("../../../lib/price", () => ({ - mcv2BondAbi: [], - erc20Abi: [], -})); - -vi.mock("../../../lib/zap", () => ({ - getZapQuote: vi.fn(), - buildZapMintTx: vi.fn(), -})); - -import { TradingWidget } from "../TradingWidget"; - -const TOKEN = "0x1234567890123456789012345678901234567890" as const; - -describe("TradingWidget", () => { - it("returns null when wallet is not connected (isConnected = false)", () => { - const { container } = render(); - expect(container.innerHTML).toBe(""); - }); -});