Migrate from Jest to Vitest (#3173)

* first part of transition

* second part of transition

* remove jest

* bump up node version and playwright version

* update all screenshots because of the new playwright version

* fix tests

* one more screenshot update
This commit is contained in:
tom goriunov
2025-12-10 19:21:07 +01:00
committed by GitHub
parent fb7fa1ba98
commit 5d337fc555
899 changed files with 1668 additions and 2491 deletions
+11 -11
View File
@@ -34,7 +34,7 @@ jobs:
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 22.11.0
node-version: 22.14.0
cache: 'yarn'
- name: Cache node_modules
@@ -70,7 +70,7 @@ jobs:
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 22.11.0
node-version: 22.14.0
cache: 'yarn'
- name: Cache node_modules
@@ -133,7 +133,7 @@ jobs:
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 22.11.0
node-version: 22.14.0
cache: 'yarn'
- name: Cache node_modules
@@ -163,8 +163,8 @@ jobs:
echo "exitcode=$exitcode" >> $GITHUB_OUTPUT
exit "$exitcode"
jest_tests:
name: Jest tests
vitest_tests:
name: Unit tests
needs: [ code_quality, envs_validation ]
runs-on: ubuntu-latest
steps:
@@ -176,7 +176,7 @@ jobs:
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 22.11.0
node-version: 22.14.0
cache: 'yarn'
- name: Cache node_modules
@@ -195,8 +195,8 @@ jobs:
if: steps.cache-node-modules.outputs.cache-hit == 'true'
run: yarn chakra:typegen
- name: Run Jest
run: yarn test:jest ${{ github.event_name == 'pull_request' && '--changedSince=origin/main' || '' }} --passWithNoTests
- name: Run Vitest
run: yarn test:vitest ${{ github.event_name == 'pull_request' && '--changed=origin/main' || '' }} --passWithNoTests
pw_affected_tests:
name: Resolve affected Playwright tests
@@ -212,7 +212,7 @@ jobs:
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 22.11.0
node-version: 22.14.0
cache: 'yarn'
- name: Cache node_modules
@@ -254,7 +254,7 @@ jobs:
(needs.pw_affected_tests.result == 'success' || needs.pw_affected_tests.result == 'skipped')
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.49.0-noble
image: mcr.microsoft.com/playwright:v1.57.0-noble
strategy:
fail-fast: false
@@ -273,7 +273,7 @@ jobs:
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 22.11.0
node-version: 22.14.0
cache: 'yarn'
- name: Cache node_modules
+1 -1
View File
@@ -9,7 +9,7 @@ on:
- '.husky/**'
- '.vscode/**'
- 'docs/**'
- 'jest/**'
- 'vitest/**'
- 'mocks/**'
- 'playwright/**'
- 'stubs/**'
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: '22.11.0'
node-version: '22.14.0'
registry-url: 'https://registry.npmjs.org'
# Ensure npm 11.5.1 is installed
+1 -1
View File
@@ -1 +1 @@
22.11.0
>=22.14.0
+1 -19
View File
@@ -3,23 +3,5 @@
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Jest: watch current file",
"program": "${workspaceFolder}/node_modules/jest/bin/jest",
"args": [
"${fileBasename}",
"--runInBand",
"--verbose",
"-i",
"--no-cache",
"--watchAll",
"--testTimeout=1000000000",
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}
]
"configurations": [ ]
}
+7 -28
View File
@@ -198,34 +198,13 @@
},
},
// JEST TESTS
// VITEST TESTS
{
"type": "npm",
"script": "test:jest",
"script": "test:vitest",
"problemMatcher": [],
"label": "jest",
"detail": "run jest tests",
"presentation": {
"reveal": "always",
"panel": "shared",
"focus": true,
"close": false,
"revealProblems": "onProblem",
},
"icon": {
"color": "terminal.ansiBlue",
"id": "beaker"
},
"runOptions": {
"instanceLimit": 1
}
},
{
"type": "npm",
"script": "test:jest:watch",
"problemMatcher": [],
"label": "jest: watch all",
"detail": "run jest tests in watch mode",
"label": "vitest",
"detail": "run unit tests",
"presentation": {
"reveal": "always",
"panel": "shared",
@@ -243,10 +222,10 @@
},
{
"type": "shell",
"command": "yarn test:jest ${relativeFileDirname}/${fileBasename} --watch",
"command": "yarn test:vitest ${relativeFileDirname}/${fileBasename}",
"problemMatcher": [],
"label": "jest: watch",
"detail": "run jest tests in watch mode for current file",
"label": "vitest: file",
"detail": "run unit tests for current file",
"presentation": {
"reveal": "always",
"panel": "shared",
+3 -3
View File
@@ -1,7 +1,7 @@
# *****************************
# *** STAGE 1: Dependencies ***
# *****************************
FROM node:22.11.0-alpine AS deps
FROM node:22.14.0-alpine AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat python3 make g++
RUN ln -sf /usr/bin/python3 /usr/bin/python
@@ -67,7 +67,7 @@ RUN yarn --frozen-lockfile --network-timeout 100000
# *****************************
# ****** STAGE 2: Build *******
# *****************************
FROM node:22.11.0-alpine AS builder
FROM node:22.14.0-alpine AS builder
RUN apk add --no-cache --upgrade libc6-compat bash jq
# pass build args to env variables
@@ -144,7 +144,7 @@ RUN cd ./deploy/tools/llms-txt-generator && yarn build
# ******* STAGE 3: Run ********
# *****************************
# Production image, copy all the files and run next
FROM node:22.11.0-alpine AS runner
FROM node:22.14.0-alpine AS runner
RUN apk add --no-cache --upgrade bash curl jq unzip
### APP
@@ -1,4 +1,4 @@
# Set of ENVs for Jest unit tests
# Set of ENVs for Vitest unit tests
# app configuration
NEXT_PUBLIC_APP_PROTOCOL=http
@@ -37,7 +37,7 @@ NEXT_PUBLIC_GIT_TAG=v1.0.11
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]
# app features
NEXT_PUBLIC_APP_INSTANCE=jest
NEXT_PUBLIC_APP_INSTANCE=vitest
NEXT_PUBLIC_APP_ENV=testing
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL=https://localhost:3000/marketplace-config.json
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://localhost:3000/marketplace-submit-form
+6 -9
View File
@@ -47,7 +47,7 @@ We are using following technology stack in the project
- [Next.js](https://nextjs.org/) as application framework
- [Chakra](https://chakra-ui.com/) as component library; our theme customization can be found in `/theme` folder
- [TanStack Query](https://tanstack.com/query/v4/docs/react/overview/) for fetching, caching and updating data from the API
- [Jest](https://jestjs.io/) as JavaScript testing framework
- [Vitest](https://vitest.dev/) as JavaScript testing framework
- [Playwright](https://playwright.dev/) as a tool for components visual testing
And of course our premier language is [Typescript](https://www.typescriptlang.org/).
@@ -119,13 +119,13 @@ These are the steps that you have to follow to make everything work:
Every feature or bugfix should be accompanied by tests, either unit tests or component visual tests, or both, except from trivial fixes (for example, typo fix). All commands for running tests you can find [below](./CONTRIBUTING.md#command-list).
### Jest unit tests
### Vitest unit tests
If your changes are only related to the logic of the app and not to its visual presentation, then try to write unit tests using [Jest](https://jestjs.io/) framework and [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/). In general these tests are "cheaper" and faster than Playwright ones. Use them for testing your utilities and React hooks, as well as the whole components logic.
If your changes are only related to the logic of the app and not to its visual presentation, then try to write unit tests using [Vitest](https://vitest.dev/) framework and [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/). In general these tests are "cheaper" and faster than Playwright ones. Use them for testing your utilities and React hooks, as well as the whole components logic.
Place your test suites in `.test.ts` or `.test.tsx` files. You can find or add some mocks or other helpful utilities for these tests purposes in the `/jest` folder.
Place your test suites in `.spec.ts` or `.spec.tsx` files. You can find or add some mocks or other helpful utilities for these tests purposes in the `/vitest` folder.
*Note*, that we are using custom renderer and wrapper in all tests for React components, so please do not import package `@testing-library/react` directly in your test suites, instead use imports from `jest/lib` utility.
*Note*, that we are using custom renderer and wrapper in all tests for React components, so please do not import package `@testing-library/react` directly in your test suites, instead use imports from `vitest/lib` utility.
### Playwright components tests
@@ -198,8 +198,7 @@ We have 3 pre-configured projects. You can run your test with the desired projec
| `yarn svg:format` | format and optimize SVG icons in the `/icons` folder using SVGO tool |
| `yarn svg:build-sprite` | build SVG icons sprite |
| **Testing** |
| `yarn test:jest` | run all Jest unit tests |
| `yarn test:jest:watch` | run all Jest unit tests in watch mode |
| `yarn test:vitest` | run all Vitest unit tests |
| `yarn test:pw:local` | run Playwright component tests locally |
| `yarn test:pw:docker` | run Playwright component tests in docker container |
| `yarn test:pw:ci` | run Playwright component tests in CI |
@@ -214,6 +213,4 @@ We have 3 pre-configured projects. You can run your test with the desired projec
There are some predefined tasks for all commands described above. You can see the full list by pressing <kbd>cmd + shift + P</kbd> and using command `Task: Run task`
Also there is a Jest test launch configuration for debugging and running current test file in the watch mode.
And you may find the Dev Container setup useful too.
+16 -7
View File
@@ -6,13 +6,13 @@ import reactQueryPlugin from '@tanstack/eslint-plugin-query';
import consistentDefaultExportNamePlugin from 'eslint-plugin-consistent-default-export-name';
import importPlugin from 'eslint-plugin-import';
import importHelpersPlugin from 'eslint-plugin-import-helpers';
import jestPlugin from 'eslint-plugin-jest';
import jsxA11yPlugin from 'eslint-plugin-jsx-a11y';
import noCyrillicStringPlugin from 'eslint-plugin-no-cyrillic-string';
import playwrightPlugin from 'eslint-plugin-playwright';
import reactPlugin from 'eslint-plugin-react';
import reactHooksPlugin from 'eslint-plugin-react-hooks';
import * as regexpPlugin from 'eslint-plugin-regexp';
import vitestPlugin from 'eslint-plugin-vitest';
import globals from 'globals';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -79,14 +79,12 @@ export default tseslint.config(
{
plugins: {
'@typescript-eslint': tseslint.plugin,
jest: jestPlugin,
},
languageOptions: {
parser: tseslint.parser,
parserOptions: {
projectService: true,
},
globals: jestPlugin.environments.globals.globals,
},
rules: {
'@typescript-eslint/array-type': [ 'error', {
@@ -261,10 +259,21 @@ export default tseslint.config(
},
{
files: [ '**/*.test.{ts,js,jsx,tsx}' ],
plugins: { jest: jestPlugin },
files: [ '**/*.spec.{ts,js,jsx,tsx}' ],
plugins: { vitest: vitestPlugin },
rules: {
...vitestPlugin.configs.recommended.rules,
},
settings: {
vitest: {
typecheck: true,
},
},
languageOptions: {
globals: jestPlugin.environments.globals.globals,
globals: {
...vitestPlugin.environments.env.globals,
fetchMock: true,
},
},
},
@@ -297,7 +306,6 @@ export default tseslint.config(
'/^data/',
'/^deploy/',
'/^icons/',
'/^jest/',
'/^lib/',
'/^mocks/',
'/^pages/',
@@ -306,6 +314,7 @@ export default tseslint.config(
'/^theme/',
'/^toolkit/',
'/^ui/',
'/^vitest/',
],
[ 'parent', 'sibling', 'index' ],
],
Vendored
+1
View File
@@ -1,6 +1,7 @@
import type { EssentialDappsChainConfig } from 'types/client/marketplace';
import type { MultichainConfig } from 'types/multichain';
import type { WalletProvider } from 'types/web3';
import 'vitest-fetch-mock';
type CPreferences = {
zone: string;
-43
View File
@@ -1,43 +0,0 @@
import type { JestConfigWithTsJest } from 'ts-jest';
/*
* For a detailed explanation regarding each configuration property and type check, visit:
* https://jestjs.io/docs/configuration
*/
const config: JestConfigWithTsJest = {
clearMocks: true,
coverageProvider: 'v8',
globalSetup: '<rootDir>/jest/global-setup.ts',
moduleDirectories: [
'node_modules',
__dirname,
],
moduleNameMapper: {
'^jest/(.*)': '<rootDir>/jest/$1',
'^nextjs-routes$': '<rootDir>/jest/mocks/nextjs-routes.js',
'\\.svg$': '<rootDir>/jest/mocks/svg.js',
'^@uidotdev/usehooks$': '<rootDir>/jest/mocks/usehooks.js',
},
modulePathIgnorePatterns: [
'node_modules_linux',
],
preset: 'ts-jest',
reporters: [ 'default', 'github-actions' ],
setupFiles: [
'<rootDir>/jest/setup.ts',
],
testEnvironment: 'jsdom',
transform: {
// '^.+\\.[tj]sx?$' to process js/ts with `ts-jest`
// '^.+\\.m?[tj]sx?$' to process js/ts/mjs/mts with `ts-jest`
'^.+\\.tsx?$': [
'ts-jest',
{
tsconfig: 'tsconfig.jest.json',
},
],
},
};
export default config;
-5
View File
@@ -1,5 +0,0 @@
import dotenv from 'dotenv';
export default async function globalSetup() {
dotenv.config({ path: './configs/envs/.env.jest' });
}
-17
View File
@@ -1,17 +0,0 @@
import type { NextRouter } from 'next/router';
export const router = {
query: {},
push: jest.fn(() => Promise.resolve()),
};
export const useRouter = jest.fn<unknown, Array<Partial<NextRouter>>>(() => (router));
export const mockUseRouter = (params?: Partial<NextRouter>) => {
return {
useRouter: jest.fn(() => ({
...router,
...params,
})),
};
};
-18
View File
@@ -1,18 +0,0 @@
module.exports = {
route: jest.fn((opts) => {
const pathname = opts?.pathname;
const query = opts?.query || {};
if (pathname === '/address/[hash]') {
return `/address/${ query.hash || 'test-hash' }`;
}
if (pathname === '/tx/[hash]') {
return `/tx/${ query.hash || 'test-hash' }`;
}
if (pathname === '/name-services/clusters/[name]') {
return `/name-services/clusters/${ query.name || 'test-cluster' }`;
}
return pathname || '/';
}),
};
-1
View File
@@ -1 +0,0 @@
module.exports = 'test-file-stub';
-12
View File
@@ -1,12 +0,0 @@
module.exports = {
useClickAway: jest.fn(() => jest.fn()),
useEventListener: jest.fn(),
useLocalStorage: jest.fn(() => [ '', jest.fn() ]),
useSessionStorage: jest.fn(() => [ '', jest.fn() ]),
useToggle: jest.fn(() => [ false, jest.fn() ]),
useDebounce: jest.fn((value) => value),
useThrottle: jest.fn((value) => value),
usePrevious: jest.fn(),
useCounter: jest.fn(() => ({ count: 0, increment: jest.fn(), decrement: jest.fn(), reset: jest.fn() })),
useCopyToClipboard: jest.fn(() => [ '', jest.fn() ]),
};
-55
View File
@@ -1,55 +0,0 @@
import dotenv from 'dotenv';
import { TextEncoder, TextDecoder } from 'util';
import fetchMock from 'jest-fetch-mock';
fetchMock.enableMocks();
const envs = dotenv.config({ path: './configs/envs/.env.jest' });
Object.assign(global, { TextDecoder, TextEncoder });
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
Object.defineProperty(window, '__envs', {
writable: true,
value: envs.parsed || {},
});
// eslint-disable-next-line no-console
const consoleError = console.error;
global.console = {
...console,
error: (...args) => {
// silence some irrelevant errors
if (args.some((arg) => typeof arg === 'string' && arg.includes('Using kebab-case for css properties'))) {
return;
}
consoleError(...args);
},
};
// Polyfill for structuredClone
if (typeof structuredClone === 'undefined') {
global.structuredClone = <T>(obj: T): T => {
try {
return JSON.parse(JSON.stringify(obj)) as T;
} catch (error) {
// Fallback for circular references and other special cases
return obj;
}
};
}
@@ -1,3 +1,5 @@
import { expect, test, describe } from 'vitest';
import buildUrl from './buildUrl';
test('builds URL for resource without path params', () => {
@@ -1,3 +1,5 @@
import { describe, it, expect } from 'vitest';
import {
shouldShowClearButton,
shouldDisableViewToggle,
@@ -1,3 +1,5 @@
import { describe, it, expect } from 'vitest';
import {
filterOwnedClusters,
getTotalRecordsDisplay,
@@ -1,4 +1,5 @@
import { isEvmAddress } from 'lib/address/isEvmAddress';
import { describe, it, expect } from 'vitest';
import { detectInputType } from './detectInputType';
@@ -1,6 +1,8 @@
import { ClustersOrderBy } from 'types/api/clusters';
import type { ClustersDirectoryObject } from 'types/api/clusters';
import { describe, it, expect } from 'vitest';
import {
getViewModeOrderBy,
shouldShowDirectoryView,
@@ -1,32 +1,47 @@
// @vitest-environment jsdom
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { renderHook } from 'vitest/lib';
import { useAddressClusters } from './useAddressClusters';
jest.mock('lib/api/useApiQuery', () => ({
__esModule: true,
'default': jest.fn(),
const { mockUseApiQuery } = vi.hoisted(() => ({
mockUseApiQuery: vi.fn(),
}));
jest.mock('configs/app', () => ({
features: {
nameServices: {
isEnabled: true,
ens: { isEnabled: true },
clusters: { isEnabled: true },
vi.mock('lib/api/useApiQuery', () => ({
'default': mockUseApiQuery,
}));
vi.mock('configs/app', async() => {
return {
'default': {
UI: {
colorTheme: {},
homepage: {},
fonts: {},
},
features: {
nameServices: {
isEnabled: true,
ens: { isEnabled: true },
clusters: { isEnabled: true },
},
},
},
},
}));
const mockUseApiQuery = require('lib/api/useApiQuery').default;
};
});
describe('useAddressClusters', () => {
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
mockUseApiQuery.mockReturnValue({ data: null, isLoading: false });
});
it('should call API with correct parameters', () => {
const addressHash = '0x1234567890123456789012345678901234567890';
useAddressClusters(addressHash);
renderHook(() => useAddressClusters(addressHash));
expect(mockUseApiQuery).toHaveBeenCalledWith('clusters:get_clusters_by_address', {
queryParams: {
@@ -41,7 +56,7 @@ describe('useAddressClusters', () => {
});
it('should be disabled when addressHash is empty', () => {
useAddressClusters('');
renderHook(() => useAddressClusters(''));
expect(mockUseApiQuery).toHaveBeenCalledWith('clusters:get_clusters_by_address', {
queryParams: {
@@ -58,7 +73,7 @@ describe('useAddressClusters', () => {
it('should handle isEnabled parameter', () => {
const addressHash = '0x1234567890123456789012345678901234567890';
useAddressClusters(addressHash, false);
renderHook(() => useAddressClusters(addressHash, false));
expect(mockUseApiQuery).toHaveBeenCalledWith('clusters:get_clusters_by_address', {
queryParams: {
@@ -1,25 +1,30 @@
import { renderHook, act } from 'jest/lib';
// @vitest-environment jsdom
import { useRouter } from 'next/router';
import getQueryParamString from 'lib/router/getQueryParamString';
import { useQueryParams } from 'lib/router/useQueryParams';
import type { Mock } from 'vitest';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { renderHook, act } from 'vitest/lib';
import { useClusterPagination } from '../clusters/useClusterPagination';
jest.mock('next/router', () => ({
useRouter: jest.fn(),
vi.mock('next/router', () => ({
useRouter: vi.fn(),
}));
jest.mock('lib/router/useQueryParams');
jest.mock('lib/router/getQueryParamString');
vi.mock('lib/router/useQueryParams');
vi.mock('lib/router/getQueryParamString');
const { useRouter } = require('next/router');
const mockUseRouter = useRouter as jest.MockedFunction<typeof useRouter>;
const mockUseQueryParams = useQueryParams as jest.MockedFunction<typeof useQueryParams>;
const mockGetQueryParamString = getQueryParamString as jest.MockedFunction<typeof getQueryParamString>;
const mockUseRouter = useRouter as Mock<typeof useRouter>;
const mockUseQueryParams = useQueryParams as Mock<typeof useQueryParams>;
const mockGetQueryParamString = getQueryParamString as Mock<typeof getQueryParamString>;
describe('useClusterPagination', () => {
const mockUpdateQuery = jest.fn();
const mockUpdateQuery = vi.fn();
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
mockUseRouter.mockReturnValue({
query: {},
} as unknown as ReturnType<typeof useRouter>);
@@ -1,46 +1,50 @@
// @vitest-environment jsdom
import { useRouter } from 'next/router';
import { renderHook } from 'jest/lib';
import type { Mock } from 'vitest';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { renderHook } from 'vitest/lib';
import { useClusterSearch } from './useClusterSearch';
jest.mock('next/router', () => ({
useRouter: jest.fn(),
vi.mock('next/router', () => ({
useRouter: vi.fn(),
}));
const mockUseRouter = useRouter as jest.MockedFunction<typeof useRouter>;
const mockUseRouter = useRouter as Mock<typeof useRouter>;
describe('useClusterSearch', () => {
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
});
it('should return search term from router query', () => {
it('should return search term from router query', async() => {
mockUseRouter.mockReturnValue({
query: { q: 'test-search' },
} as unknown as ReturnType<typeof useRouter>);
const { result } = renderHook(() => useClusterSearch());
const { result } = await renderHook(() => useClusterSearch());
expect(result.current.searchTerm).toBe('test-search');
});
it('should debounce search term', () => {
it('should debounce search term', async() => {
mockUseRouter.mockReturnValue({
query: { q: 'test' },
} as unknown as ReturnType<typeof useRouter>);
const { result } = renderHook(() => useClusterSearch());
const { result } = await renderHook(() => useClusterSearch());
expect(result.current.debouncedSearchTerm).toBe('test');
});
it('should handle empty query', () => {
it('should handle empty query', async() => {
mockUseRouter.mockReturnValue({
query: {},
} as unknown as ReturnType<typeof useRouter>);
const { result } = renderHook(() => useClusterSearch());
const { result } = await renderHook(() => useClusterSearch());
expect(result.current.searchTerm).toBe('');
expect(result.current.debouncedSearchTerm).toBe('');
@@ -1,23 +1,28 @@
// @vitest-environment jsdom
import { renderHook } from '@testing-library/react';
import useApiQuery from 'lib/api/useApiQuery';
import { detectInputType } from 'lib/clusters/detectInputType';
import type { Mock } from 'vitest';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { useClustersData } from './useClustersData';
jest.mock('lib/api/useApiQuery');
const mockUseApiQuery = useApiQuery as jest.MockedFunction<typeof useApiQuery>;
vi.mock('lib/api/useApiQuery');
const mockUseApiQuery = useApiQuery as Mock<typeof useApiQuery>;
type MockQueryResult = ReturnType<typeof useApiQuery>;
jest.mock('lib/clusters/detectInputType', () => ({
detectInputType: jest.fn(),
vi.mock('lib/clusters/detectInputType', () => ({
detectInputType: vi.fn(),
}));
const mockDetectInputType = require('lib/clusters/detectInputType').detectInputType;
const mockDetectInputType = detectInputType as Mock<typeof detectInputType>;
describe('useClustersData', () => {
beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();
mockUseApiQuery.mockReturnValue({
data: null,
@@ -183,7 +188,7 @@ describe('useClustersData', () => {
{ initialProps: { searchTerm: 'search' } },
);
jest.clearAllMocks();
vi.clearAllMocks();
rerender({ searchTerm: 'search' });
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`generates correct metadata for: dynamic route 1`] = `
exports[`generates correct metadata for: > dynamic route 1`] = `
{
"canonical": undefined,
"description": "View transaction 0x12345 on Blockscout (Blockscout) Explorer",
@@ -14,7 +14,7 @@ exports[`generates correct metadata for: dynamic route 1`] = `
}
`;
exports[`generates correct metadata for: dynamic route with API data 1`] = `
exports[`generates correct metadata for: > dynamic route with API data 1`] = `
{
"canonical": undefined,
"description": "0x12345, balances and analytics on the Blockscout (Blockscout) Explorer",
@@ -45,7 +45,7 @@ exports[`generates correct metadata for: dynamic route with API data 1`] = `
}
`;
exports[`generates correct metadata for: static route 1`] = `
exports[`generates correct metadata for: > static route 1`] = `
{
"canonical": "http://localhost:3000/txs",
"description": "Open-source block explorer by Blockscout. Search transactions, verify smart contracts, analyze addresses, and track network activity. Complete blockchain data and APIs for the Blockscout (Blockscout) Explorer network.",
@@ -2,6 +2,8 @@ import type { ApiData } from './types';
import type { Route } from 'nextjs-routes';
import { describe, it, expect } from 'vitest';
import generate from './generate';
interface TestCase<Pathname extends Route['pathname']> {
+14 -17
View File
@@ -4,8 +4,8 @@
"private": false,
"homepage": "https://github.com/blockscout/frontend#readme",
"engines": {
"node": "22.11.0",
"npm": "10.9.0"
"node": ">=22.14.0",
"npm": ">=10.9.0"
},
"scripts": {
"dev": "./tools/scripts/dev.sh",
@@ -28,12 +28,11 @@
"svg:build-sprite": "./deploy/scripts/build_sprite.sh",
"test:pw": "./tools/scripts/pw.sh",
"test:pw:local": "export NODE_PATH=$(pwd)/node_modules && yarn test:pw",
"test:pw:docker": "docker run --rm --ipc=host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.49.0-noble ./tools/scripts/pw.docker.sh",
"test:pw:docker:deps": "docker run --rm --ipc=host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.49.0-noble ./tools/scripts/pw.docker.deps.sh",
"test:pw:docker": "docker run --rm --ipc=host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.57.0-noble ./tools/scripts/pw.docker.sh",
"test:pw:docker:deps": "docker run --rm --ipc=host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.57.0-noble ./tools/scripts/pw.docker.deps.sh",
"test:pw:ci": "yarn test:pw --project=$PW_PROJECT",
"test:pw:detect-affected": "node ./deploy/tools/affected-tests/index.js",
"test:jest": "jest",
"test:jest:watch": "jest --watch",
"test:vitest": "vitest",
"favicon:generate:dev": "./tools/scripts/favicon-generator.dev.sh",
"og-image:generate:dev": "./tools/scripts/og-image-generator.dev.sh",
"sitemap:generate:dev": "./tools/scripts/sitemap-generator.dev.sh",
@@ -96,7 +95,7 @@
"dayjs": "^1.11.5",
"dom-to-image": "^2.6.0",
"es-toolkit": "1.39.10",
"esbuild": "0.21.5",
"esbuild": "0.25.12",
"focus-visible": "^5.2.0",
"gradient-avatar": "git+https://github.com/blockscout/gradient-avatar.git",
"graphiql": "^4.1.2",
@@ -137,12 +136,12 @@
"xss": "^1.0.14"
},
"devDependencies": {
"@chakra-ui/cli": "3.15.0",
"@chakra-ui/cli": "3.30.0",
"@eslint/compat": "1.2.2",
"@eslint/js": "9.14.0",
"@next/eslint-plugin-next": "15.0.3",
"@playwright/experimental-ct-react": "1.49.0",
"@playwright/test": "1.49.0",
"@playwright/experimental-ct-react": "1.57.0",
"@playwright/test": "1.57.0",
"@stylistic/eslint-plugin": "5.2.3",
"@svgr/webpack": "^6.5.1",
"@tanstack/eslint-plugin-query": "5.60.1",
@@ -152,7 +151,6 @@
"@types/csp-dev": "^1.0.0",
"@types/d3": "^7.4.0",
"@types/dom-to-image": "^2.6.4",
"@types/jest": "29.2.1",
"@types/js-cookie": "^3.0.2",
"@types/mixpanel-browser": "^2.38.1",
"@types/node": "20.16.7",
@@ -172,29 +170,28 @@
"eslint-plugin-consistent-default-export-name": "^0.0.15",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-import-helpers": "2.0.1",
"eslint-plugin-jest": "28.9.0",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-no-cyrillic-string": "^1.0.5",
"eslint-plugin-playwright": "2.0.1",
"eslint-plugin-react": "7.37.2",
"eslint-plugin-react-hooks": "5.0.0",
"eslint-plugin-regexp": "2.6.0",
"eslint-plugin-vitest": "0.5.4",
"globals": "15.12.0",
"husky": "^8.0.0",
"jest": "^29.2.1",
"jest-environment-jsdom": "^29.2.1",
"jest-fetch-mock": "^3.0.3",
"jsdom": "27.3.0",
"lint-staged": ">=10",
"mockdate": "^3.0.5",
"style-loader": "^3.3.1",
"svg-icons-cli": "0.0.8",
"svgo": "^2.8.0",
"ts-jest": "^29.0.3",
"ts-node": "^10.9.1",
"typescript": "5.9.2",
"typescript-eslint": "8.41.0",
"vite-plugin-svgr": "^2.2.2",
"vite-tsconfig-paths": "4.3.2",
"vite-tsconfig-paths": "5.1.4",
"vitest": "4.0.15",
"vitest-fetch-mock": "0.4.5",
"ws": "^8.17.1"
},
"lint-staged": {
+2 -1
View File
@@ -1,6 +1,7 @@
import type { PlaywrightTestConfig } from '@playwright/experimental-ct-react';
import { devices, defineConfig } from '@playwright/experimental-ct-react';
import react from '@vitejs/plugin-react';
import type { Plugin } from 'esbuild';
import svgr from 'vite-plugin-svgr';
import tsconfigPaths from 'vite-tsconfig-paths';
@@ -61,7 +62,7 @@ const config: PlaywrightTestConfig = defineConfig({
svgr({
exportAsDefault: true,
}),
],
] as unknown as Array<Plugin>,
build: {
// it actually frees some memory that vite needs a lot
// https://github.com/storybookjs/builder-vite/issues/409#issuecomment-1152848986
+1 -1
View File
@@ -63,7 +63,7 @@
"vite-tsconfig-paths": "5.1.4"
},
"engines": {
"node": ">=22.11.0"
"node": ">=22.14.0"
},
"publishConfig": {
"access": "public"
+1 -1
View File
@@ -15,7 +15,7 @@
"jsx": "preserve",
"incremental": true,
"baseUrl": ".",
"types": ["node", "jest"],
"types": ["node"],
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.node.ts", "**/*.tsx", "**/*.pw.tsx", "decs.d.ts", "global.d.ts"],
"exclude": ["node_modules", "node_modules_linux", "./deploy/tools/envs-validator", "./deploy/tools/favicon-generator", "./deploy/tools/multichain-config-generator", "./deploy/tools/essential-dapps-chains-config-generator", "./toolkit/package", "./deploy/tools/llms-txt-generator"],
Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Some files were not shown because too many files have changed in this diff Show More