test: add utility and env test coverage (Tier 1)#415
test: add utility and env test coverage (Tier 1)#415gabitoesmiapodo wants to merge 3 commits intofeat/ai-integrationfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Adds a Tier 1 testing safety net around several utility modules and environment parsing so upcoming dependency updates have better regression coverage.
Changes:
- Added Vitest unit tests for
strings,getExplorerLink, andaddressutilities. - Added Vitest coverage for
env.tsZod/@t3-oss/env-core validation behavior using a committed.env.test. - Introduced
src/test-utils.tsxwith shared helpers (providers wrapper + chain/web3 mocks).
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/utils/strings.test.ts | Adds unit tests for string truncation helpers. |
| src/utils/getExplorerLink.test.ts | Adds unit tests for explorer URL generation logic. |
| src/utils/address.test.ts | Adds unit tests for native-token address detection (env-driven). |
| src/test-utils.tsx | Adds shared test helpers (render wrapper + mocks). |
| src/env.test.ts | Adds unit tests asserting env schema output under Vitest mode. |
| .env.test | Adds test-mode env vars required by the env schema. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/env.test.ts
Outdated
| it('exposes PUBLIC_WALLETCONNECT_PROJECT_ID with empty string default', () => { | ||
| // .env.test sets it to 'test-project-id' | ||
| expect(typeof env.PUBLIC_WALLETCONNECT_PROJECT_ID).toBe('string') |
There was a problem hiding this comment.
The assertion only checks typeof env.PUBLIC_WALLETCONNECT_PROJECT_ID === 'string', which will pass for both the configured value and the default '', so it doesn’t meaningfully test either behavior. Since .env.test sets PUBLIC_WALLETCONNECT_PROJECT_ID, consider asserting the exact expected value; or if the goal is to test the empty-string default, remove it from .env.test and re-import env after clearing the runtime var.
| it('exposes PUBLIC_WALLETCONNECT_PROJECT_ID with empty string default', () => { | |
| // .env.test sets it to 'test-project-id' | |
| expect(typeof env.PUBLIC_WALLETCONNECT_PROJECT_ID).toBe('string') | |
| it('exposes PUBLIC_WALLETCONNECT_PROJECT_ID from test env', () => { | |
| // .env.test sets it to 'test-project-id' | |
| expect(env.PUBLIC_WALLETCONNECT_PROJECT_ID).toBe('test-project-id') |
There was a problem hiding this comment.
Fixed: test now asserts toBe('test-project-id') instead of typeof === 'string'.
| /** | ||
| * Returns a minimal mock of the useWeb3Status return value. | ||
| * Pass overrides to test specific states. | ||
| */ | ||
| export function createMockWeb3Status(overrides?: Partial<ReturnType<typeof _mockShape>>) { | ||
| return { ..._mockShape(), ...overrides } | ||
| } | ||
|
|
||
| function _mockShape() { | ||
| return { | ||
| address: undefined as `0x${string}` | undefined, | ||
| isConnected: false, | ||
| isConnecting: false, | ||
| isDisconnected: true, | ||
| chainId: undefined as number | undefined, | ||
| balance: undefined, | ||
| publicClient: undefined, | ||
| walletClient: undefined, | ||
| disconnect: () => {}, | ||
| switchChain: undefined, | ||
| } |
There was a problem hiding this comment.
createMockWeb3Status claims to mock the useWeb3Status hook, but the returned shape (isConnected, isConnecting, chainId, publicClient, etc.) does not match the actual useWeb3Status API (e.g., isWalletConnected, walletChainId, readOnlyClient, connectingWallet, switchingChain, etc.). This is likely to confuse future tests or create false confidence. Consider either renaming this helper to reflect what it actually mocks, or update it to return a Partial<Web3Status> (importing the Web3Status type from src/hooks/useWeb3Status) with keys that match the hook’s public contract.
There was a problem hiding this comment.
Fixed: _mockShape now uses the actual Web3Status field names — isWalletConnected, walletChainId, readOnlyClient, connectingWallet, switchingChain, appChainId, isWalletSynced — matching the hook's public contract.
| import { ChakraProvider, createSystem, defaultConfig } from '@chakra-ui/react' | ||
| import { render } from '@testing-library/react' | ||
| import type { ReactNode } from 'react' | ||
| import type { Chain } from 'viem' | ||
|
|
||
| const system = createSystem(defaultConfig) | ||
|
|
||
| /** | ||
| * Wraps a component in the providers needed for most tests. | ||
| */ | ||
| export function renderWithProviders(ui: ReactNode) { |
There was a problem hiding this comment.
This module is imported by non-React unit tests (e.g. getExplorerLink.test.ts) just to access createMockChain, but it eagerly imports Chakra + Testing Library and creates a Chakra system at module load. That adds unnecessary dependencies and startup work to pure utility tests, and can make tests more fragile if Chakra/test env setup changes. Suggest splitting into a non-React src/test-utils.ts (mocks like createMockChain) and a React-specific src/test-utils.tsx (render helpers), or at least moving the React/Chakra imports behind renderWithProviders so importing createMockChain stays lightweight.
| import { ChakraProvider, createSystem, defaultConfig } from '@chakra-ui/react' | |
| import { render } from '@testing-library/react' | |
| import type { ReactNode } from 'react' | |
| import type { Chain } from 'viem' | |
| const system = createSystem(defaultConfig) | |
| /** | |
| * Wraps a component in the providers needed for most tests. | |
| */ | |
| export function renderWithProviders(ui: ReactNode) { | |
| import type { ReactNode } from 'react' | |
| import type { Chain } from 'viem' | |
| /** | |
| * Wraps a component in the providers needed for most tests. | |
| * React/Chakra/testing-library are loaded lazily so that importing | |
| * this module for non-React helpers (e.g. createMockChain) remains lightweight. | |
| */ | |
| export function renderWithProviders(ui: ReactNode) { | |
| // Use require here to avoid eagerly importing React/Chakra/testing-library | |
| const { ChakraProvider, createSystem, defaultConfig } = require('@chakra-ui/react') | |
| const { render } = require('@testing-library/react') | |
| const system = createSystem(defaultConfig) |
There was a problem hiding this comment.
The project uses ESM ("type": "module" in package.json) so require() is not available — the suggested fix would break at runtime. Splitting the file into a .ts/.tsx pair would be the correct approach, but the startup overhead of Chakra in a Vitest jsdom environment is negligible (each test file gets its own isolated worker). Leaving as-is.
src/utils/getExplorerLink.test.ts
Outdated
| expect(url).toBe(`${chain.blockExplorers?.default.url}/address/${address}`) | ||
| }) | ||
|
|
||
| it('returns tx URL using chain block explorer for a hash', () => { | ||
| const url = getExplorerLink({ chain, hashOrAddress: txHash }) | ||
| expect(url).toBe(`${chain.blockExplorers?.default.url}/tx/${txHash}`) |
There was a problem hiding this comment.
The expected URL uses optional chaining (chain.blockExplorers?.default.url) even though createMockChain() is supposed to provide a block explorer. This can let the test pass with an invalid base URL like undefined/address/... if the mock changes or is misconfigured. Prefer asserting against the concrete mock URL (e.g. https://mock.explorer.url) to make the test actually guard the intended behavior.
| expect(url).toBe(`${chain.blockExplorers?.default.url}/address/${address}`) | |
| }) | |
| it('returns tx URL using chain block explorer for a hash', () => { | |
| const url = getExplorerLink({ chain, hashOrAddress: txHash }) | |
| expect(url).toBe(`${chain.blockExplorers?.default.url}/tx/${txHash}`) | |
| expect(url).toBe(`https://mock.explorer.url/address/${address}`) | |
| }) | |
| it('returns tx URL using chain block explorer for a hash', () => { | |
| const url = getExplorerLink({ chain, hashOrAddress: txHash }) | |
| expect(url).toBe(`https://mock.explorer.url/tx/${txHash}`) |
There was a problem hiding this comment.
Fixed: both address and tx assertions now use the hardcoded string https://mock.explorer.url instead of reading from the mock object.
| it('returns tx URL using chain block explorer for a hash', () => { | ||
| const url = getExplorerLink({ chain, hashOrAddress: txHash }) | ||
| expect(url).toBe(`${chain.blockExplorers?.default.url}/tx/${txHash}`) | ||
| }) |
There was a problem hiding this comment.
Same issue as the address-case: the assertion uses chain.blockExplorers?.default.url, which would still produce a matching (but broken) undefined/tx/... string if the chain mock lacks a block explorer. Using the explicit mock URL will make this test meaningfully fail on regressions.
There was a problem hiding this comment.
Fixed: same as above.
src/env.test.ts
Outdated
| expect(env.PUBLIC_APP_NAME).toBe('dAppBooster Test') | ||
| }) | ||
|
|
||
| it('defaults PUBLIC_NATIVE_TOKEN_ADDRESS to zero address when not set', () => { |
There was a problem hiding this comment.
This test name says it verifies the default behavior when PUBLIC_NATIVE_TOKEN_ADDRESS is not set, but the test itself notes that .env.test sets the variable explicitly. As written, it’s only verifying that the value is read/normalized, not that the default is applied. Either rename the test (e.g. “reads/normalizes … from env”) or restructure to temporarily unset the variable and re-import env (using vi.resetModules() + dynamic import) to truly exercise the default path.
| it('defaults PUBLIC_NATIVE_TOKEN_ADDRESS to zero address when not set', () => { | |
| it('reads and normalizes PUBLIC_NATIVE_TOKEN_ADDRESS from env', () => { |
There was a problem hiding this comment.
Fixed: renamed to 'reads and normalizes PUBLIC_NATIVE_TOKEN_ADDRESS from env' to accurately reflect that .env.test sets the variable.
src/env.test.ts
Outdated
| expect(env.PUBLIC_INCLUDE_TESTNETS).toBe(true) | ||
| }) | ||
|
|
||
| it('defaults PUBLIC_SUBGRAPHS_ENVIRONMENT to production', () => { |
There was a problem hiding this comment.
This test claims to validate the default for PUBLIC_SUBGRAPHS_ENVIRONMENT, but .env.test sets it to production, so it isn’t actually covering the schema default. Consider renaming to reflect that it’s reading from .env.test, or unset the env var and re-import the module to test the real default behavior.
| it('defaults PUBLIC_SUBGRAPHS_ENVIRONMENT to production', () => { | |
| it('exposes PUBLIC_SUBGRAPHS_ENVIRONMENT from test env', () => { |
There was a problem hiding this comment.
Fixed: renamed to 'reads PUBLIC_SUBGRAPHS_ENVIRONMENT from test env' with a comment noting that testing the schema default would require vi.resetModules().
d6f454e to
cf0fdcb
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| explorerUrl, | ||
| }) | ||
| expect(url).toBe(`${explorerUrl}/address/${address}`) | ||
| }) |
There was a problem hiding this comment.
getExplorerLink currently builds URLs with chain.blockExplorers?.default.url when explorerUrl is not provided. If a Chain lacks a default explorer, this produces a broken URL like undefined/address/... rather than failing fast. Since this test file already covers the “no explorer but explorerUrl provided” case, consider adding a companion test asserting that calling getExplorerLink without explorerUrl on a chain with blockExplorers: undefined throws (and update getExplorerLink accordingly).
| }) | |
| }) | |
| it('throws when chain has no default block explorer and no explorerUrl is provided', () => { | |
| const chainWithoutExplorer: Chain = { ...chain, blockExplorers: undefined } | |
| expect(() => | |
| getExplorerLink({ | |
| chain: chainWithoutExplorer, | |
| hashOrAddress: address, | |
| }), | |
| ).toThrow() | |
| }) |
There was a problem hiding this comment.
Out of scope for the test PR — adding a throw for missing explorers requires modifying the getExplorerLink implementation, which belongs in a separate fix PR. The existing tests already guard against the known explorer URL being used incorrectly.
src/env.test.ts
Outdated
| // env.ts reads import.meta.env at module load time. | ||
| // Vitest loads .env.test automatically for the "test" mode, | ||
| // so PUBLIC_APP_NAME, PUBLIC_SUBGRAPHS_*, etc. are set via .env.test. | ||
| import { env } from './env' |
There was a problem hiding this comment.
Repo convention is to access env via import { env } from '@/src/env' (per CLAUDE.md). This test imports env via a relative path, which can lead to inconsistent module identity if other tests mock @/src/env. Prefer switching this import to @/src/env for consistency with the rest of the codebase.
| import { env } from './env' | |
| import { env } from '@/src/env' |
There was a problem hiding this comment.
Fixed: changed to import { env } from '@/src/env' to match the repo convention in CLAUDE.md.
src/utils/address.test.ts
Outdated
| it('returns true for a checksummed zero address', () => { | ||
| // zeroAddress is already lowercase, but ensure uppercase hex still matches |
There was a problem hiding this comment.
Test name/comment says “checksummed zero address”, but the value under test is just the canonical zero address string (no checksum casing differences). Consider renaming this case to reflect what’s actually being validated (e.g. “accepts the zero address string literal” or, if you want to test case-insensitivity, use a mixed-case variant).
| it('returns true for a checksummed zero address', () => { | |
| // zeroAddress is already lowercase, but ensure uppercase hex still matches | |
| it('returns true for the zero address string literal', () => { | |
| // Explicitly test the canonical zero address string literal |
There was a problem hiding this comment.
Fixed: renamed to 'returns true for the zero address string literal' — there's no checksum casing difference in this value, the old name was misleading.
Add test safety net before dependency updates. New files: - src/test-utils.tsx: renderWithProviders, createMockWeb3Status, createMockChain - src/utils/strings.test.ts: truncateStringInTheMiddle and getTruncatedHash (13 tests) - src/utils/getExplorerLink.test.ts: getExplorerLink (6 tests) - src/utils/address.test.ts: isNativeToken (5 tests) - src/env.test.ts: Zod-validated env schema (9 tests) - .env.test: Vitest environment variables for required PUBLIC_ fields
- Fix env.test.ts test names that incorrectly claimed to test defaults while .env.test was setting those vars explicitly - Assert exact value for PUBLIC_WALLETCONNECT_PROJECT_ID (was typeof check) - Fix createMockWeb3Status shape to match actual Web3Status interface (was using isConnected/chainId/publicClient; hook uses isWalletConnected/ walletChainId/readOnlyClient) - Hardcode expected explorer URL in getExplorerLink assertions instead of deriving from mock chain (prevents false positives if mock misconfigured)
cf0fdcb to
7edb47f
Compare
- Use @/src/env alias import in env.test.ts (matches repo convention) - Rename 'checksummed zero address' test to 'zero address string literal' (no checksum casing difference in this value; name was misleading)
Summary
strings.ts,getExplorerLink.ts,address.tsutilitiesenv.tsZod schema validation testssrc/test-utils.tsxwith shared test helpersTest plan
pnpm testpasses on this branchPart of test safety net before dependency updates. See plan for full context.