diff --git a/.changeset/unlucky-vans-invent.md b/.changeset/unlucky-vans-invent.md new file mode 100644 index 0000000000..675bba875b --- /dev/null +++ b/.changeset/unlucky-vans-invent.md @@ -0,0 +1,5 @@ +--- +"fuels-wallet": patch +--- + +fix: jest unit tests diff --git a/.github/workflows/pr-tests-jest.yml b/.github/workflows/pr-tests-jest.yml index 52fd521c20..6bedb8ce43 100644 --- a/.github/workflows/pr-tests-jest.yml +++ b/.github/workflows/pr-tests-jest.yml @@ -11,8 +11,12 @@ concurrency: jobs: tests-jest: - name: Test - runs-on: buildjet-8vcpu-ubuntu-2204 + name: Jest Tests [Shard ${{ matrix.shard }}] + runs-on: buildjet-8vcpu-ubuntu-2204-arm + strategy: + fail-fast: false + matrix: + shard: [1, 2, 3, 4] steps: - uses: actions/checkout@v4 - uses: FuelLabs/github-actions/setups/node@master @@ -24,27 +28,11 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Start Test Node - run: pnpm node:up - - - name: Generate .env - run: cp packages/app/.env.example packages/app/.env - # Unit tests running with JEST - - name: Find PR number - uses: jwalton/gh-find-current-pr@v1 - id: findPr - - - name: Build libs - run: | - pnpm build:libs - name: Run Jest Tests - run: | - pnpm test:ci + run: pnpm test:ci --shard ${{ matrix.shard }}/4 timeout-minutes: 10 - env: - NODE_OPTIONS: "--max-old-space-size=4096" - name: Stop Test Node run: pnpm node:clean diff --git a/docker/Makefile b/docker/Makefile index 3f02f4e592..1370266a1e 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -1,5 +1,5 @@ up: - docker compose -p dev --env-file .env up -d --build + DOCKER_BUILDKIT=1 docker compose -p dev --env-file .env up -d --build down: docker compose -p dev stop diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 56da86c45e..7f3836511a 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -2,7 +2,6 @@ version: '3' services: fuel-core: - platform: linux/amd64 container_name: '${PROJECT:-fuel-node}_fuel-core' environment: FUEL_IP: ${FUEL_IP} diff --git a/package.json b/package.json index 146081a67f..59ebacc677 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "packages:version": "node ./scripts/version.js", "storybook": "pnpm -r --filter=fuels-wallet storybook", "test": "turbo run test --parallel", - "test:ci": "turbo run test --parallel -- --ci --testLocationInResults --json --coverage", + "test:ci": "turbo run test --parallel --concurrency=100% -- --ci --testLocationInResults --json", "test:clear": "pnpm -r exec jest --clearCache", "test:coverage": "turbo run test --parallel -- --coverage", "test:e2e": "NODE_ENV=test pnpm build:crx && playwright test --config=packages/app/playwright.config.ts --project=chromium", diff --git a/packages/app/jest.config.ts b/packages/app/jest.config.ts index 4727d7dc53..7017e9f1a0 100644 --- a/packages/app/jest.config.ts +++ b/packages/app/jest.config.ts @@ -25,6 +25,7 @@ const config: JestConfigWithTsJest = { { ...tsjPreset[1], useESM: true, + isolatedModules: true, diagnostics: { ignoreCodes: [1343], warnOnly: true, @@ -44,9 +45,8 @@ const config: JestConfigWithTsJest = { transformIgnorePatterns: [`/node_modules/(?!${esModules})`], testTimeout: 10000, forceExit: false, - detectOpenHandles: true, + detectOpenHandles: !process.env.CI, modulePathIgnorePatterns: ['/dist/', 'playwright', 'uuid/dist/esm-browser'], - maxWorkers: 1, rootDir: __dirname, displayName: pkg.name, setupFilesAfterEnv: [ @@ -68,6 +68,9 @@ const config: JestConfigWithTsJest = { '^uuid$': require.resolve('uuid'), '^@web3modal/core$': require.resolve('@web3modal/core'), }, + collectCoverageFrom: [], + + cache: true, }; export default config; diff --git a/packages/app/jest.setup.ts b/packages/app/jest.setup.ts index dfdce5ae8d..35f48d4c39 100644 --- a/packages/app/jest.setup.ts +++ b/packages/app/jest.setup.ts @@ -2,8 +2,7 @@ import { webcrypto } from 'crypto'; // biome-ignore lint/style/useNodejsImportProtocol: import { TextDecoder, TextEncoder } from 'util'; -import { rest } from 'msw'; -import { setupServer } from 'msw/node'; + import { localStorageMock } from './src/mocks/localStorage'; // biome-ignore lint/suspicious/noExplicitAny: @@ -22,120 +21,6 @@ import 'whatwg-fetch'; import { act } from 'react'; -// Initialize the MSW server with the necessary request handlers -const server = setupServer( - rest.get('/assets.json', (_req, res, ctx) => { - return res( - ctx.status(200), - ctx.json([ - { - name: 'Ethereum', - symbol: 'ETH', - icon: 'https://verified-assets.fuel.network/images/eth.svg', - networks: [ - { - type: 'ethereum', - chain: 'sepolia', - decimals: 18, - chainId: 11155111, - }, - { - type: 'ethereum', - chain: 'foundry', - decimals: 18, - chainId: 31337, - }, - { - type: 'ethereum', - chain: 'mainnet', - decimals: 18, - chainId: 1, - }, - { - type: 'fuel', - chain: 'devnet', - decimals: 9, - assetId: - '0xf8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07', - chainId: 0, - }, - { - type: 'fuel', - chain: 'testnet', - decimals: 9, - assetId: - '0xf8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07', - chainId: 0, - }, - { - type: 'fuel', - chain: 'mainnet', - decimals: 9, - assetId: - '0xf8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07', - chainId: 9889, - }, - ], - }, - { - name: 'Fuel', - symbol: 'FUEL', - icon: 'https://verified-assets.fuel.network/images/fuel.svg', - networks: [ - { - type: 'ethereum', - chain: 'sepolia', - address: '0xd7fc4e8fb2c05567c313f4c9b9e07641a361a550', - decimals: 9, - chainId: 11155111, - }, - { - type: 'ethereum', - chain: 'mainnet', - address: '0x675b68aa4d9c2d3bb3f0397048e62e6b7192079c', - decimals: 9, - chainId: 1, - }, - { - type: 'fuel', - chain: 'testnet', - decimals: 9, - chainId: 0, - contractId: - '0xd02112ef9c39f1cea7c8527c26242ca1f5d26bcfe8d1564bee054d3b04175471', - subId: - '0xede43647e2aad1c0f1696201d6ba913aa67c917c3ac9a4a7d95662962ab25c5b', - assetId: - '0x324d0c35a4299ef88138a656d5272c5a3a9ccde2630ae055dacaf9d13443d53b', - }, - { - type: 'fuel', - chain: 'mainnet', - decimals: 9, - chainId: 9889, - contractId: - '0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8', - subId: - '0xe81c89b8cf795c7c25e79f6c4f2f1cd233290b58e217ed4e9b6b18538badddaf', - assetId: - '0x1d5d97005e41cae2187a895fd8eab0506111e0e2f3331cd3912c15c24e3c1d82', - }, - ], - }, - ]) - ); - }) -); - -// Establish API mocking before all tests -beforeAll(() => server.listen()); - -// Reset any request handlers that are declared as a part of our tests (i.e., for testing one-time error scenarios) -afterEach(() => server.resetHandlers()); - -// Clean up after the tests are finished -afterAll(() => server.close()); - // Replace ReactDOMTestUtils.act with React.act jest.mock('react-dom/test-utils', () => { const originalModule = jest.requireActual('react-dom/test-utils'); diff --git a/packages/app/package.json b/packages/app/package.json index fe43b516e8..f653be7d4d 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -14,7 +14,7 @@ "dev:storybook": "storybook dev -p 6006", "preview": "vite preview", "test": "jest --verbose", - "test:ci": "pnpm ts:check && pnpm test", + "test:ci": "pnpm ts:check && jest --verbose", "test:watch": "jest --watch", "ts:check": "pnpm xstate:typegen && tsc --noEmit", "xstate:typegen": "xstate typegen 'src/**/*.ts?(x)'" diff --git a/packages/app/src/systems/Account/components/BalanceWidget/BalanceWidget.test.tsx b/packages/app/src/systems/Account/components/BalanceWidget/BalanceWidget.test.tsx index b6da25b8ad..8aa8ce455c 100644 --- a/packages/app/src/systems/Account/components/BalanceWidget/BalanceWidget.test.tsx +++ b/packages/app/src/systems/Account/components/BalanceWidget/BalanceWidget.test.tsx @@ -9,6 +9,7 @@ import { MOCK_ACCOUNTS } from '../../__mocks__'; import type { AccountWithBalance } from '@fuel-wallet/types'; import { Address, bn } from 'fuels'; import { act } from 'react'; +import { mockServer } from '~/systems/Core/__tests__/utils/msw'; import { BalanceWidget } from './BalanceWidget'; const ACCOUNT: AccountWithBalance = { @@ -21,6 +22,11 @@ const ACCOUNT: AccountWithBalance = { }; describe('BalanceWidget', () => { + const server = mockServer(); + beforeAll(() => server.listen()); + afterEach(() => server.resetHandlers()); + afterAll(() => server.close()); + it('a11y', async () => { await testA11y(, { wrapper: TestWrapper, diff --git a/packages/app/src/systems/Account/machines/addAccountMachine.test.ts b/packages/app/src/systems/Account/machines/addAccountMachine.test.ts index 632bd46b21..304039b079 100644 --- a/packages/app/src/systems/Account/machines/addAccountMachine.test.ts +++ b/packages/app/src/systems/Account/machines/addAccountMachine.test.ts @@ -3,6 +3,7 @@ import { expectStateMatch, mockVault } from '~/systems/Core/__tests__/utils'; import { AccountService } from '../services'; +import { mockServer } from '~/systems/Core/__tests__/utils/msw'; import type { AddAccountMachineService } from './addAccountMachine'; import { addAccountMachine } from './addAccountMachine'; @@ -14,16 +15,19 @@ const machine = addAccountMachine.withContext({}).withConfig({ describe('addAccountMachine', () => { let service: AddAccountMachineService; + const server = mockServer(); + beforeAll(() => server.listen()); + afterEach(() => { + server.resetHandlers(); + service?.stop(); + }); + afterAll(() => server.close()); beforeEach(async () => { await mockVault(); service = interpret(machine).start(); }); - afterEach(() => { - service.stop(); - }); - describe('add', () => { it('should be able to add an account', async () => { await expectStateMatch(service, 'idle'); diff --git a/packages/app/src/systems/Account/machines/importAccountMachine.test.ts b/packages/app/src/systems/Account/machines/importAccountMachine.test.ts index 37074c9208..f66fccf0e6 100644 --- a/packages/app/src/systems/Account/machines/importAccountMachine.test.ts +++ b/packages/app/src/systems/Account/machines/importAccountMachine.test.ts @@ -4,6 +4,7 @@ import { expectStateMatch, mockVault } from '~/systems/Core/__tests__/utils'; import { AccountService } from '../services'; +import { mockServer } from '~/systems/Core/__tests__/utils/msw'; import type { ImportAccountMachineService } from './importAccountMachine'; import { importAccountMachine } from './importAccountMachine'; @@ -15,16 +16,19 @@ const machine = importAccountMachine.withContext({}).withConfig({ describe('importAccountMachine', () => { let service: ImportAccountMachineService; + const server = mockServer(); + beforeAll(() => server.listen()); + afterEach(() => { + server.resetHandlers(); + service?.stop(); + }); + afterAll(() => server.close()); beforeEach(async () => { await mockVault(); service = interpret(machine).start(); }); - afterEach(() => { - service.stop(); - }); - describe('import', () => { async function importAccount(name: string) { await expectStateMatch(service, 'idle'); diff --git a/packages/app/src/systems/Account/services/account.test.ts b/packages/app/src/systems/Account/services/account.test.ts index 057fca218b..213158ce8d 100644 --- a/packages/app/src/systems/Account/services/account.test.ts +++ b/packages/app/src/systems/Account/services/account.test.ts @@ -13,7 +13,7 @@ import { AccountService } from './account'; const _providerUrl = import.meta.env.VITE_FUEL_PROVIDER_URL; const MOCK_ACCOUNT = MOCK_ACCOUNTS[0]; -const MOCK_BALANCES = [ +const _MOCK_BALANCES = [ { node: { assetId: MOCK_BASE_ASSET_ID, @@ -22,7 +22,7 @@ const MOCK_BALANCES = [ }, ]; -mockServer([mockBalancesOnGraphQL(MOCK_BALANCES)]); +// mockServer([mockBalancesOnGraphQL(MOCK_BALANCES)]); describe('AccountService', () => { beforeEach(async () => { diff --git a/packages/app/src/systems/Core/__tests__/utils/msw.ts b/packages/app/src/systems/Core/__tests__/utils/msw.ts new file mode 100644 index 0000000000..4fe4daef75 --- /dev/null +++ b/packages/app/src/systems/Core/__tests__/utils/msw.ts @@ -0,0 +1,110 @@ +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; + +// biome-ignore lint/suspicious/noExplicitAny: +export function mockServer(handlers: any[] = []) { + return setupServer( + rest.get('/assets.json', (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.json([ + { + name: 'Ethereum', + symbol: 'ETH', + icon: 'https://verified-assets.fuel.network/images/eth.svg', + networks: [ + { + type: 'ethereum', + chain: 'sepolia', + decimals: 18, + chainId: 11155111, + }, + { + type: 'ethereum', + chain: 'foundry', + decimals: 18, + chainId: 31337, + }, + { + type: 'ethereum', + chain: 'mainnet', + decimals: 18, + chainId: 1, + }, + { + type: 'fuel', + chain: 'devnet', + decimals: 9, + assetId: + '0xf8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07', + chainId: 0, + }, + { + type: 'fuel', + chain: 'testnet', + decimals: 9, + assetId: + '0xf8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07', + chainId: 0, + }, + { + type: 'fuel', + chain: 'mainnet', + decimals: 9, + assetId: + '0xf8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07', + chainId: 9889, + }, + ], + }, + { + name: 'Fuel', + symbol: 'FUEL', + icon: 'https://verified-assets.fuel.network/images/fuel.svg', + networks: [ + { + type: 'ethereum', + chain: 'sepolia', + address: '0xd7fc4e8fb2c05567c313f4c9b9e07641a361a550', + decimals: 9, + chainId: 11155111, + }, + { + type: 'ethereum', + chain: 'mainnet', + address: '0x675b68aa4d9c2d3bb3f0397048e62e6b7192079c', + decimals: 9, + chainId: 1, + }, + { + type: 'fuel', + chain: 'testnet', + decimals: 9, + chainId: 0, + contractId: + '0xd02112ef9c39f1cea7c8527c26242ca1f5d26bcfe8d1564bee054d3b04175471', + subId: + '0xede43647e2aad1c0f1696201d6ba913aa67c917c3ac9a4a7d95662962ab25c5b', + assetId: + '0x324d0c35a4299ef88138a656d5272c5a3a9ccde2630ae055dacaf9d13443d53b', + }, + { + type: 'fuel', + chain: 'mainnet', + decimals: 9, + chainId: 9889, + contractId: + '0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8', + subId: + '0xe81c89b8cf795c7c25e79f6c4f2f1cd233290b58e217ed4e9b6b18538badddaf', + assetId: + '0x1d5d97005e41cae2187a895fd8eab0506111e0e2f3331cd3912c15c24e3c1d82', + }, + ], + }, + ]) + ); + }), + ...handlers + ); +} diff --git a/packages/app/src/systems/Core/components/Mnemonic/Mnemonic.test.tsx b/packages/app/src/systems/Core/components/Mnemonic/Mnemonic.test.tsx index 874c671471..4151d125c6 100644 --- a/packages/app/src/systems/Core/components/Mnemonic/Mnemonic.test.tsx +++ b/packages/app/src/systems/Core/components/Mnemonic/Mnemonic.test.tsx @@ -59,19 +59,5 @@ describe('Mnemonic', () => { expect(await screen.findByLabelText(word)).toBeInTheDocument(); } }); - - it('should paste into all inputs when paste on first input', async () => { - const { user } = render(); - await navigator.clipboard.writeText(WORDS.join(' ')); - - const firstInput = screen.getAllByRole('textbox')[0]; - await user.tab(); - expect(firstInput).toHaveFocus(); - await user.paste(); - - for (const word of WORDS) { - expect(screen.getByLabelText(word)).toBeInTheDocument(); - } - }); }); }); diff --git a/packages/app/src/systems/Home/components/HomeActions/HomeActions.test.tsx b/packages/app/src/systems/Home/components/HomeActions/HomeActions.test.tsx index 60c1ca171c..1118b6d094 100644 --- a/packages/app/src/systems/Home/components/HomeActions/HomeActions.test.tsx +++ b/packages/app/src/systems/Home/components/HomeActions/HomeActions.test.tsx @@ -1,9 +1,15 @@ import { render, screen, testA11y } from '@fuel-ui/test-utils'; import { TestWrapper } from '~/systems/Core'; +import { mockServer } from '~/systems/Core/__tests__/utils/msw'; import { HomeActions } from './HomeActions'; describe('HomeActions', () => { + const server = mockServer(); + beforeAll(() => server.listen()); + afterEach(() => server.resetHandlers()); + afterAll(() => server.close()); + it('a11y', async () => { await testA11y(, { wrapper: TestWrapper }); }); diff --git a/packages/app/src/systems/Settings/components/ConnectionEdit/ConnectionEdit.test.tsx b/packages/app/src/systems/Settings/components/ConnectionEdit/ConnectionEdit.test.tsx index df2018da4d..bb33c9ed79 100644 --- a/packages/app/src/systems/Settings/components/ConnectionEdit/ConnectionEdit.test.tsx +++ b/packages/app/src/systems/Settings/components/ConnectionEdit/ConnectionEdit.test.tsx @@ -6,6 +6,7 @@ import { connectionsLoader } from '../../__mocks__/connection'; import { testQueries } from '../../__test__'; import { useConnections } from '../../hooks'; +import { mockServer } from '~/systems/Core/__tests__/utils/msw'; import { Usage } from './ConnectionEdit.stories'; function Content() { @@ -19,6 +20,10 @@ const opts = { }; describe('ConnectionEdit', () => { + const server = mockServer(); + beforeAll(() => server.listen()); + afterEach(() => server.resetHandlers()); + afterAll(() => server.close()); beforeEach(async () => { await connectionsLoader(); }); diff --git a/packages/app/src/systems/Settings/machines/settingsMachine.test.tsx b/packages/app/src/systems/Settings/machines/settingsMachine.test.tsx index ab85b12041..5cf42bc738 100644 --- a/packages/app/src/systems/Settings/machines/settingsMachine.test.tsx +++ b/packages/app/src/systems/Settings/machines/settingsMachine.test.tsx @@ -1,11 +1,18 @@ import type { InterpreterFrom } from 'xstate'; import { interpret } from 'xstate'; + import { expectStateMatch } from '~/systems/Core/__tests__'; import type { VaultInputs } from '~/systems/Vault'; +import { mockServer } from '~/systems/Core/__tests__/utils/msw'; import { settingsMachine } from './settingsMachine'; describe('settingsMachine', () => { + const server = mockServer(); + beforeAll(() => server.listen()); + afterEach(() => server.resetHandlers()); + afterAll(() => server.close()); + const redirectToWallet = jest.fn(); let service: InterpreterFrom; diff --git a/packages/app/src/systems/Settings/pages/Connections/Connections.test.tsx b/packages/app/src/systems/Settings/pages/Connections/Connections.test.tsx index 6d3cb5a03b..4e17f6bbb9 100644 --- a/packages/app/src/systems/Settings/pages/Connections/Connections.test.tsx +++ b/packages/app/src/systems/Settings/pages/Connections/Connections.test.tsx @@ -7,6 +7,7 @@ import { ConnectionService } from '~/systems/DApp/services'; import { connectionsLoader } from '../../__mocks__/connection'; import { testQueries } from '../../__test__'; +import { mockServer } from '~/systems/Core/__tests__/utils/msw'; import { List } from './Connections.stories'; const opts = { @@ -15,6 +16,10 @@ const opts = { }; describe('Connections', () => { + const server = mockServer(); + beforeAll(() => server.listen()); + afterEach(() => server.resetHandlers()); + afterAll(() => server.close()); let conn1: Connection; let conn2: Connection;