diff --git a/package.json b/package.json index 3349b74..f42bcae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kredeum/wagmi-svelte5", - "version": "0.1.4", + "version": "0.1.5", "description": "Svelte5 Web3 library based on Wagmi", "keywords": [ "svelte", @@ -33,10 +33,10 @@ "format": "prettier --write --list-different .", "check": "prettier --check . && eslint . && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "build": "vite build", - "test": "npm run test:unit -- --run && npm run test:e2e", + "test": "npm run test:unit && npm run test:e2e", "dev": "vite dev", "preview": "vite preview", - "test:unit": "vitest", + "test:unit": "vitest run", "test:e2e": "playwright test", "package": "NODE_ENV=prod svelte-kit sync && svelte-package && publint" }, @@ -53,6 +53,7 @@ "@sveltejs/kit": "^2.15.0", "@sveltejs/package": "^2.3.7", "@sveltejs/vite-plugin-svelte": "^4.0.4", + "@types/node": "^22.10.2", "@wagmi/connectors": "^5.7.3", "@wagmi/core": "^2.16.3", "@walletconnect/ethereum-provider": "^2.17.3", @@ -63,6 +64,7 @@ "eslint-plugin-svelte": "^2.46.1", "esm-env": "^1.2.1", "globals": "^15.14.0", + "happy-dom": "^16.0.1", "prettier": "^3.4.2", "prettier-plugin-svelte": "^3.3.2", "prettier-plugin-tailwindcss": "^0.6.9", diff --git a/playwright.config.ts b/playwright.config.ts index 3f99a83..b3b0621 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -6,5 +6,6 @@ export default defineConfig({ port: 4173 }, - testDir: "tests/e2e" + testDir: "tests/e2e", + testMatch: "*.t.ts" }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a666427..52d87ac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,19 +26,22 @@ importers: version: 1.49.1 '@sveltejs/adapter-auto': specifier: ^3.3.1 - version: 3.3.1(@sveltejs/kit@2.15.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11))(svelte@5.16.0)(vite@5.4.11)) + version: 3.3.1(@sveltejs/kit@2.15.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2)))(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2))) '@sveltejs/adapter-static': specifier: ^3.0.8 - version: 3.0.8(@sveltejs/kit@2.15.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11))(svelte@5.16.0)(vite@5.4.11)) + version: 3.0.8(@sveltejs/kit@2.15.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2)))(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2))) '@sveltejs/kit': specifier: ^2.15.0 - version: 2.15.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11))(svelte@5.16.0)(vite@5.4.11) + version: 2.15.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2)))(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2)) '@sveltejs/package': specifier: ^2.3.7 version: 2.3.7(svelte@5.16.0)(typescript@5.7.2) '@sveltejs/vite-plugin-svelte': specifier: ^4.0.4 - version: 4.0.4(svelte@5.16.0)(vite@5.4.11) + version: 4.0.4(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2)) + '@types/node': + specifier: ^22.10.2 + version: 22.10.2 '@wagmi/connectors': specifier: ^5.7.3 version: 5.7.3(@wagmi/core@2.16.3(react@18.3.1)(typescript@5.7.2)(use-sync-external-store@1.2.0(react@18.3.1))(viem@2.21.57(bufferutil@4.0.8)(typescript@5.7.2)(utf-8-validate@5.0.10)))(bufferutil@4.0.8)(react@18.3.1)(typescript@5.7.2)(utf-8-validate@5.0.10)(viem@2.21.57(bufferutil@4.0.8)(typescript@5.7.2)(utf-8-validate@5.0.10)) @@ -69,6 +72,9 @@ importers: globals: specifier: ^15.14.0 version: 15.14.0 + happy-dom: + specifier: ^16.0.1 + version: 16.0.1 prettier: specifier: ^3.4.2 version: 3.4.2 @@ -104,10 +110,10 @@ importers: version: 2.21.57(bufferutil@4.0.8)(typescript@5.7.2)(utf-8-validate@5.0.10) vite: specifier: ^5.4.11 - version: 5.4.11 + version: 5.4.11(@types/node@22.10.2) vitest: specifier: ^2.1.8 - version: 2.1.8 + version: 2.1.8(@types/node@22.10.2)(happy-dom@16.0.1) packages: @@ -820,6 +826,9 @@ packages: '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} + '@types/node@22.10.2': + resolution: {integrity: sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==} + '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -1671,6 +1680,10 @@ packages: h3@1.13.0: resolution: {integrity: sha512-vFEAu/yf8UMUcB4s43OaDaigcqpQd14yanmOsn+NcRX3/guSKncyE2rOYhq8RIchgJrPSs/QiIddnTTR1ddiAg==} + happy-dom@16.0.1: + resolution: {integrity: sha512-cqbqvutE6XAIMe4nM93TkbW5SDFtLkU/6nsQfJBJ2KSlaT+My2kmnYsCFXrvEzvmP7s1xGJ6Xt4D9LNJIJHMbA==} + engines: {node: '>=18.0.0'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -2661,6 +2674,9 @@ packages: uncrypto@0.1.3: resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + unenv@1.10.0: resolution: {integrity: sha512-wY5bskBQFL9n3Eca5XnhH6KbUo/tfvkwm9OpcdCvLaeA7piBNbavbOKJySEwQ1V0RH6HvNlSAFRTpvTqgKRQXQ==} @@ -2853,6 +2869,14 @@ packages: webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -3747,18 +3771,18 @@ snapshots: '@steeze-ui/heroicons@2.4.2': {} - '@sveltejs/adapter-auto@3.3.1(@sveltejs/kit@2.15.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11))(svelte@5.16.0)(vite@5.4.11))': + '@sveltejs/adapter-auto@3.3.1(@sveltejs/kit@2.15.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2)))(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2)))': dependencies: - '@sveltejs/kit': 2.15.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11))(svelte@5.16.0)(vite@5.4.11) + '@sveltejs/kit': 2.15.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2)))(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2)) import-meta-resolve: 4.1.0 - '@sveltejs/adapter-static@3.0.8(@sveltejs/kit@2.15.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11))(svelte@5.16.0)(vite@5.4.11))': + '@sveltejs/adapter-static@3.0.8(@sveltejs/kit@2.15.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2)))(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2)))': dependencies: - '@sveltejs/kit': 2.15.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11))(svelte@5.16.0)(vite@5.4.11) + '@sveltejs/kit': 2.15.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2)))(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2)) - '@sveltejs/kit@2.15.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11))(svelte@5.16.0)(vite@5.4.11)': + '@sveltejs/kit@2.15.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2)))(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2))': dependencies: - '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.16.0)(vite@5.4.11) + '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2)) '@types/cookie': 0.6.0 cookie: 0.6.0 devalue: 5.1.1 @@ -3772,7 +3796,7 @@ snapshots: sirv: 3.0.0 svelte: 5.16.0 tiny-glob: 0.2.9 - vite: 5.4.11 + vite: 5.4.11(@types/node@22.10.2) '@sveltejs/package@2.3.7(svelte@5.16.0)(typescript@5.7.2)': dependencies: @@ -3785,25 +3809,25 @@ snapshots: transitivePeerDependencies: - typescript - '@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11))(svelte@5.16.0)(vite@5.4.11)': + '@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2)))(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2))': dependencies: - '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.16.0)(vite@5.4.11) + '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2)) debug: 4.4.0 svelte: 5.16.0 - vite: 5.4.11 + vite: 5.4.11(@types/node@22.10.2) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11)': + '@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11))(svelte@5.16.0)(vite@5.4.11) + '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2)))(svelte@5.16.0)(vite@5.4.11(@types/node@22.10.2)) debug: 4.4.0 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.17 svelte: 5.16.0 - vite: 5.4.11 - vitefu: 1.0.4(vite@5.4.11) + vite: 5.4.11(@types/node@22.10.2) + vitefu: 1.0.4(vite@5.4.11(@types/node@22.10.2)) transitivePeerDependencies: - supports-color @@ -3819,6 +3843,10 @@ snapshots: '@types/ms@0.7.34': {} + '@types/node@22.10.2': + dependencies: + undici-types: 6.20.0 + '@types/trusted-types@2.0.7': {} '@typescript-eslint/eslint-plugin@8.18.2(@typescript-eslint/parser@8.18.2(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2))(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2)': @@ -3905,13 +3933,13 @@ snapshots: chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.8(vite@5.4.11)': + '@vitest/mocker@2.1.8(vite@5.4.11(@types/node@22.10.2))': dependencies: '@vitest/spy': 2.1.8 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 5.4.11 + vite: 5.4.11(@types/node@22.10.2) '@vitest/pretty-format@2.1.8': dependencies: @@ -5259,6 +5287,11 @@ snapshots: uncrypto: 0.1.3 unenv: 1.10.0 + happy-dom@16.0.1: + dependencies: + webidl-conversions: 7.0.0 + whatwg-mimetype: 3.0.0 + has-flag@4.0.0: {} has-property-descriptors@1.0.2: @@ -6170,6 +6203,8 @@ snapshots: uncrypto@0.1.3: {} + undici-types@6.20.0: {} + unenv@1.10.0: dependencies: consola: 3.3.2 @@ -6248,13 +6283,13 @@ snapshots: - utf-8-validate - zod - vite-node@2.1.8: + vite-node@2.1.8(@types/node@22.10.2): dependencies: cac: 6.7.14 debug: 4.4.0 es-module-lexer: 1.5.4 pathe: 1.1.2 - vite: 5.4.11 + vite: 5.4.11(@types/node@22.10.2) transitivePeerDependencies: - '@types/node' - less @@ -6266,22 +6301,23 @@ snapshots: - supports-color - terser - vite@5.4.11: + vite@5.4.11(@types/node@22.10.2): dependencies: esbuild: 0.21.5 postcss: 8.4.49 rollup: 4.29.1 optionalDependencies: + '@types/node': 22.10.2 fsevents: 2.3.3 - vitefu@1.0.4(vite@5.4.11): + vitefu@1.0.4(vite@5.4.11(@types/node@22.10.2)): optionalDependencies: - vite: 5.4.11 + vite: 5.4.11(@types/node@22.10.2) - vitest@2.1.8: + vitest@2.1.8(@types/node@22.10.2)(happy-dom@16.0.1): dependencies: '@vitest/expect': 2.1.8 - '@vitest/mocker': 2.1.8(vite@5.4.11) + '@vitest/mocker': 2.1.8(vite@5.4.11(@types/node@22.10.2)) '@vitest/pretty-format': 2.1.8 '@vitest/runner': 2.1.8 '@vitest/snapshot': 2.1.8 @@ -6297,9 +6333,12 @@ snapshots: tinyexec: 0.3.1 tinypool: 1.0.2 tinyrainbow: 1.2.0 - vite: 5.4.11 - vite-node: 2.1.8 + vite: 5.4.11(@types/node@22.10.2) + vite-node: 2.1.8(@types/node@22.10.2) why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.10.2 + happy-dom: 16.0.1 transitivePeerDependencies: - less - lightningcss @@ -6320,6 +6359,10 @@ snapshots: webidl-conversions@3.0.1: {} + webidl-conversions@7.0.0: {} + + whatwg-mimetype@3.0.0: {} + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 diff --git a/src/lib/wagmi/classes/Account.svelte.ts b/src/lib/wagmi/classes/Account.svelte.ts index 5659fb9..953fb3c 100644 --- a/src/lib/wagmi/classes/Account.svelte.ts +++ b/src/lib/wagmi/classes/Account.svelte.ts @@ -1,8 +1,6 @@ import { getAccount, watchAccount } from "@wagmi/core"; import { Address, wagmiConfig } from ".."; -import type { Nullable } from ".."; - -type AccountType = ReturnType; +import type { Nullable, AccountType } from ".."; // Account => account & chain & chainId & isConnected & connectorId // Address => address & balance & symbol & decimals & ensName & ensAvatar @@ -25,7 +23,7 @@ class Account extends Address { watch = () => watchAccount(wagmiConfig, { onChange: (newAccount: AccountType) => { - console.log("watchAccount Change:", newAccount); + console.info("watchAccount Change:", newAccount); this.#account = newAccount; super.address = newAccount.address; } diff --git a/src/lib/wagmi/classes/Events.svelte.ts b/src/lib/wagmi/classes/Events.svelte.ts index 0dc5f7a..ce011cf 100644 --- a/src/lib/wagmi/classes/Events.svelte.ts +++ b/src/lib/wagmi/classes/Events.svelte.ts @@ -1,10 +1,6 @@ -import { type Address as AddressType, type ContractEventName, type Log } from "viem"; +import { type Address as AddressType, type ContractEventName } from "viem"; import { watchContractEvent, getBlockNumber } from "@wagmi/core"; - -import { type LogWithArgs, SmartContract, wagmi, wagmiConfig, getContractEvents } from ".."; - -type EventsFilter = { eventName?: ContractEventName; args?: Record }; -type EventsSortOrder = "DESC" | "ASC" | undefined; +import { type LogWithArgs, type EventsFilter, type EventsSortOrder, SmartContract, wagmi, wagmiConfig, getContractEvents } from ".."; class Events extends SmartContract { limit: number = $state(0); @@ -73,7 +69,7 @@ class Events extends SmartContract { return blockDelta > 0 ? 1 : blockDelta < 0 ? -1 : indexDelta; } ); - console.log("EVENTS fetch", this.listAll.length, params, $state.snapshot(this.listAll)); + // console.log("EVENTS fetch", this.listAll.length, params, $state.snapshot(this.listAll)); } catch (error) { console.error("EVENTS Failed to fetch logs:", error); } @@ -106,7 +102,7 @@ class Events extends SmartContract { $effect(() => { wagmi.chainId; - console.log("EVENTS $effect ~ chainId :", wagmi.chainId); + // console.log("EVENTS $effect ~ chainId :", wagmi.chainId); this.fetch(watch); }); @@ -115,4 +111,3 @@ class Events extends SmartContract { } export { Events }; -export type { EventsFilter, EventsSortOrder }; diff --git a/src/lib/wagmi/classes/Network.svelte.ts b/src/lib/wagmi/classes/Network.svelte.ts index 750bfac..4820f94 100644 --- a/src/lib/wagmi/classes/Network.svelte.ts +++ b/src/lib/wagmi/classes/Network.svelte.ts @@ -23,7 +23,6 @@ class Network { #chainId: number = $state(31337); chainIdDefault: number = Network.chainIdLocal; get chainId() { - console.log("Network $effect getchainId ~ chainId:", this.#chainId); return this.#chainId; } get chain() { @@ -78,7 +77,7 @@ class Network { if (chainId !== wagmi.chainId) await switchChain(wagmiConfig, { chainId }); - console.log("", wagmi.chainId); + console.info("", wagmi.chainId); this.getBlockNumber(); }; @@ -97,7 +96,7 @@ class Network { untrack(() => { if (account.chainId == this.chainId) return; - console.log("Network $effect switch:"); + // console.log("Network $effect switch:"); this.switch(account.chainId); }); }); diff --git a/src/lib/wagmi/classes/Wagmi.svelte.ts b/src/lib/wagmi/classes/Wagmi.svelte.ts index d055886..7a011be 100644 --- a/src/lib/wagmi/classes/Wagmi.svelte.ts +++ b/src/lib/wagmi/classes/Wagmi.svelte.ts @@ -19,7 +19,7 @@ const getChains = () => { chainName in allChains && selectedChains.push(allChains[chainName as keyof typeof allChains]) ); selectedChains.push(mainnet); - console.log("selectedChains:", selectedChains); + // console.log("selectedChains:", selectedChains); return selectedChains; }; @@ -67,7 +67,7 @@ class Wagmi extends Network { watch = () => watchChainId(wagmiConfig, { onChange: (chainId: number) => { - console.log("watchChainId Change:", chainId); + console.info("watchChainId Change:", chainId); this.#chainId = chainId; } }); diff --git a/src/lib/wagmi/components/Connect.svelte b/src/lib/wagmi/components/Connect.svelte index 5b3f717..3a80196 100644 --- a/src/lib/wagmi/components/Connect.svelte +++ b/src/lib/wagmi/components/Connect.svelte @@ -64,7 +64,7 @@ modalDisplay = false; if (!wagmiConfig) return; - console.log("connectWallet", connector.type); + console.info("connectWallet", connector.type); if (isActiveConnection(connector.type)) return; const parameters: { connector: ConnectorType; chainId?: number } = { connector }; @@ -85,7 +85,7 @@ : network.chainIdDefault; if (chainId && chainId !== wallet.chainId) { - console.log(" {#if modalDisplay} -
+

