Bug: __mocks__/viem.js is incomplete — many consumers hit (0 , _viem.formatUnits) is not a function
Observed (verbatim from pnpm test --testPathPattern='ReferralLeaderboard' on origin/main)
FAIL src/components/__tests__/ReferralLeaderboard.test.tsx
● ReferralLeaderboard › ...
TypeError: (0 , _viem.formatUnits) is not a function
> 154 | formatUnits(BigInt(userRank.totalRewardsEarned), 18)
| ^
The error wording (0 , _viem.formatUnits) is the standard CommonJS interop pattern emitted by babel-jest when an import { formatUnits } from 'viem' resolves to an undefined property on the auto-applied manual mock (__mocks__/viem.js).
Reproduction
git checkout origin/main
pnpm test --testPathPattern='ReferralLeaderboard'
Result: every test that mounts ReferralLeaderboard (or any of the other listed consumers) crashes at the first formatUnits(...) call site. Subsequent downstream assertions (getByText('Alice'), getByText('Your Position'), etc.) all fail because the component never renders.
Root cause
__mocks__/viem.js is auto-applied by jest for every test that imports anything from viem. Its current contents are:
module.exports = {
recoverMessageAddress: jest.fn(() => Promise.resolve('0x123')),
};
So under test, viem resolves to { recoverMessageAddress: jest.fn(...) }. Every other viem API the production code references is undefined at test time.
Production code is correct: require('viem').formatUnits returns a function in viem ^2.44.4 (verified locally — installed version is 2.48.4, the export lives at node_modules/viem/_esm/utils/unit/formatUnits.js and is reachable from the top-level viem namespace). The mismatch is purely at the test/mock boundary.
Affected callers (every formatUnits consumer on origin/main)
| File |
Line(s) |
First-seen error |
src/components/referral/ReferralLeaderboard.tsx |
154, 252 |
yes (the one in the report) |
src/components/referral/ClaimRewardsModal.tsx |
— |
yes |
src/components/referral/ReferralStatsCard.tsx |
— |
yes |
src/components/referral/RewardsDisplay.tsx |
— |
yes |
src/components/referral/QuickStats.tsx |
— |
yes |
src/components/GasEstimator.tsx |
— |
yes |
Any future viem API consumer (e.g., formatEther, parseUnits, parseEther, encodeAbiParameters, keccak256, toHex, etc.) added to a component that's imported from a test will hit the same failure mode until the mock is filled out.
Suggested fix
Extend __mocks__/viem.js to provide deterministic stubs for the APIs actually consumed in the codebase. Minimal, faithful example mirroring viem's real semantics:
// __mocks__/viem.js
// Auto-applied by jest for every test that touches `viem` (see jest.config.cjs).
// Production code is unaffected: this only takes effect in the test runner.
const bigIntToDecimalString = (value, decimals) => {
const bi = typeof value === 'bigint' ? value : BigInt(value);
const sign = bi < 0n ? '-' : '';
const abs = bi < 0n ? -bi : bi;
const base = 10n ** BigInt(decimals);
const whole = abs / base;
const fraction = abs % base;
if (fraction === 0n) return `${sign}${whole}`;
const fractionStr = fraction.toString().padStart(decimals, '0').replace(/0+$/, '');
return `${sign}${whole}.${fractionStr}`;
};
module.exports = {
// existing
recoverMessageAddress: jest.fn(() => Promise.resolve('0x123')),
// unit formatting (consumed by 6 components today)
formatUnits: jest.fn((value, decimals = 18) =>
bigIntToDecimalString(value, decimals)
),
formatEther: jest.fn((value) => bigIntToDecimalString(value, 18)),
parseUnits: jest.fn((value, decimals = 18) => {
const [whole, fraction = ''] = String(value).split('.');
const padded = (fraction + '0'.repeat(decimals)).slice(0, decimals);
return BigInt((whole || '0') + padded);
}),
// future-proofing for viem APIs the codebase is likely to adopt
keccak256: jest.fn(() => '0x' + '0'.repeat(64)),
toHex: jest.fn((v) => '0x' + Buffer.from(String(v)).toString('hex')),
};
Acceptance criteria
pnpm test --testPathPattern='ReferralLeaderboard' passes (no TypeError at line 154 or 252).
- The other 5 consumer test suites also pass without crashing on
formatUnits.
- Production code is unchanged —
node -e "require('viem').formatUnits('12345',3)" still returns the live viem result when the mock is not loaded.
- CI gate (jest) stays at currently-green status; no introduction of new test flakes.
Why a separate issue (not a PR with PR #612)
PR #612 (MettaChain/PropChain-FrontEnd#612, branch perf/stellar-wave-adiz4415-503-506) is already touching 11 files in the same Stellar Wave perf tranche and is on its way to merge. Folding a fix for an orthogonal test-mock gap into that PR would obscure the perf narrative in review. Filing here so a focused PR can target just __mocks__/viem.js (and any related fixtures) without scope creep.
Suggestion for the assignee
- Run
grep -rn "from 'viem'" src tests to enumerate every viem-named-import consumer. Each API referenced is a candidate stub for __mocks__/viem.js.
- After extending the mock, run
pnpm test (full suite) and confirm no (0 , _viem.X) is not a function regressions on any previously-passing suite.
- Optionally, add a tiny
__mocks__/viem.test.ts that asserts the mock returns deterministic values for representative inputs, so future viem-API additions can't quietly regress again.
Bug:
__mocks__/viem.jsis incomplete — many consumers hit(0 , _viem.formatUnits) is not a functionObserved (verbatim from
pnpm test --testPathPattern='ReferralLeaderboard'onorigin/main)The error wording
(0 , _viem.formatUnits)is the standard CommonJS interop pattern emitted bybabel-jestwhen animport { formatUnits } from 'viem'resolves to an undefined property on the auto-applied manual mock (__mocks__/viem.js).Reproduction
Result: every test that mounts
ReferralLeaderboard(or any of the other listed consumers) crashes at the firstformatUnits(...)call site. Subsequent downstream assertions (getByText('Alice'),getByText('Your Position'), etc.) all fail because the component never renders.Root cause
__mocks__/viem.jsis auto-applied by jest for every test that imports anything fromviem. Its current contents are:So under test,
viemresolves to{ recoverMessageAddress: jest.fn(...) }. Every other viem API the production code references isundefinedat test time.Production code is correct:
require('viem').formatUnitsreturns a function in viem^2.44.4(verified locally — installed version is2.48.4, the export lives atnode_modules/viem/_esm/utils/unit/formatUnits.jsand is reachable from the top-levelviemnamespace). The mismatch is purely at the test/mock boundary.Affected callers (every
formatUnitsconsumer onorigin/main)src/components/referral/ReferralLeaderboard.tsxsrc/components/referral/ClaimRewardsModal.tsxsrc/components/referral/ReferralStatsCard.tsxsrc/components/referral/RewardsDisplay.tsxsrc/components/referral/QuickStats.tsxsrc/components/GasEstimator.tsxAny future viem API consumer (e.g.,
formatEther,parseUnits,parseEther,encodeAbiParameters,keccak256,toHex, etc.) added to a component that's imported from a test will hit the same failure mode until the mock is filled out.Suggested fix
Extend
__mocks__/viem.jsto provide deterministic stubs for the APIs actually consumed in the codebase. Minimal, faithful example mirroring viem's real semantics:Acceptance criteria
pnpm test --testPathPattern='ReferralLeaderboard'passes (noTypeErrorat line 154 or 252).formatUnits.node -e "require('viem').formatUnits('12345',3)"still returns the live viem result when the mock is not loaded.Why a separate issue (not a PR with PR #612)
PR #612(MettaChain/PropChain-FrontEnd#612, branchperf/stellar-wave-adiz4415-503-506) is already touching 11 files in the same Stellar Wave perf tranche and is on its way to merge. Folding a fix for an orthogonal test-mock gap into that PR would obscure the perf narrative in review. Filing here so a focused PR can target just__mocks__/viem.js(and any related fixtures) without scope creep.Suggestion for the assignee
grep -rn "from 'viem'" src teststo enumerate every viem-named-import consumer. Each API referenced is a candidate stub for__mocks__/viem.js.pnpm test(full suite) and confirm no(0 , _viem.X) is not a functionregressions on any previously-passing suite.__mocks__/viem.test.tsthat asserts the mock returns deterministic values for representative inputs, so future viem-API additions can't quietly regress again.