Skip to content

test: add utility and env test coverage (Tier 1)#415

Open
gabitoesmiapodo wants to merge 3 commits intofeat/ai-integrationfrom
test/utils
Open

test: add utility and env test coverage (Tier 1)#415
gabitoesmiapodo wants to merge 3 commits intofeat/ai-integrationfrom
test/utils

Conversation

@gabitoesmiapodo
Copy link
Collaborator

Summary

  • Adds tests for strings.ts, getExplorerLink.ts, address.ts utilities
  • Adds env.ts Zod schema validation tests
  • Creates src/test-utils.tsx with shared test helpers

Test plan

  • pnpm test passes on this branch
  • All new utility tests cover the public API surface used by wagmi/viem-dependent code

Part of test safety net before dependency updates. See plan for full context.

Copilot AI review requested due to automatic review settings March 23, 2026 21:17
@vercel
Copy link

vercel bot commented Mar 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
components.dappbooster Ready Ready Preview, Comment Mar 23, 2026 11:54pm
demo.dappbooster Ready Ready Preview Mar 23, 2026 11:54pm
docs.dappbooster Ready Ready Preview, Comment Mar 23, 2026 11:54pm

Request Review

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, and address utilities.
  • Added Vitest coverage for env.ts Zod/@t3-oss/env-core validation behavior using a committed .env.test.
  • Introduced src/test-utils.tsx with 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
Comment on lines +43 to +45
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')
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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')

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: test now asserts toBe('test-project-id') instead of typeof === 'string'.

Comment on lines +15 to +35
/**
* 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,
}
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: _mockShape now uses the actual Web3Status field names — isWalletConnected, walletChainId, readOnlyClient, connectingWallet, switchingChain, appChainId, isWalletSynced — matching the hook's public contract.

Comment on lines +1 to +11
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) {
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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)

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +15 to +20
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}`)
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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}`)

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: both address and tx assertions now use the hardcoded string https://mock.explorer.url instead of reading from the mock object.

Comment on lines +18 to +21
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}`)
})
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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', () => {
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
it('defaults PUBLIC_NATIVE_TOKEN_ADDRESS to zero address when not set', () => {
it('reads and normalizes PUBLIC_NATIVE_TOKEN_ADDRESS from env', () => {

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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', () => {
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
it('defaults PUBLIC_SUBGRAPHS_ENVIRONMENT to production', () => {
it('exposes PUBLIC_SUBGRAPHS_ENVIRONMENT from test env', () => {

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: renamed to 'reads PUBLIC_SUBGRAPHS_ENVIRONMENT from test env' with a comment noting that testing the schema default would require vi.resetModules().

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}`)
})
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
})
})
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()
})

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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'
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
import { env } from './env'
import { env } from '@/src/env'

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: changed to import { env } from '@/src/env' to match the repo convention in CLAUDE.md.

Comment on lines +22 to +23
it('returns true for a checksummed zero address', () => {
// zeroAddress is already lowercase, but ensure uppercase hex still matches
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
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

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
- 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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants