mirror of
https://github.com/luxfi/safe-wallet.git
synced 2026-10-30 15:19:21 +00:00
* feat: pre-fetch chains config at build time for instant startup
Fetch all chain configurations from the CGW API at build time and save
as static JSON. On the client, seed the RTK Query cache with this data
immediately after hydration so useGetChainsConfigQuery() returns data
without a loading state, then trigger a background refetch for freshness.
- Add build script (fetch-chains.ts) that fetches paginated chains
- Seed RTK Query cache via upsertQueryData in a client-only useEffect
- Await upsert completion before calling initiate({ forceRefetch: true })
to avoid RTK Query request deduplication
- Increase chains page limit to 50 to reduce pagination round-trips
- Export apiSliceWithChainsConfig and chainsInitialState from store
* fix: seed chains cache in makeStore() for earlier dependent requests
Move upsertQueryData from useEffect to makeStore() so the RTK Query
cache is seeded synchronously at store creation. The upsert resolves
via microtask (before any useEffect fires), making chains data
available earlier and unblocking dependent API requests (safe info,
balances, history) sooner. Both server and client see 'pending' state
during hydration, so no SSR mismatch occurs.
* fix: move chains cache seeding back to useEffect to avoid hydration errors
Revert the makeStore() injection approach — upsertQueryData's microtask
resolves during hydration on the client but not on the server, causing
a mismatch in chain-dependent components (ChainIndicator renders a
skeleton on server but real content on client).
The useEffect approach is hydration-safe: both server and client render
without chains data during hydration, then chains become available in
the first effect cycle.
* chore: add generated chains config to prettierignore
The build-time fetch-chains script writes to src/config/__generated__/
which is already gitignored. Adding to .prettierignore so prettier
doesn't flag it.
* fix: use dynamic require for build-time chains data to fix CI
The static `import ... from '@/config/__generated__/chains.json'`
fails in CI environments where type-check and tests run without
a prior build step (the file is generated by fetch-chains.ts).
Switch to a dynamic require wrapped in try/catch so the module
gracefully falls back to an empty array when the file is absent.
* fix: remove stray blank line in store/index.ts for prettier
* refactor: address review feedback for build-time chains prefetch
- Use `string | null` + nullish coalescing for pagination URL in
fetch-chains.ts for safer next-page handling
- Add clarifying comments on the staticChainsData.length > 0 guard
- Add unit tests for useInitStaticChains hook (dispatch + cleanup)
* feat: seed RTK Query cache via preloadedState for SSG support
Move chain data seeding from useEffect (upsertQueryData) to synchronous
preloadedState in makeStore(). This makes pre-cached chains available
during SSG/SSR, avoiding the client-only limitation of the previous
approach. The purely synchronous preloaded state is identical on both
server and client, preventing hydration mismatches.
Simplify useInitStaticChains to only trigger background refetch.
* feat: parallelize initial API requests on page load
Eliminate sequential blocking of safe info, tx queue, tx history, and
balances requests by addressing two bottlenecks:
1. useSafeAddressFromUrl: Add location.search fallback (same pattern as
useUrlChainId) so the safe address is available on first render
instead of waiting ~600ms for Next.js router hydration.
2. useLoadTxQueue/useLoadTxHistory/useLoadBalances: Use URL-derived
chainId + address directly instead of waiting for safeLoaded. Requests
now fire as soon as chainId and address are known from the URL, in
parallel with safe info. Undeployed safe handling is preserved — empty
results are returned once safe info confirms not deployed.
* refactor: extract useEffectiveSafeParams hook to reduce complexity
Extract the duplicated effectiveAddress/effectiveChainId pattern into a
shared hook, reducing cyclomatic complexity in usePortfolioBalances,
useTxServiceBalances, useLoadTxHistory, and useLoadTxQueue.
* tests: fix unit, smoke, and storybook tests for parallel API requests
- Unit test: mock location.search in useSafeAddressFromUrl tests to
prevent stale values from the location.search fallback leaking between
tests. Add test case for the new fallback behavior.
- Smoke test: move cy.intercept before cy.visit in dashboard mock tests
so the queue endpoint mock catches the request that now fires in
parallel with page load (instead of after safe info completes).
- Storybook: add next/router mock to storybook-setup.ts since
useEffectiveSafeParams now calls useSafeAddressFromUrl (which uses
useRouter) in the render path of usePortfolioBalances.
* fix: use next/compat/router to handle unmounted router in storybook
Replace next/router with next/compat/router in useSafeAddressFromUrl.
The compat version returns null instead of throwing when the router is
not mounted, allowing storybook decorator-provided router context to
work properly without needing a global jest.mock override.
* fix: register cy.intercept before cy.visit in tx_history smoke test
The history API request now fires immediately on page load (parallel
with other initial requests). The intercept must be registered before
cy.visit so the mock is in place when the request fires.
* fix: use cy.intercept fixture shorthand in tx_history smoke test
Replace cy.fixture().then() with cy.intercept({ fixture: '...' }) to
ensure the intercept is registered synchronously before cy.visit. This
eliminates potential timing issues with the .then() callback and
Cypress command queue ordering.
* fix: make tx history intercept pattern specific to CGW API
The broad pattern `**/history**` was matching the page URL
`/transactions/history?safe=...` when cy.intercept was registered
before cy.visit, causing the page navigation to receive fixture JSON
instead of HTML. Use a more specific pattern that only matches the
CGW API endpoint `/v1/.../transactions/history`.
* fix: register cy.intercept before cy.visit in add_owner and address_book smoke tests
With parallel API requests firing immediately on page load,
cy.intercept must be registered before cy.visit so the history
API call is captured. Replace waitForHistoryCallToComplete()
(which registered the intercept after visit) with inline
intercept→visit→wait sequence.
* tests: update storybook snapshots for preloaded chain data
ChainIndicator, TokenIcon, and NetworkLogosList snapshots now
reflect the actual chain data (Ethereum logo, name, etc.) instead
of Skeleton loading state, because makeStore() seeds the RTK Query
cache with build-time chain configs.
---------
Co-authored-by: Hanzo Dev <[email protected]>
58 lines
2.3 KiB
JavaScript
58 lines
2.3 KiB
JavaScript
import * as constants from '../../support/constants'
|
|
import * as dashboard from '../pages/dashboard.pages'
|
|
import * as main from '../pages/main.page.js'
|
|
import { getSafes, CATEGORIES } from '../../support/safes/safesHandler.js'
|
|
|
|
let staticSafes = []
|
|
|
|
const txData = ['Send', '-0.00002 ETH', '1/1']
|
|
const txaddOwner = ['addOwnerWithThreshold', '1/2']
|
|
const txMultiSendCall3 = ['Batch', '3 actions', '1/2']
|
|
const txMultiSendCall2 = ['Batch', '2 actions', '1/2']
|
|
|
|
describe('[SMOKE] Dashboard tests', { defaultCommandTimeout: 60000 }, () => {
|
|
before(async () => {
|
|
staticSafes = await getSafes(CATEGORIES.static)
|
|
})
|
|
|
|
beforeEach(() => {
|
|
cy.visit(constants.homeUrl + staticSafes.SEP_STATIC_SAFE_2)
|
|
})
|
|
|
|
it('[SMOKE] Verify the overview widget is displayed', () => {
|
|
dashboard.verifyOverviewWidgetData()
|
|
})
|
|
|
|
it('[SMOKE] Verify the transaction queue widget is displayed', () => {
|
|
dashboard.verifyTxQueueWidget()
|
|
})
|
|
|
|
it('[SMOKE] Verify the Safe Apps Section is displayed', () => {
|
|
dashboard.verifyExplorePossibleSection()
|
|
})
|
|
|
|
// mock — intercept must be set up before visit so the mock catches the parallel queue request
|
|
it('[SMOKE] Verify that the last created tx in conflicting tx is showed in the widget', () => {
|
|
cy.fixture('pending_tx/pending_tx.json').then((mockData) => {
|
|
cy.intercept('GET', constants.queuedEndpoint, mockData).as('getQueuedTransactions')
|
|
cy.visit(constants.homeUrl + staticSafes.SEP_STATIC_SAFE_2)
|
|
})
|
|
cy.wait('@getQueuedTransactions')
|
|
cy.get(dashboard.pendingTxWidget, { timeout: 30000 }).should('be.visible')
|
|
main.verifyElementsCount(dashboard.pendingTxItem, 1)
|
|
dashboard.verifyDataInPendingTx(txData)
|
|
})
|
|
|
|
// mock — intercept must be set up before visit so the mock catches the parallel queue request
|
|
it('[SMOKE] Verify that tx are displayed correctly in Pending tx section', () => {
|
|
cy.fixture('pending_tx/pending_tx_order.json').then((mockData) => {
|
|
cy.intercept('GET', constants.queuedEndpoint, mockData).as('getQueuedTransactions')
|
|
cy.visit(constants.homeUrl + staticSafes.SEP_STATIC_SAFE_2)
|
|
})
|
|
cy.wait('@getQueuedTransactions')
|
|
dashboard.verifyTxItemInPendingTx(txMultiSendCall3)
|
|
dashboard.verifyTxItemInPendingTx(txaddOwner)
|
|
dashboard.verifyTxItemInPendingTx(txMultiSendCall2)
|
|
})
|
|
})
|