Connect Wallet

diff --git a/src/lib/wagmi/components/index.ts b/src/lib/wagmi/components/index.ts deleted file mode 100644 index 94fcee7..0000000 --- a/src/lib/wagmi/components/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { default as BaseNotification } from "./BaseNotification.svelte"; -export { default as Connect } from "./Connect.svelte"; -export { default as Logs } from "./Logs.svelte"; -export { default as Link } from "./Link.svelte"; -export { default as LinkAddress } from "./LinkAddress.svelte"; -export { default as LinkTx } from "./LinkTx.svelte"; -export { default as ViewEvents } from "./ViewEvents.svelte"; diff --git a/src/lib/wagmi/index.ts b/src/lib/wagmi/index.ts index b95c923..a95a2e1 100644 --- a/src/lib/wagmi/index.ts +++ b/src/lib/wagmi/index.ts @@ -1,5 +1,6 @@ export { default as BaseNotification } from "./components/BaseNotification.svelte"; export { default as Connect } from "./components/Connect.svelte"; +export { default as Disconnect } from "./components/Disconnect.svelte"; export { default as Logs } from "./components/Logs.svelte"; export { default as Link } from "./components/Link.svelte"; export { default as LinkAddress } from "./components/LinkAddress.svelte"; @@ -14,7 +15,6 @@ export * from "./ts/getContractEvents"; export * from "./ts/notification"; export * from "./ts/readDeployments"; export * from "./ts/utils"; -export * from "./ts/utils2"; export * from "./classes/SmartContract.svelte"; export * from "./classes/Address.svelte"; export * from "./classes/Account.svelte"; diff --git a/src/lib/wagmi/ts/createBurnerConnector.ts b/src/lib/wagmi/ts/createBurnerConnector.ts index 2791104..9b3468a 100644 --- a/src/lib/wagmi/ts/createBurnerConnector.ts +++ b/src/lib/wagmi/ts/createBurnerConnector.ts @@ -1,4 +1,4 @@ -import { loadBurnerSK } from "./utils2"; +import { loadBurnerSK } from "./utils"; import { type EIP1193RequestFn, type Hex, @@ -161,7 +161,7 @@ export const createBurnerConnector = () => { }, async switchChain({ chainId }) { - console.log("createBurnerConnector switchChain", chainId); + // console.log("createBurnerConnector switchChain", chainId); const provider = await this.getProvider(); const chain = config.chains.find((x) => x.id === chainId); if (!chain) throw new SwitchChainError(new ChainNotConfiguredError()); diff --git a/src/lib/wagmi/ts/index.ts b/src/lib/wagmi/ts/index.ts deleted file mode 100644 index 3e6bf63..0000000 --- a/src/lib/wagmi/ts/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export * from "./alchemy"; -export * from "./createBurnerConnector"; -export * from "./clickOutside"; -export * from "./types"; -export * from "./getContractEvents"; -export * from "./notification"; -export * from "./readDeployments"; -export * from "./utils"; -export * from "./utils2"; diff --git a/src/lib/wagmi/ts/notification.ts b/src/lib/wagmi/ts/notification.ts index dbaaec0..a5308d2 100644 --- a/src/lib/wagmi/ts/notification.ts +++ b/src/lib/wagmi/ts/notification.ts @@ -1,4 +1,5 @@ import toast, { type Renderable, type ToastPosition } from "svelte-hot-french-toast"; + import { BaseNotification } from ".."; type NotificationProps = { diff --git a/src/lib/wagmi/ts/readDeployments.ts b/src/lib/wagmi/ts/readDeployments.ts index 1f0d35c..02c7f30 100644 --- a/src/lib/wagmi/ts/readDeployments.ts +++ b/src/lib/wagmi/ts/readDeployments.ts @@ -1,5 +1,5 @@ import { type Abi, type Address } from "viem"; -import { isAddress, type Nullable } from "@wagmi-svelte5"; +import { isAddress, type Nullable } from ".."; type DeploymentContract = { address: Address; abi: Abi; name?: string }; type DeploymentsChain = { diff --git a/src/lib/wagmi/ts/types.ts b/src/lib/wagmi/ts/types.ts index 6699ce3..d572aee 100644 --- a/src/lib/wagmi/ts/types.ts +++ b/src/lib/wagmi/ts/types.ts @@ -1,12 +1,31 @@ -import type { Address, Log } from "viem"; +import type { Address, ContractEventName, Log } from "viem"; +import type { getAccount } from "@wagmi/core"; -export type Nullable = T | null | undefined; +type Nullable = T | null | undefined; -export type LogWithArgs = Log & { args: []; index: number }; +type LogWithArgs = Log & { args: []; index: number }; -export type LogsParamsType = { +type LogsParamsType = { address: Address; abi: unknown; eventName?: string; args?: unknown[]; }; + +type AccountType = ReturnType; + +type EventsFilter = { + eventName?: ContractEventName; + args?: Record +}; + +type EventsSortOrder = "DESC" | "ASC" | undefined; + +export type { + Nullable, + LogWithArgs, + LogsParamsType, + AccountType, + EventsFilter, + EventsSortOrder +}; \ No newline at end of file diff --git a/src/lib/wagmi/ts/utils.ts b/src/lib/wagmi/ts/utils.ts index 97693d9..fcd371b 100644 --- a/src/lib/wagmi/ts/utils.ts +++ b/src/lib/wagmi/ts/utils.ts @@ -1,5 +1,10 @@ -import { type Address, isAddress as isAddressViem } from "viem"; +import { type Address, type Hex, isAddress as isAddressViem } from "viem"; +import { generatePrivateKey } from "viem/accounts"; +import { DEV } from "esm-env"; +import { BURNER_WALLET_KEY } from ".."; + +// Address and ENS utilities const isEns = (ensName: string | null | undefined) => { if (!ensName) return false; @@ -13,11 +18,54 @@ const isAddress = (address: Address | string | null | undefined): address is Add return isAddressViem(address as string); }; -const shorten0xString = (addr: `0x${string}`) => addr?.slice(0, 8) + "..." + addr?.slice(-6); +const shorten0xString = (addr: `0x${string}`) => addr?.slice(0, 9) + "..." + addr?.slice(-5); -// To be used in JSON.stringify when a field might be bigint -// https://wagmi.sh/react/faq#bigint-serialization +// JSON utilities const replacer = (_key: string, value: unknown) => typeof value === "bigint" ? value.toString() : value; -export { isEns, isAddress, shorten0xString, replacer }; +// Burner wallet utilities +const burnerLocalStorageKey = "wagmiSvelte5.burnerWallet.sk"; +let currentSk: Hex = "0x"; + +const isValidSk = (pk: Hex | string | undefined | null): boolean => { + return pk?.length === 64 || pk?.length === 66; +}; + +const generatedPrivateKey = generatePrivateKey(); + +const saveBurnerSK = (privateKey: Hex): void => { + if (typeof window != "undefined" && window != null) { + window?.localStorage?.setItem(burnerLocalStorageKey, privateKey); + } +}; + +const loadBurnerSK = (): Hex => { + if (isValidSk(currentSk)) return currentSk; + + const localStorageKey = (window?.localStorage + ?.getItem?.(burnerLocalStorageKey) + ?.replaceAll('"', "") ?? "0x") as Hex; + + const envStorageKey = ((DEV && BURNER_WALLET_KEY) || "0x") as Hex; + + currentSk = isValidSk(localStorageKey) + ? localStorageKey + : isValidSk(envStorageKey) + ? envStorageKey + : generatedPrivateKey; + + return currentSk; +}; + +export { + // Address and ENS utilities + isEns, + isAddress, + shorten0xString, + // JSON utilities + replacer, + // Burner wallet utilities + saveBurnerSK, + loadBurnerSK +}; diff --git a/src/lib/wagmi/ts/utils2.ts b/src/lib/wagmi/ts/utils2.ts deleted file mode 100644 index 4a00e9c..0000000 --- a/src/lib/wagmi/ts/utils2.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { DEV } from "esm-env"; -import type { Hex } from "viem"; -import { generatePrivateKey } from "viem/accounts"; - -import { BURNER_WALLET_KEY } from ".."; - -const burnerLocalStorageKey = "wagmiSvelte5.burnerWallet.sk"; -let currentSk: Hex = "0x"; - -/** - * Checks if the private key is valid - */ -const isValidSk = (pk: Hex | string | undefined | null): boolean => { - return pk?.length === 64 || pk?.length === 66; -}; - -/** - * If no burner is found in localstorage or environment, generate a random private key - */ -const generatedPrivateKey = generatePrivateKey(); - -/** - * Save the current burner private key to local storage - */ -export const saveBurnerSK = (privateKey: Hex): void => { - if (typeof window != "undefined" && window != null) { - window?.localStorage?.setItem(burnerLocalStorageKey, privateKey); - } -}; - -/** - * Gets the current burner private key from local storage - */ -export const loadBurnerSK = (): Hex => { - if (isValidSk(currentSk)) return currentSk; - - // search for Key in local storage - const localStorageKey = (window?.localStorage - ?.getItem?.(burnerLocalStorageKey) - ?.replaceAll('"', "") ?? "0x") as Hex; - - // search for Key in environnement (dev mode only) - const envStorageKey = ((DEV && BURNER_WALLET_KEY) || "0x") as Hex; - - // set the current key to the first valid key found - currentSk = isValidSk(localStorageKey) - ? localStorageKey - : isValidSk(envStorageKey) - ? envStorageKey - : generatedPrivateKey; - - // save the current key to local storage and return it - if (DEV) saveBurnerSK(currentSk); - return currentSk; -}; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index f1b78d4..5a91990 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,6 +1,6 @@ @@ -30,7 +26,7 @@ {#if account.address} {account.address} ({account.chainId}) - + {#each chains as chain (chain.id)} {#if chain.id !== account.chainId} diff --git a/src/routes/counter/Counter.svelte.ts b/src/routes/counter/Counter.svelte.ts index 0e07e68..8a6e9c7 100644 --- a/src/routes/counter/Counter.svelte.ts +++ b/src/routes/counter/Counter.svelte.ts @@ -1,5 +1,5 @@ import type { Address } from "viem"; -import { SmartContract, isAddress } from "@wagmi-svelte5"; +import { SmartContract, isAddress } from "$lib/wagmi"; class Counter extends SmartContract { get number() { diff --git a/src/routes/deployments/+page.svelte b/src/routes/deployments/+page.svelte index a342fde..7b6295d 100644 --- a/src/routes/deployments/+page.svelte +++ b/src/routes/deployments/+page.svelte @@ -1,5 +1,5 @@ diff --git a/tests/e2e/demo.t.ts b/tests/e2e/demo.t.ts new file mode 100644 index 0000000..5b1b7c8 --- /dev/null +++ b/tests/e2e/demo.t.ts @@ -0,0 +1,69 @@ +import { expect, test } from "@playwright/test"; + +test("home page diplays 'Tests'", async ({ page }) => { + await page.goto("/"); + const h1 = page.locator("h1"); + await expect(h1).toBeVisible(); + await expect(h1).toHaveText("Tests"); +}); + +test("connect button should be visible and clickable", async ({ page }) => { + await page.goto("/"); + + // Wait for the connect button to be visible + const connectButton = page.locator("#connect-wallet"); + await expect(connectButton).toBeVisible(); + + // Click the connect button + await connectButton.click(); + + // Wait for the modal to appear + const modal = page.locator("#connect-wallet-modal"); + await expect(modal).toBeVisible(); + + // Verify the modal title + const modalTitle = page.locator("#connect-wallet-modal h3"); + await expect(modalTitle).toHaveText("Connect Wallet"); + + // Verify Burner Wallet is available + const burnerWalletButton = page.locator("#connect-burnerwallet"); + await expect(burnerWalletButton).toBeVisible(); + + // Click the Burner Wallet button + await burnerWalletButton.click(); + + // Wait for the modal to close + await expect(modal).not.toBeVisible(); + + // Verify we're connected by checking for the disconnect button + const disconnectButton = page.getByRole("button", { name: "Disconnect" }); + await expect(disconnectButton).toBeVisible(); +}); + +test('disconnect wallet functionality', async ({ page }) => { + // Navigate to the home page + await page.goto('/'); + + // Find and click the connect button + const connectButton = page.locator("#connect-wallet"); + await expect(connectButton).toBeVisible(); + await connectButton.click(); + + // Wait for the modal to appear + const modal = page.locator("#connect-wallet-modal"); + await expect(modal).toBeVisible(); + + // Connect with Burner Wallet + const burnerWalletButton = page.locator("#connect-burnerwallet"); + await burnerWalletButton.click(); + + // Wait for the disconnect button to be visible + const disconnectButton = page.getByRole("button", { name: "Disconnect" }); + await expect(disconnectButton).toBeVisible(); + + // Click the disconnect button + await disconnectButton.click(); + + // Verify the connect button is visible again after disconnection + await expect(connectButton).toBeVisible(); +}); diff --git a/tests/e2e/demo.test.ts b/tests/e2e/demo.test.ts deleted file mode 100644 index 4e8ffcc..0000000 --- a/tests/e2e/demo.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { expect, test } from "@playwright/test"; - -test("home page has expected h1", async ({ page }) => { - await page.goto("/"); - await expect(page.locator("h1")).toBeVisible(); -}); diff --git a/src/demo.spec.ts b/tests/unit/demo.t.ts similarity index 100% rename from src/demo.spec.ts rename to tests/unit/demo.t.ts diff --git a/tests/unit/mocks/env/static/public.ts b/tests/unit/mocks/env/static/public.ts new file mode 100644 index 0000000..59db6f3 --- /dev/null +++ b/tests/unit/mocks/env/static/public.ts @@ -0,0 +1,6 @@ +export const PUBLIC_CHAINS = 'anvil,baseSepolia' +export const PUBLIC_POLLING_INTERVAL = '5000' +export const PUBLIC_ALCHEMY_API_KEY = 'test-key' +export const PUBLIC_WALLET_CONNECT_PROJECT_ID = 'test-project-id' +export const PUBLIC_BURNER_WALLET_ONLY_LOCAL = 'true' +export const PUBLIC_BURNER_WALLET_KEY = 'test-key' diff --git a/tests/unit/setup.ts b/tests/unit/setup.ts new file mode 100644 index 0000000..a903888 --- /dev/null +++ b/tests/unit/setup.ts @@ -0,0 +1,34 @@ +import { vi } from 'vitest' +import { Window } from 'happy-dom' + +// Setup global environment +Object.defineProperty(globalThis, 'window', { value: new Window() }) + +// Mock deployments JSON if not already defined +if (!('__DEPLOYMENTS_JSON__' in globalThis)) { + Object.defineProperty(globalThis, '__DEPLOYMENTS_JSON__', { + value: { + '31337': { + name: 'Anvil', + chainId: '31337', + contracts: {} + }, + '84532': { + name: 'Base Sepolia', + chainId: '84532', + contracts: {} + } + }, + configurable: true + }) +} + +// Mock SvelteKit's $env/static/public module +vi.mock('$env/static/public', () => ({ + PUBLIC_CHAINS: 'anvil,baseSepolia', + PUBLIC_POLLING_INTERVAL: '5000', + PUBLIC_ALCHEMY_API_KEY: 'test-key', + PUBLIC_WALLET_CONNECT_PROJECT_ID: 'test-project-id', + PUBLIC_BURNER_WALLET_ONLY_LOCAL: 'true', + PUBLIC_BURNER_WALLET_KEY: 'test-key' +})) diff --git a/tests/unit/utils.t.ts b/tests/unit/utils.t.ts new file mode 100644 index 0000000..d388e9a --- /dev/null +++ b/tests/unit/utils.t.ts @@ -0,0 +1,116 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { isEns, isAddress, shorten0xString, replacer, saveBurnerSK, loadBurnerSK } from '../../src/lib/wagmi/ts/utils'; +import { type Address } from 'viem'; + +declare const global: typeof globalThis; + +describe('Address and ENS utilities', () => { + describe('isEns', () => { + it('should return false for null or undefined', () => { + expect(isEns(null)).toBe(false); + expect(isEns(undefined)).toBe(false); + }); + + it('should return true for valid .eth addresses', () => { + expect(isEns('vitalik.eth')).toBe(true); + expect(isEns('test-name.eth')).toBe(true); + }); + + it('should return false for non-.eth addresses', () => { + expect(isEns('vitalik')).toBe(false); + expect(isEns('test.com')).toBe(false); + }); + }); + + describe('isAddress', () => { + it('should return false for null or undefined', () => { + expect(isAddress(null)).toBe(false); + expect(isAddress(undefined)).toBe(false); + }); + + it('should return true for valid Ethereum addresses', () => { + const validAddress = '0x742d35Cc6634C0532925a3b844Bc454e4438f44e' as Address; + expect(isAddress(validAddress)).toBe(true); + }); + + it('should return false for invalid Ethereum addresses', () => { + expect(isAddress('0xinvalid')).toBe(false); + expect(isAddress('not an address')).toBe(false); + }); + }); + + describe('shorten0xString', () => { + it('should shorten address to format: 0x1234...5678', () => { + const address = '0x742d35Cc6634C0532925a3b844Bc454e4438f44e'; + expect(shorten0xString(address as `0x${string}`)).toBe('0x742d35C...8f44e'); + }); + }); +}); + +describe('JSON utilities', () => { + describe('replacer', () => { + it('should convert BigInt to string', () => { + const bigIntValue = BigInt('123456789'); + expect(replacer('test', bigIntValue)).toBe('123456789'); + }); + + it('should return non-BigInt values as is', () => { + expect(replacer('test', 123)).toBe(123); + expect(replacer('test', 'string')).toBe('string'); + expect(replacer('test', { key: 'value' })).toEqual({ key: 'value' }); + }); + }); +}); + +describe('Burner wallet utilities', () => { + let localStorageMock: { [key: string]: string }; + + beforeEach(() => { + localStorageMock = {}; + vi.stubGlobal('window', { + localStorage: { + getItem: (key: string) => localStorageMock[key] || null, + setItem: (key: string, value: string) => { + localStorageMock[key] = value; + }, + }, + }); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + describe('saveBurnerSK', () => { + it('should save private key to localStorage', () => { + const testKey = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'; + saveBurnerSK(testKey as `0x${string}`); + expect(localStorageMock['wagmiSvelte5.burnerWallet.sk']).toBe(testKey); + }); + + it('should handle undefined window object', () => { + (global as { window: unknown }).window = undefined; + const testKey = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'; + expect(() => saveBurnerSK(testKey as `0x${string}`)).not.toThrow(); + }); + }); + + describe('loadBurnerSK', () => { + it('should load existing private key from localStorage', () => { + const testKey = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'; + localStorageMock['wagmiSvelte5.burnerWallet.sk'] = testKey; + expect(loadBurnerSK()).toBe(testKey); + }); + + it('should generate new private key if none exists', () => { + const result = loadBurnerSK(); + expect(result).toMatch(/^0x[a-fA-F0-9]{64}$/); + }); + + it('should handle invalid localStorage key', () => { + localStorageMock['wagmiSvelte5.burnerWallet.sk'] = 'invalid-key'; + const result = loadBurnerSK(); + expect(result).toMatch(/^0x[a-fA-F0-9]{64}$/); + }); + }); +}); diff --git a/vite.config.ts b/vite.config.ts index 790b652..b76d114 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,8 +6,5 @@ import deploymentsJson from "./deployments.json"; export default defineConfig({ plugins: [sveltekit()], server: { open: true }, - define: { __DEPLOYMENTS_JSON__: deploymentsJson }, - test: { - include: ["src/**/*.{test,spec}.{js,ts}"] - } + define: { __DEPLOYMENTS_JSON__: deploymentsJson } }); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..945bd46 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,32 @@ +import { defineConfig } from 'vitest/config' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import { resolve } from 'path' + +export default defineConfig({ + plugins: [svelte({ hot: !process.env.VITEST })], + test: { + include: ['tests/unit/**/*.{test,spec,t}.{js,ts}'], + environment: 'happy-dom', + globals: true, + setupFiles: ['tests/unit/setup.ts'], + testTimeout: 10000, + hookTimeout: 10000, + teardownTimeout: 10000, + isolate: false, + pool: 'threads', + environmentOptions: { + happyDOM: { + settings: { + disableJavaScriptEvaluation: true + } + } + } + }, + resolve: { + alias: { + '$env': resolve(__dirname, './tests/unit/mocks/env'), + '$lib': resolve(__dirname, './src/lib'), + '@wagmi-svelte5': resolve(__dirname, './src/lib/wagmi/index.ts') + } + } +})